公式サポートに載っているバグ
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.
との記載があり、引数unit
にMD
を用いると誤った結果を返すことが読み取れる。
公式サポートに載っていないバグ
しかしながら、実際には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で正しい関数を作成
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