1
0

More than 1 year has passed since last update.

Microsoft Office Excel DateDif関数のバグ

Posted at

公式サポートに載っているバグ

Microsoft Office ExcelのDATEDIF(start_date,end_date,unit)関数には既知のバグがある。
Microsoftサポートページ(日本語, English )には

既知の問題
"MD" 引数は、負の値、ゼロ、または不正確な結果を返すことがあります。

Known issues
The "MD" argument may result in a negative number, a zero, or an inaccurate result.

との記載があり、引数unitMDを用いると誤った結果を返すことが読み取れる。

公式サポートに載っていないバグ

しかしながら、実際にはMD以外の引数も誤った結果を返すことがある。

バグの例

例として、2020年2月29日から2021年2月28日までの期間を数えてみよう。ちょうど1年(=12か月)であるが、DATEDIF関数を用いると次のような結果となる。

=DATEDIF(DATE(2020,2,29),DATE(2021,2,28),"Y")
→0(誤り。正しくは1)

=DATEDIF(DATE(2020,2,29),DATE(2021,2,28),"M")
→11(誤り。正しくは12)

=DATEDIF(DATE(2020,2,29),DATE(2021,2,28),"D")
→365(正しい)

=DATEDIF(DATE(2020,2,29),DATE(2021,2,28),"MD")
→30(誤り。正しくは0)

=DATEDIF(DATE(2020,2,29),DATE(2021,2,28),"YM")
→11(誤り。正しくは0)

=DATEDIF(DATE(2020,2,29),DATE(2021,2,28),"YD")
→365(誤り。正しくは0)

まとめ

要するに、unit引数がDのとき以外は誤った結果を返すことがある。
(なお、unit引数がDであればDATEDIF関数を使わずとも2つの日付を引き算するだけで求められる。)

VBAで正しい関数を作成

datedifmod.bas
Option Explicit

Public Function DATEDIFMOD(ByVal dateStart As Date, ByVal dateEnd As Date, ByVal strUnit As String) As Variant

    Dim lngTmp As Long, dateTmp As Date, blnTmp As Boolean, cnt As Long

    '初期値の明示的設定
    DATEDIFMOD = 0

    '開始日と終了日が逆だったら反転処理
    If dateStart = dateEnd Then
        Exit Function
    ElseIf dateStart > dateEnd Then
        blnTmp = True
        dateTmp = dateEnd
        dateEnd = dateStart
        dateStart = dateTmp
    End If

    Select Case strUnit
        Case "Y"
            '「開始日からy年後の日付 ≦ 終了日」を満たす最大のyをDATEDIFMODに代入
            Do While DateAdd("yyyy", DATEDIFMOD + 1, dateStart) <= dateEnd
                DATEDIFMOD = DATEDIFMOD + 1
            Loop

        Case "YM"
            '「開始日からmか月後の日付 ≦ 終了日」を満たす最大のmをDATEDIFMODに代入
            Do While DateAdd("m", DATEDIFMOD + 1, dateStart) <= dateEnd
                DATEDIFMOD = DATEDIFMOD + 1
            Loop
            '12で割った余りをDATEDIFMODに代入
            DATEDIFMOD = DATEDIFMOD Mod 12

        Case "M"
            '「開始日からmか月後の日付 ≦ 終了日」を満たす最大のmをDATEDIFMODに代入
            Do While DateAdd("m", DATEDIFMOD + 1, dateStart) <= dateEnd
                DATEDIFMOD = DATEDIFMOD + 1
            Loop

        Case "MD"
            '「開始日からmか月後の日付 ≦ 終了日」を満たす最大のmをDATEDIFMODに代入
            Do While DateAdd("m", DATEDIFMOD + 1, dateStart) <= dateEnd
                DATEDIFMOD = DATEDIFMOD + 1
            Loop
            '開始日から当該mか月後の日付から終了日(片端)の日数をDATEDIFMODに代入
            DATEDIFMOD = dateEnd - DateAdd("m", DATEDIFMOD, dateStart)

        Case "YD"
            '「開始日からy年後の日付 ≦ 終了日」を満たす最大のyをDATEDIFMODに代入
            Do While DateAdd("yyyy", DATEDIFMOD + 1, dateStart) <= dateEnd
                DATEDIFMOD = DATEDIFMOD + 1
            Loop
            '開始日から当該y年後の日付から終了日(片端)の日数をDATEDIFMODに代入
            DATEDIFMOD = dateEnd - DateAdd("yyyy", DATEDIFMOD, dateStart)
        
        Case "D"
            DATEDIFMOD = dateEnd - dateStart

        Case Else
            DATEDIFMOD = CVErr(xlErrNum)
            Exit Function
    End Select

    '開始日と終了日が逆だったら日数を負値として返す
    If blnTmp Then
        DATEDIFMOD = DATEDIFMOD * (-1)
    End If

End Function
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0