In vb.net i implement an Excel formula, using Microsoft.Office.Interop.Excel. The returnvalue is a 2D object-array, which contains Strings. To print the returnvalue into multiple cells im using a matrix-formula.
Problem: If one of the Strings in the array has more then 255 letters, i get a #VALUE! error in every cell. Is that fixable or can i just bypass that?
Function in vb.net: Public Function multivalue() As Object
Formula in Excel: {=multivalue()}
I also tried it in VBA, same error
Public Function testF() As Variant
Dim Output(1, 1) As Object
Output(0, 0) = 1
Output(0, 1) = 2
Output(1, 0) = 3
Output(1, 1) = "String with over 255 letters: There are seven days of the week, or uniquely named 24-hour periods designed to provide scheduling context and make time more easily measureable. Each of these days is identifiable by specific plans, moods, and tones. Monday is viewed by many to be the worst day of the week, as it marks the return to work following the weekend, when most full-time employees are given two days off."
testF = Output
EndFunction
Your VBA code has some errors.
Your VBA code returns the #VALUE! error to the worksheet because you are trying to assign contents to an Object as if it were an array. The error comes from this line: Output(0,0) = 1
Output could be either of type Variant or type String
Output and testF need to be of the same type.
The following works as expected:
Option Explicit
Public Function testF() As String()
Dim Output(1, 1) As String
Output(0, 0) = 1
Output(0, 1) = 2
Output(1, 0) = 3
Output(1, 1) = "String with over 255 letters: There are seven days of the week, or uniquely named 24-hour periods designed to provide scheduling context and make time more easily measureable. Each of these days is identifiable by specific plans, moods, and tones. Monday is viewed by many to be the worst day of the week, as it marks the return to work following the weekend, when most full-time employees are given two days off."
testF = Output
End Function
or
Option Explicit
Public Function testF() As Variant
Dim Output(1, 1) As Variant
Output(0, 0) = 1
Output(0, 1) = 2
Output(1, 0) = 3
Output(1, 1) = "String with over 255 letters: There are seven days of the week, or uniquely named 24-hour periods designed to provide scheduling context and make time more easily measureable. Each of these days is identifiable by specific plans, moods, and tones. Monday is viewed by many to be the worst day of the week, as it marks the return to work following the weekend, when most full-time employees are given two days off."
testF = Output
End Function
Related
Microsoft Excel does not recognise pre-1900 dates and there is plenty of information online which documents this, including the question here.
The best work around (which many other posts link to) seems to be at ExcelUser
However, although the work around gets Excel to recognise a pre-1900 date as a date, it still does not allow it to be used in calculations e.g. when wanting to calculate the number of years since a pre-1900 date.
My question is whether the work around described at ExcelUser can be modified to allow the result to be used in a calculation.
To put things simply, for example, I want to calculate in Excel the number of years since 1/4/1756 - is this possible?
Or does another solution have to be adopted? Perhaps there are plug-ins which address this problem?
First of all I highly recommend to use the ISO 8601 format yyyy-mm-dd for dates because even if you only have strings and no real numeric dates this is properly sortable and the only date format that is defined clearly and cannot be misinterpret like 02/03/2021 where no one can ever say if it is mm/dd/yyyy or dd/mm/yyyy because both actually exist.
Since old dates cannot be real numeric dates but only entered as strings (looking like a date) that means misinterpretation needs to be avioded or you get wrong results. Therefore a date format that cannot be misinterpret is a clear advantage.
Second there is more than one way to calculate "how many years since the birth of Mr. X": For example lets take the birthday of Maryam Mirzakhani 1977-05-12 compared to the date today 2021-04-15. Today she would not have had birthday yet this year and therefore she would be 43 years old. But this year she would have turned 44 years (2021 - 1977 = 44). So the question needs to be asked more precisely. Either "how old would Mr. X be today?" or "how old would Mr. X be this year". The calculation for that would be different.
So let's start and assume the following data. We already know the fact that Excel cannot calculate with dates before 1900. You can see that if we enter pre-1900 dates that they are formatted as string (red dates) and post-1900 dates get formatted as numeric dates (green dates).
Image 1: #WERT! means #VALUE! (sorry for the German screenshot).
Also in column D where the formula =DATEDIF($B2,TODAY(),"y") was used the string dates cannot be calculated with. But since VBA can actually handle pre-1900 dates we can write our own UDF (user defined function) for that. Since as I explained above there is 2 different methods to calculate there is 2 different functions:
OldDateDiff(Date1, Date2, Interval) called like =OldDateDiff($B2,TODAY(),"yyyy")
OldDateAge(Date1, Date2) called like =OldDateAge($B2,TODAY())
Option Explicit
Public Function OldDateDiff(ByVal Date1 As Variant, ByVal Date2 As Variant, ByVal Interval As String) As Long
Dim RetVal As Long 'variable for the value we want to return
Dim localDate1 As Date
If VarType(Date1) = vbDate Or VarType(Date1) = vbDouble Then 'check if Date1 is numeric
localDate1 = CDate(Date1) 'if numeric take it
ElseIf VarType(Date1) = vbString Then 'check if Date1 is a string
localDate1 = ISO8601StringToDate(Date1) 'if it is a string convert it to numeric
Else 'neither string nor numeric throw an error
RetVal = CVErr(xlErrValue)
Exit Function
End If
Dim localDate2 As Date 'same as for Date1 but with Date2
If VarType(Date2) = vbDate Or VarType(Date2) = vbDouble Then
localDate2 = CDate(Date2)
ElseIf VarType(Date2) = vbString Then
localDate2 = ISO8601StringToDate(Date2)
Else
RetVal = CVErr(xlErrValue)
Exit Function
End If
If localDate1 <> 0 And localDate2 <> 0 Then 'make sure both dates were filled with values
RetVal = DateDiff(Interval, localDate1, localDate2) 'calculate the difference between dates with the desired interaval eg yyyy for years
End If
OldDateDiff = RetVal 'return the difference as result of the function
End Function
Public Function OldDateAge(ByVal Date1 As Variant, ByVal Date2 As Variant) As Long
Dim RetVal As Long 'variable for the value we want to return
Dim localDate1 As Date
If VarType(Date1) = vbDate Or VarType(Date1) = vbDouble Then 'check if Date1 is numeric
localDate1 = CDate(Date1) 'if numeric take it
ElseIf VarType(Date1) = vbString Then 'check if Date1 is a string
localDate1 = ISO8601StringToDate(Date1) 'if it is a string convert it to numeric
Else 'neither string nor numeric throw an error
RetVal = CVErr(xlErrValue)
Exit Function
End If
Dim localDate2 As Date 'same as for Date1 but with Date2
If VarType(Date2) = vbDate Or VarType(Date2) = vbDouble Then
localDate2 = CDate(Date2)
ElseIf VarType(Date2) = vbString Then
localDate2 = ISO8601StringToDate(Date2)
Else
RetVal = CVErr(xlErrValue)
Exit Function
End If
If localDate1 <> 0 And localDate2 <> 0 Then 'make sure both dates were filled with values
RetVal = WorksheetFunction.RoundDown((localDate2 - localDate1) / 365, 0)
'subtract date1 from date2 and divide by 365 to get years, then round down to full years to respect the birthday date.
End If
OldDateAge = RetVal 'return the age as result of the function
End Function
' convert yyyy-mm-dd string into numeric date
Private Function ISO8601StringToDate(ByVal ISO8601String As String) As Date
Dim ISO8601Split() As String
ISO8601Split = Split(ISO8601String, "-") 'split input yyyy-mm-dd by dashes into an array with 3 parts
ISO8601StringToDate = DateSerial(ISO8601Split(0), ISO8601Split(1), ISO8601Split(2)) 'DateSerial returns a real numeric date
' ≙yyyy ≙mm ≙dd
End Function
Note that here column B contains 2 different kind of data. Strings (that look like a date) and real numeric dates. If you sort them, all the numeric dates will sort before the string dates (which is probably not what you want). So if you want this to be sortable by birthday column make sure you turn all dates into strings. This can be done by adding an apostrophe ' infront of every date. This will not display but ensure the entered date is considered to be a string.
If your date is in an unambiguous format (eg ISO or a format corresponding to your Windows Regional Settings, or a real date if after 1900), you can use VBA which will recognize early dates.
Function Age(dt As Date)
Age = DateDiff("yyyy", dt, Date)
End Function
You should be aware that, because of how the function calculates years differences, depending on what you want exactly for a result, you may need to adjust the answer if the birthdate is before/after today's date.
In other words, if the day of the year of the birthdate is after the day of the year of Today, you may need to subtract 1 from the result.
But this should get you started.
There's a much easier way than the accepted answer. Simply convert your dates to Unix time:
Function nUnixTime(dTimestamp As Date) As LongLong
' Return given VB date converted to a Unix timestamp.
Const nSecondsPerDay As Long = 86400 ' 24 * 60 * 60
nUnixTime = Int(CDbl(CDate(dTimestamp) - CDate("1/1/1970"))) * nSecondsPerDay
End Function
Unix time is the number of seconds since Jan. 1, 1970, with times before that date being negative. So if you convert your dates to Unix time, you can just subtract them and divide the result by 86,400 to have the difference in days, or by 31,557,600 for years (31,557,600 = 60 * 60 * 24 * 365.25).
Example results of the above VB function called from Excel:
Column A
Column B formula
Column B value
2/2/2022
=nUnixTime(A1)
1,643,760,000
1/1/1970
=nUnixTime(A2)
0
12/14/1901
=nUnixTime(A3)
-2,147,472,000
12/13/1901
=nUnixTime(A4)
-2,147,558,400
1/1/1900
=nUnixTime(A5)
-2,208,988,800
1/1/1800
=nUnixTime(A6)
-5,364,662,400
1/1/100
=nUnixTime(A7)
-59,011,459,200
The reason I included the two dates in 1901 is because their magnitudes in Unix time are just smaller than and just larger than the largest magnitude of a signed 32-bit integer, i.e., a Long in VBA. If the output of the above function were a Long, then values for dates before Dec. 14, 1901 would be the error #Value!. That is the reason the output of the function is defined as LongLong, which is VBA's signed 64-bit integer.
I have an excel spreadsheet where 1s represent whether a person was still contracted with a company and 0 represents that they were not. We have a 4 month reporting term and we need to find which people left within the reporting term, when they started and when they left.
In the above example, 7/1 - 10/1 would be the report dates. It would return that Person G started 5/1/2020 and ended 9/1/2020 and that Person H started 4/1/2020 and ended 8/1/2020.
I was thinking of writing a VBA script that took the reporting start date and end date as input from the user, finding any 1s within those dates, and returning the date that corresponds with the 1 in the reporting term and the first 1 in a range of consecutive 1s. Problem is that I'm not sure how to scan the row of dates with the VBA script and account for scenarios where a person started and ended in the reporting term (or started outside the reporting term and ended in the reporting term and then started and ended again within the reporting term).
Does anyone have some good suggestions for how best to go about this? Thank you in advance.
I recommend using two program cases:
There is a (1,0) sequence, which means that the term ended. If this pattern is detected, that means that the person ended by the second cell in that sequence.
There is a (0,1) sequence, which means that the term started. If this pattern is detected, that means that the person started by the second cell in that sequence.
This can be done using a for loop that tracks the current value and last known value:
Dim currVal As Integer, prevVal as Integer
prevVal = 0
Dim i as Integer
For i = startColumn to endColumn
currVal = Cells(row, i)
If currVal = 0 And prevVal = 1 Then
endDate = Cells(1,i)
ElseIf currVal = 1 And prevVal = 0 Then
startDate = Cells(1,i)
End If
prevVal = currVal
Next i
Then, you would just need variables to capture the start/end dates, and to fill in which rows and columns you are processing.
I have 10 years worth of monthly returns of an asset. I am trying to create a function that can give me the annualised returns of this series of monthly return data. I would like to use the Offset function to go to the last monthly returns date, select an array of periods that I choose (3 months back, 6 months back, 12 months back, 24 months back etc) so that I can get the annualised return of a series of returns from the end date back to a specific period.
The goal is to calculate the monthly average monthly returns of the last 3 months, 6 months, 12 months etc.
However, the parameters for the Offset function in VBA are different to those in normal Excel functions. I think I need to use the .Resize function to resize my Offset function.
Here is how the function works in Excel
=GEOMEAN(1+OFFSET($W$4,MATCH(LARGE($V$4:$V$10485,1),$V$4:$V$10485,0)-1,0,-$AJ17,1))-1
Rewritten so that you can understand my variables
=GEOMEAN(1+OFFSET(ref,MATCH(LARGE(Dates,1),Dates,0)-1,0,-period,1))-1
Here is the code that I have written so far...
Function Returns(ref As Range, Dates As Range, Period As Integer) As Variant
With Application.WorksheetFunction
Returns = .Geomean(1 + ref.Resize(-Period, 1).Offset(.Match(.Large(Dates, 1), Dates, 0) - 1, 0))-1
End With
End Function
Please, can someone help?
Okay, so I figured out a few of my errors.
A) I declared the variables incorrectly.
B) I used a negative number for resize as I wanted the function to go to the end of the series and count back (to get 6 month. 12 month, 24 month returns). This does not work.
As an alternative, I have used LARGE to find the nth largest date and select the array below that point.
See code below.
Function AnnReturns(Ref As Variant, Dates As Variant, Period As Variant, M As Variant) As Variant
With Application.WorksheetFunction
AnnReturns = .FVSchedule(1, (Ref.Offset(.Match(.Large(Dates, Period), Dates, 0) - 1, 0).Resize(Period, 1))) ^ (1 / M) - 1
End With
End Function
I have written some VBA functions (listed in code below)
I am comparing records from two worksheets using functions to return the related values from one sheet to the other.
The first function, upon which all other functions depend on, returns the Patient ID number.
Criteria to select a Patient ID:
The function compares date and time of patient arrival within a 30
minute interval (since the information recieved from one source
usually varies by a few minutes from the other), gender, clinic ID,
and birthyear. Patient ID numbers start at around 50000, and go on
until around 150000. I need to compare date and time, because from
time to time two patients with the same gender, birthdate and clinic
arrived on the same day.
The function fails after 100000's rows
Beyond this only #VALUE! errors are returned.
Following is a complex scenario I tested, and found the Date and Time to be at fault.
Comparing only Date, with no interval, returns a normal value.
The last Patient ID to work is 98472 (not all Patient IDs have been reported yet), the Patient has an arrival date of May 1st, 2018 at
8:42pm.
The next Patient ID is 100471, arriving on the 4th of May, 2018 at 10:43am. * The function returns this Patient as a #VALUE! error,
although all parameters are there.
Here is the code (pardon any rookie mistakes, I'm no professional coder):
Function EINSATZ(aufnahmdat As Date, geburtsdat As Integer, geschlecht As Integer, klinik As Integer)
'DEFINING PARAMETERS
'rsu_r is the regional stroke unit row
'rsu_c is the regional stroke unit column
'size is the patient list size
'iffunction allows the function to work through the patient list
'converter converts letter to integer for sex
Dim rsu_r As Integer
Dim rsu_c As Integer
Dim size As Variant
Dim iffunction As Single
Dim converter As Integer
'here starts the dimension definition for rsu cells
rsu_r = ActiveCell.Row
rsu_c = ActiveCell.Column
'here starts the size function
'size is predetermined to measure and print the highest value within the first 9996 cells
For iffunction = 4 To 9999
If Application.WorksheetFunction.IsNumber(Worksheets("Präklinik").Cells(iffunction, 5)) Then
size = size + 1
End If
Next iffunction
'here starts the if function
For iffunction = 4 To size
If Worksheets("Präklinik").Cells(iffunction, 6).Value = "m" Then
converter = 2
Else
converter = 1
End If
If Worksheets("Präklinik").Cells(iffunction, 4).Value + Worksheets("Präklinik").Cells(iffunction, 17).Value < aufnahmdat + 1 / 48 _
And Worksheets("Präklinik").Cells(iffunction, 4).Value + Worksheets("Präklinik").Cells(iffunction, 17).Value > aufnahmdat - 1 / 48 _
And Worksheets("Präklinik").Cells(iffunction, 5).Value = geburtsdat _
And converter = geschlecht _
And Worksheets("Präklinik").Cells(iffunction, 41).Value = klinik Then
EINSATZ = Worksheets("Präklinik").Cells(iffunction, 2).Value
Exit For
End If
Next iffunction
End Function
Please help me diagnose the actual cause of error!
I routinely get data in a time field column on an Excel spreadsheet that I need to convert into basic numbers such as 6, 7, 8.
Here's an example of the mess I get:
(Note: I cannot change how the data is received)
Time
-------------------------------
8am
10am
9:00 am (no early birds please)
8:00
9 a.m Sharp
7:00 am
8am-2pm
9 am
8AM TO 3PM
08-10-2018 9am
7:30am, 8:30 am
Any
9am
8AM TO 3PM
Today thru sun
10:00 a.m.
I recently ran some code that would pull out all numbers from the cell, then remove everything except the first number and that's somewhat close but...
The perfect scenario would be code to output the desired numbers and if cell data wouldn't comply with the code intent (error), a message box would open allowing edits, or, the cell would be flagged with a color so I knew where to look. I end up with about 500 lines of these cells to ferret through. Any suggestions would be appreciated.
While you are considering text comprehension, here's a cheap and dirty VBA function that might get a fair chunk of your cases. I would implement this in combination with conditional formatting that highlights cells greater than or equal to one or zero. (My example screenshot has such formatting in place.)
Public Function GetDate(DateVal As String) As Date
Dim returnDate As Date ' Date to be returned
Dim cleanedDate As String ' Working string containing the candidate date
Dim wordIndex As Integer ' Counter for walking through the word array
Dim wordArray() As String ' Working string broken into words
' Initialize to zero date
returnDate = CDate(0)
If IsNumeric(DateVal) Then
' Handle dates already converted
returnDate = CDate(DateVal)
Else
' Eliminate spurious characters
cleanedDate = UCase(Replace(Replace(Replace(DateVal, ".", ""), ",", " "), "-", " "))
If IsDate(cleanedDate) Then
' If CDate can fix it, just do that.
returnDate = TimeValue(CDate(cleanedDate))
Else
wordArray() = Split(cleanedDate, " ")
If UBound(wordArray) > 0 Then
wordIndex = 0
' Look through each word and return the first time found
Do Until returnDate > 0 Or wordIndex = UBound(wordArray)
If IsDate(wordArray(wordIndex)) Then
returnDate = CDate(wordArray(wordIndex))
End If
wordIndex = wordIndex + 1
Loop
End If
End If
End If
GetDate = returnDate
End Function
Here is what I get with your sample data in column A and =PERSONAL.XLSB!GetDate(A1) (or equivalent) in column B:
Note that rows 4, 6, and 8 show time values. This is because Excel converted the values when I pasted your sample data in place. The function handles values that Excel automatically converts.
Fixing Row 5 in code could get tricky. You'll have to weigh that coding effort against the simple manual fix of deleting the space between 9 and a.m.
The code gets about 80% of your example cases. If the ratio holds for your overall data, you're looking at manually adjusting roughly 100 cells. If you're doing this regularly, consider further tweaking of the code. One last ditch effort might be to walk through the string from left to right character by character to see if you can find and build a time string.
...or, of course, you can use text comprehension as mentioned above. :-)
Good Luck!