簡體   English   中英

Excel 電子表格中使用 1900 年前日期的計算

[英]Calculations in Excel spreadsheet using pre-1900 date

Microsoft Excel 無法識別 1900 年之前的日期,網上有大量信息記錄了這一點,包括此處的問題。

最好的解決方法(許多其他帖子鏈接到)似乎是在ExcelUser

然而,盡管解決方法讓 Excel 將 1900 年前的日期識別為日期,但它仍然不允許將其用於計算中,例如當想要計算自 1900 年前日期以來的年數時。

我的問題是是否可以修改 ExcelUser 中描述的解決方法以允許在計算中使用結果。

簡單地說,例如,我想在 Excel 中計算自 1756 年 1 月 4 日以來的年數 - 這可能嗎?

還是必須采用其他解決方案? 也許有插件可以解決這個問題?

首先,我強烈建議對日期使用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM