简体   繁体   中英

Calculations in Excel spreadsheet using pre-1900 date

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 eg 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, ie, 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM