首先,我强烈建议对日期使用ISO 8601 格式yyyy-mm-dd,因为即使您只有字符串而没有真正的数字日期,这也是可正确排序的,并且是唯一明确定义且不会被误解的日期格式比如02/03/2021,没有人能说它是mm/dd/yyyy还是dd/mm/yyyy,因为两者都确实存在。
由于旧日期不能是真正的数字日期,而只能作为字符串输入(看起来像日期),这意味着需要避免误解,否则您会得到错误的结果。因此,不会被误解的日期格式是一个明显的优势。
其次,计算“X先生出生多少年”的方法不止一种:例如,将Maryam Mirzakhani1977-05-12的生日与今天的日期进行比较2021-04-15。今天她今年还没有过生日,因此她将是43 岁。但今年她会变成44 年(2021 - 1977 = 44)。所以这个问题需要问得更准确。 “X 先生今天几岁?” 或“X 先生今年几岁”。计算方式会有所不同。
让我们开始并假设以下数据。我们已经知道 Excel 无法使用 1900 年之前的日期进行计算。您可以看到,如果我们输入 1900 年之前的日期,它们会被格式化为字符串(红色日期),而 1900 年之后的日期会被格式化为数字日期(绿色日期)。
图 1:#WERT! 表示 #VALUE!(对于德语截图很抱歉)。
同样在 D 列中,使用了公式 =DATEDIF($B2,TODAY(),"y") 的字符串日期无法计算。但由于 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 种不同类型的数据。字符串(看起来像日期)和实数日期。如果您对它们进行排序,所有数字日期将在字符串日期之前排序(这可能不是您想要的)。因此,如果您希望它可以按生日列排序,请确保将 all 日期转换为字符串。这可以通过在每个日期前面添加撇号 ' 来完成。这不会显示,但请确保输入的日期被视为字符串。