[英]Calculations in Excel spreadsheet using pre-1900 date
首先,我強烈建議對日期使用ISO 8601格式yyyy-mm-dd
,因為即使您只有字符串且沒有真正的數字日期,這也是可正確排序的,並且唯一的日期格式定義明確且不能像02/03/2021
那樣被誤解02/03/2021
沒有人能說它是mm/dd/yyyy
還是dd/mm/yyyy
,因為兩者都確實存在。
由於舊日期不能是真正的數字日期,而只能作為字符串輸入(看起來像日期),這意味着需要避免誤解,否則您會得到錯誤的結果。 因此,不能被誤解的日期格式是一個明顯的優勢。
其次,計算“X 先生出生多少年”的方法不止一種:例如,讓Maryam Mirzakhani的生日 1977-05-12 與今天的日期1977-05-12
2021-04-15
。 今天她今年還沒有過生日,因此她已經43
歲了。 但今年她將滿44
歲( 2021 - 1977 = 44
)。 所以這個問題需要問得更准確。 要么是“X先生今天幾歲?” 或者“ X先生今年多大了” 。 對此的計算會有所不同。
因此,讓我們開始並假設以下數據。 我們已經知道 Excel 無法使用 1900 年之前的日期進行計算。您可以看到,如果我們輸入 1900 年之前的日期,它們會被格式化為字符串(紅色日期),而 1900 年之后的日期會被格式化為數字日期(綠色日期)。
圖 1:
#WERT!
表示#VALUE!
(對不起德國的截圖)。
同樣在使用公式=DATEDIF($B2,TODAY(),"y")
的 D 列中,無法計算字符串日期。 但是由於 VBA 實際上可以處理 1900 年前的日期,我們可以為此編寫自己的 UDF(用戶定義函數)。 因為正如我上面解釋的,有 2 種不同的計算方法,所以有 2 種不同的函數:
OldDateDiff(Date1, Date2, Interval)
稱為=OldDateDiff($B2,TODAY(),"yyyy")
OldDateAge(Date1, Date2)
像=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
請注意,此處 B 列包含 2 種不同類型的數據。 字符串(看起來像日期)和實數日期。 如果對它們進行排序,所有數字日期將在字符串日期之前排序(這可能不是您想要的)。 因此,如果您希望它可以按生日列排序,請確保將所有日期轉換為字符串。 這可以通過在每個日期前添加撇號'
來完成。 這不會顯示,但要確保輸入的日期被視為字符串。
如果您的日期采用明確的格式(例如 ISO 或與您的 Windows 區域設置相對應的格式,或者 1900 年之后的真實日期),您可以使用 VBA 來識別早期日期。
Function Age(dt As Date)
Age = DateDiff("yyyy", dt, Date)
End Function
您應該知道,由於 function 如何計算年份差異,具體取決於您想要的結果,如果生日在今天之前/之后,您可能需要調整答案。
換句話說,如果生日的年份在今天之后,您可能需要從結果中減去1
。
但這應該讓你開始。
有一種比接受的答案更簡單的方法。 只需將您的日期轉換為 Unix 時間:
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 時間是自 1970 年 1 月 1 日以來的秒數,該日期之前的時間為負數。 因此,如果您將日期轉換為 Unix 時間,您只需將它們相減,然后將結果除以 86,400 即可得出天數之差,或者除以 31,557,600 年份(31,557,600 = 60 * 60 * 24 * 365.25)。
從 Excel 調用的上述 VB function 的示例結果:
A欄 | B列公式 | B 列值 |
---|---|---|
2/2/2022 | =nUnixTime(A1) |
1,643,760,000 |
1970 年 1 月 1 日 | =nUnixTime(A2) |
0 |
1901 年 12 月 14 日 | =nUnixTime(A3) |
-2,147,472,000 |
1901 年 12 月 13 日 | =nUnixTime(A4) |
-2,147,558,400 |
1900 年 1 月 1 日 | =nUnixTime(A5) |
-2,208,988,800 |
1800 年 1 月 1 日 | =nUnixTime(A6) |
-5,364,662,400 |
1/1/100 | =nUnixTime(A7) |
-59,011,459,200 |
我在 1901 年包含這兩個日期的原因是因為它們在 Unix 時間中的量級僅小於或大於帶符號的 32 位 integer 的最大量級,即 Z6E3EC7E6A9F6007B4AZ83 中的Long
。 如果上述 function 的 output 是Long
,那么 1901 年 12 月 14 日之前的日期值將是錯誤#Value!
. 這就是將 function 的 output 定義為LongLong
的原因,也就是 VBA 的有符號 64 位 integer。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.