ロゴ(青) Excel/VBA Tips ロゴ(緑)

Tips05: 『期間計算(y年mヶ月d日)マクロ』        

kt関数アドイン/ktPeriodYMD関数で使っている期間計算マクロ([1a]:民法規定による算出)
です。モーグ(Eccel/VBA ,2003/4)で公開しましたので、ここでも公開します。活用して下さい。

※ Excel : DATEDIF 関数 の バグ について ※
  (1) Excel 発売当初から存在している バグ    [ 詳細 ( 1 ) , ( 2 ) ]
    閏年の2/29 に絡んで YD & MD の算出結果に誤り ( 時にはマイナスになります ) があります。
しかし、Lotus 1-2-3 との互換性維持の為に用意されたという背景から DATEDIF は [ 隠し関数 ] の
扱いとなっており、その位置付け故に Microsoft は積極的に修正するという姿勢を見せていません。
Excel2010 でも、このバグは健在です

  (2) Excel2007 での算出結果が [ Excel2003以前&Excel2010 ] と異なる バグ    [ 詳 細 ]
    Excel2007 で施された変更によって、YD & MD の算出に、使用に耐えられない程の著しい ミス
増えました。その後、[ SP2 ] [ KB 969682 ] と2回の修正が試みられ、かなり改善(上記 (1) の バグ を
含んだ状態の Excel2003以前の算出に、ほぼ戻った) されましたが、未だ一部で計算ミス が残っています。
MD には [ 終了日が 閏年&1月 ] 限定で 100 を超える値を返すバグが残っています。
2012/2/20 : Excel2007(SP3) の DATEDIF 不具合に関する 技術情報が公開されました ( KB2678753 )
2014/2/12 : Excel2007(SP3) で 修正アップデート KB2827324 (2013/10/8 リリース) の適用によって、
                上記で紹介した補正値(誤差)が変わっている事が判明しました。

2014/3/7 : この デグレード に伴い MS-KB ( KB2678753 )が修正されました。



  (1)  JavaScript による日数計算シミュレーター
  (2)  期間計算マクロ ktDATEDIF (初日算入オプション付き バージョン)
  (3)            〃        ktDATEDIF (初日不算入 ベーシック バージョン)
  (4)  [M:期間内満総月数] の使い方について
  (5)  期間計算の民法規定
  (6)  年比率の考え方 : 抽象的2月29日説 / 具体的2月29日説
  (7)  Excel YEARFRAC (基準:1 Act/Act )
  (8)  YEARFRAC (基準:1 Act/Act ) で年齢計算する危うさについて
  (9)  OOo.Calc YEARFRAC ( 基準:1 Act/Act )
  (10) Act/Act ( AFB ) の計算方式
  (11) Google SpreadSheet  YEARFRAC ( 基準:1 Act/Act)
  (12) Excel2007 で増えた DATEDIF 関数の不具合の紹介 , Excel2007[YD / MD] ロジックの考察

-------------------------------------------------------------------------
引数『開始日』『終了日』に対し、民法規定に従って【y年mヶ月d日】を求めるマクロです。

ktDATEDIFの基本計算ルールは「初日不算入」ですが、年齢計算の際には『初日算入』
する必要があります(民法 期間計算の原則)。
    初日算入機能あり Ver. -- FirstDay オプションを True にする。
                                          ktDATEDIF(誕生日, 現在日付, "Y", True)
    初日算入機能なし Ver. -- 「終了日」 に 『+1』する。
                                          ktDATEDIF(誕生日, 現在日付 + 1, "Y")

[ 2005/4/17 ]
     今まで、計算部分のマクロだけの掲載(2003/4/23〜)でしたが、「関数」仕立てにしてDATEDIF関数の代用となるようにしました。
     尚、[y年mヶ月d日]編集 と [初日算入/不算入]指定 の機能を付け足してあります。
[ 2005/4/18 ]
    Excel/VBA だけでなく、OpenOffice.org [Calc] Basic でも利用できるように、日本語変数名を止めました。
    下記のマクロは、VBA および OOoBasic で動作します。
[ 2008/3/22 ]
    YD (年未満の端数日数) の計算機能を加えました。また、Excel 向けに省略引数仕様にしました。
[ 2008/10/24 ]
    年比率( YEARFRAC ) の計算オプションを加えました。
[ 2008/10/26 ]
    日付計算のシミュレーションを加えました。
[ 2010/5/30 ]
    Google SpreadSheet の YEARFRAC(Act/Act) の調査を行ないました。
[ 2012/1/17 ]
    ktDATEDIF (初日算入 , 初日不算入 両バージョン) 内の [ intMonth As Integer ] を [ lngMonth As Long ] に修正する。
    理由: 2日付の間隔が 2730年 を超えると オーバーフローにより #VALUE となる為



[ この場所へのリンク ]
----------------------------------------------------------------------------------------------
JavaScriptによる日数計算  日付をYYYY/M/Dで入力(日付ボタンで暦表示)。
  から の期間を
     (実日数)
     年   ヶ月   日 (by ktDATEDIF)
     年と   日間 (by ktDATEDIF)
    【 年比率 】
     [ 年 + 年未満日数 ÷  ] (by ktDATEDIF 抽象的2月29日説)
     [ 実日数 ÷  ] (Excel の YEARFRAC [基準:1,Act/Act] )
     (OOo.Calc の YEARFRAC [基準:1,Act/Act] )
     (Google SpreadSheet の YEARFRAC [基準:1,Act/Act] , ISDA 準拠 )
----------------------------------------------------------------------------------------------


[ この場所へのリンク ]
 (お願い) Q&Aサイトの掲示板等に下記 マクロ コード を丸々貼り付ける事は御遠慮願います。この マクロ コード を
              紹介される場合は、上記 リンク URL の提示にてお願いします。

[ 2012/1/17 修正 ]  2日付の間隔が 2730年 を超えると オーバーフローにより #VALUE となる不具合の修正
    intMonth As Integer  ⇒  lngMonth As Long


  [初日算入] 機能を省いたバージョンは こちら
  -- [初日算入] 機能あり バージョン ---
Public Function ktDATEDIF(ByVal StartDate As Date, _
                          ByVal EndDate As Date, _
                          Optional ByVal Interval As String = "YMD", _
                          Optional ByVal FirstDay As Boolean = FalseAs Variant
' OOo.BasicではOptionalでの初期値が指定できない
'                         ByVal Interval As String, _
'                         ByVal FirstDay As Boolean) As Variant

' [Interval]
'    YMD:"y年mヶ月d日"で編集(文字列)
'    Y :期間内満年数
'    M :期間内満総月数
'    YM:年未満の端数の月数
'    MD:月未満の端数の日数
'    YD:年未満の端数の日数
'    FR:年比率(Double型, YEARFRACモード)
' [FirstDay]
'    初日算入=True, 初日不算入=False (民法規定は[初日 不算入]が基本)

'(注)
' Interval="FR"は、YEARFRAC関数の[基準:1(Actual/Actual)]に相当しますが、
' YEARFRACとは計算方法が異なり、『抽象的2月29日説』に基づいています。
' 実務規則に合致しているかを確認してから利用して下さい。
' (例)
'  2003/5/10 - 2007/6/20 → 4年41日間 → 4.1120218579 ( = 41/366)
'    [2003/5/10(ex) - 2007/5/10(in)]で4年間。[2007/5/11(in) - 6/20(in)]で41日間。
'    [2007/5/11(in) - 6/20 - 2008/5/10(in)]の間に2/29があるので366日/年。
'
'  2003/5/10 - 2008/6/20 → 5年41日間 → 5.1123287671 ( = 41/365)
'    [2003/5/10(ex) - 2008/5/10(in)]で5年間。[2008/5/11(in) - 6/20(in)]で41日間。
'    [2008/5/11(in) - 6/20 - 2009/5/10(in)]の間に2/29がないので365日/年。

Dim dtmFirstDate As Date            '起算日
Dim dtmLastDate As Date             '満了日
Dim wkDate As Date
Dim lngMonth As Long                '総月数(13ヶ月 など)
Dim intYear As Integer
Dim intMonth_in_Year As Integer     '年未満の月数
Dim intDay_in_Month As Integer      '月未満の日数
Dim intDay_in_Year As Integer       '年未満の日数
Dim dblFrac As Double
Dim intFracDays As Integer          '[年未満の日数]部分の初日から1年間の日数
                                    '(その1年間に2/29がある→366、ない→365)

  If (StartDate > EndDate) Then
    ktDATEDIF = CVErr(xlErrValue)
    'ktDATEDIF = "Error"            'OOo.BasicではCVErr(xlErrValue)が使えない
    Exit Function
  End If

  '起算日の設定
  If (FirstDay = FalseThen
    dtmFirstDate = StartDate + 1    '民法規定は[初日 不算入]が基本
  Else
    dtmFirstDate = StartDate        'True:[初日算入]指定
  End If
  dtmLastDate = EndDate

  '注)[DateDiff関数]自体は初日不算入で処理している
' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  If (Day(dtmFirstDate) = 1) Then
    '起算日が1日(開始日が月末日)の場合、月の大小閏に係わりなく
    '[月末日]までで『丸Nヶ月』
    '月の大小閏に影響されないように「月末日〜終了日」を1日分シフトして
    '「1日〜(終了日+1)」で求める。
    lngMonth = DateDiff("m", dtmFirstDate, (dtmLastDate + 1))
    intYear = lngMonth \ 12
    intMonth_in_Year = lngMonth Mod 12
    If (Day(dtmLastDate + 1) = 1) Then      '満了日(終了日)が月末か?
      intDay_in_Month = 0
    Else
      intDay_in_Month = Day(dtmLastDate)
    End If

    wkDate = DateAdd("yyyy", intYear, dtmFirstDate)
    intDay_in_Year = DateDiff("d", wkDate, (dtmLastDate + 1))

    If (intDay_in_Year > 0) Then
        intFracDays = DateAdd("yyyy", 1, wkDate) - wkDate      '365 or 366
    End If
  Else
    '起算日≠1日の場合、「起算日の応当日前日」までで『丸Nヶ月』
    lngMonth = DateDiff("m", (dtmFirstDate - 1), dtmLastDate)
    wkDate = DateAdd("m", lngMonth, (dtmFirstDate - 1))
    If (wkDate > dtmLastDate) Then
      lngMonth = lngMonth - 1
      wkDate = DateAdd("m", lngMonth, (dtmFirstDate - 1))
    End If
    intDay_in_Month = DateDiff("d", wkDate, dtmLastDate)
    intYear = lngMonth \ 12
    intMonth_in_Year = lngMonth Mod 12

    wkDate = DateAdd("yyyy", intYear, (dtmFirstDate - 1))
    intDay_in_Year = DateDiff("d", wkDate, dtmLastDate)

    If (intDay_in_Year > 0) Then
        intFracDays = DateAdd("yyyy", 1, wkDate) - wkDate      '365 or 366
    End If
  End If

' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  Select Case UCase(Interval)
    Case "YMD"
      ktDATEDIF = intYear & "年" & intMonth_in_Year & "ヶ月" & intDay_in_Month & "日"
    Case "Y"
      ktDATEDIF = intYear
    Case "M"
      ktDATEDIF = lngMonth
    Case "YM"
      ktDATEDIF = intMonth_in_Year
    Case "MD"
      ktDATEDIF = intDay_in_Month
    Case "YD"
      ktDATEDIF = intDay_in_Year
    Case "FR"
      If (intDay_in_Year > 0) Then
        dblFrac = intDay_in_Year / CDbl(intFracDays)  '整数演算とならないように型変換
        ktDATEDIF = CDbl(intYear + dblFrac)
      Else
        ktDATEDIF = CDbl(intYear)               '年未満の日数が無いので小数は出ない
      End If
    Case Else
      ktDATEDIF = CVErr(xlErrValue)
      'ktDATEDIF = "Error"            'OOo.BasicではCVErr(xlErrValue)が使えない
  End Select
End Function



[ この場所へのリンク ]
[初日算入] 機能を盛り込んだ為に、dtmFirstDate で「1日足したのに、また1日引いて」という
ロジックになっています。この辺りの意味が良く理解できないという方の為に、[初日算入]機能を
省いたバージョンを用意しましたので、理解する上での助けとしてください。(2008/3/24 追記)

 (お願い) Q&Aサイトの掲示板等に下記 マクロ コード を丸々貼り付ける事は御遠慮願います。この マクロ コード を
              紹介される場合は、上記 リンク URL の提示にてお願いします。

[ 2012/1/17 修正 ]  2日付の間隔が 2730年 を超えると オーバーフローにより #VALUE となる不具合の修正
    intMonth As Integer  ⇒  lngMonth As Long

  -- [初日算入] 機能を省いたバージョン ---    [ シミュレーション ]
Public Function ktDATEDIF(ByVal StartDate As Date, _
                          ByVal EndDate As Date, _
                          ByVal Interval As StringAs Variant
' [Interval]
'    YMD :"y年mヶ月d日"で編集(文字列)
'    Y   :期間内満年数
'    M   :期間内満総月数
'    YM  :年未満の端数の月数
'    MD  :月未満の端数の日数
'    YD  :年未満の端数の日数
'    FR  :年比率(Double型, YEARFRACモード)

'(注)
' Interval="FR"は、YEARFRAC関数の[基準:1(Actual/Actual)]に相当しますが、
' YEARFRACとは計算方法が異なり、『抽象的2月29日説』に基づいています。
' 実務規則に合致しているかを確認してから利用して下さい。
' (例)
'  2003/5/10 - 2007/6/20 → 4年41日間 → 4.1120218579 ( = 41/366)
'    [2003/5/10(ex) - 2007/5/10(in)]で4年間。[2007/5/11(in) - 6/20(in)]で41日間。
'    [2007/5/11(in) - 6/20 - 2008/5/10(in)]の間に2/29があるので366日/年。
'
'  2003/5/10 - 2008/6/20 → 5年41日間 → 5.1123287671 ( = 41/365)
'    [2003/5/10(ex) - 2008/5/10(in)]で5年間。[2008/5/11(in) - 6/20(in)]で41日間。
'    [2008/5/11(in) - 6/20 - 2009/5/10(in)]の間に2/29がないので365日/年。

Dim wkDate As Date
Dim lngMonth As Long                '総月数(13ヶ月 など)
Dim intYear As Integer
Dim intMonth_in_Year As Integer     '年未満の月数
Dim intDay_in_Month As Integer      '月未満の日数
Dim intDay_in_Year As Integer       '年未満の日数
Dim dblFrac As Double
Dim intFracDays As Integer          '[年未満の日数]部分の初日から1年間の日数
                                    '(その1年間に2/29がある→366、ない→365)

  If (StartDate > EndDate) Then
    ktDATEDIF = CVErr(xlErrValue)
    'ktDATEDIF = "Error"            'OOo.BasicではCVErr(xlErrValue)が使えない
    Exit Function
  End If

  '注)[DateDiff関数]自体は初日不算入で処理している
' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  If (Day(StartDate + 1) = 1) Then
    '起算日が1日(開始日が月末日)の場合、月の大小閏に係わりなく
    '[月末日]までで『丸Nヶ月』
    '月の大小閏に影響されないように「月末日〜終了日」を1日分シフトして
    '「1日〜(終了日+1)」で求める。
    lngMonth = DateDiff("m", (StartDate + 1), (EndDate + 1))
    intYear = lngMonth \ 12
    intMonth_in_Year = lngMonth Mod 12
    If (Day(EndDate + 1) = 1) Then      '満了日(終了日)が月末か?
      intDay_in_Month = 0
    Else
      intDay_in_Month = Day(EndDate)
    End If

    wkDate = DateAdd("yyyy", intYear, (StartDate + 1))     '(StartDate+1)は[1日]
    intDay_in_Year = DateDiff("d", wkDate, (EndDate + 1))

    If (intDay_in_Year > 0) Then
        intFracDays = DateAdd("yyyy", 1, wkDate) - wkDate      '365 or 366
    End If
  Else
    '起算日≠1日の場合、「起算日の応当日前日」までで『丸Nヶ月』
    lngMonth = DateDiff("m", StartDate, EndDate)
    wkDate = DateAdd("m", lngMonth, StartDate)
    If (wkDate > EndDate) Then
      lngMonth = lngMonth - 1
      wkDate = DateAdd("m", lngMonth, StartDate)
    End If
    intDay_in_Month = DateDiff("d", wkDate, EndDate)
    intYear = lngMonth \ 12
    intMonth_in_Year = lngMonth Mod 12

    wkDate = DateAdd("yyyy", intYear, StartDate)
    intDay_in_Year = DateDiff("d", wkDate, EndDate)

    If (intDay_in_Year > 0) Then
        intFracDays = DateAdd("yyyy", 1, wkDate) - wkDate      '365 or 366
    End If
  End If

' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  Select Case UCase(Interval)
    Case "YMD"
      ktDATEDIF = intYear & "年" & intMonth_in_Year & "ヶ月" & intDay_in_Month & "日"
    Case "Y"
      ktDATEDIF = intYear
    Case "M"
      ktDATEDIF = lngMonth
    Case "YM"
      ktDATEDIF = intMonth_in_Year
    Case "MD"
      ktDATEDIF = intDay_in_Month
    Case "YD"
      ktDATEDIF = intDay_in_Year
    Case "FR"
      If (intDay_in_Year > 0) Then
        dblFrac = intDay_in_Year / CDbl(intFracDays)  '整数演算とならないように型変換
        ktDATEDIF = CDbl(intYear + dblFrac)
      Else
        ktDATEDIF = CDbl(intYear)               '年未満の日数が無いので小数は出ない
      End If
    Case Else
      ktDATEDIF = CVErr(xlErrValue)
      'ktDATEDIF = "Error"            'OOo.BasicではCVErr(xlErrValue)が使えない
  End Select
End Function


[ この場所へのリンク ]
---- [M:期間内満総月数] の使い方について ----
  "y年mヶ月" 同士の加減算を行なう場合、文字列の"y年mヶ月"のままでは加減算が
  出来ないので、数値である「満総月数(1年1ヶ月なら 13ヶ月)」で加減算を行ない、
  結果の月数に対して『12による商と剰余』を求め「年と月」に編集します。
        Excel : =INT(月数の結果/12) & "年" & MOD(月数の結果,12) & "ヶ月"
        VBA  : (月数の結果 \ 12) & "年" & (月数の結果 Mod 12) & "ヶ月"

  尚、"y年mヶ月d日"同士の加減算は、『算出の定義』が明確になっていない限りは、
  無意味です(何日で1ヶ月と勘定するのか等)。
 
-------------------------------------------------------------------
  期間計算を行うにあたっては、「その期間計算で処理する業務」に係わる当事者間で、
定義されている算出方法とは「何か?」が一番大事です(仕様に合っていないプログラム
を作っても、それで得られる結果は、その業務では役に立たない)。
  例えば、「民法の定義に従って求める」、「初日/末日の算入/不算入はこうする」、
「入社月は途中入社でも丸1ヶ月とカウントする」、「1ヶ月は30日間で計算する」といった
個々の業務特有の条件などがありますので、その辺を良く検討する必要があります。
  一般的には、特に定めが明記されていない場合の算出基準は『民法に従う』と解釈
されています。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[ この場所へのリンク ]
  民法における、期間の正式な定義についてはこちらのページを参考にして下さい。

民法規定 を判り易く整理すると下記のようになります。  (2008/3/25 追記)

      a ) 「期間の初日」 はカウントしない。即ち、引数:開始日に指定した日付はカウントせず、その翌日
          からカウントする。
      b ) 「1日」 からカウントする場合は(即ち、引数:開始日に月末日を指定)、月の大小閏に係わりなく、
          月末日までで 「満 n ヶ月」、開始日と同月の月末日までで 「満 n 年」 となる。
     c ) 「1日」 以外からカウントする場合は(即ち、引数:開始日が月末日でない)、開始日と「同日」 まで
          で 「満 n ヶ月」、開始日と 「同月日」 までで 「満 n 年」 となる。但し、月の大小閏の関係で対応す
          る 「同日」 「同月日」 が存在しない場合は、月末日までで 「満 n ヶ月」 「満 n 年」 となる。

 補足 ) DATEDIF 引数: Y , M , YM , MD , YD の意味
          上記仕様で計算される 「満 n ヶ月( 13ヶ月 など )」 が [M]の月数。それを12で割った商が [Y] の
          年数で、余りが [YM] の月数。
          「満 n ヶ月」 となる日付の翌日から [引数:終了日] までの日数が [MD] の日数。
          「満 n 年」 となる日付の翌日から[引数:終了日] までの日数が [YD] の日数。
         (注) DATEDIF は上記仕様とは完全には一致していません(異なる結果を返す。DATEDIF のバグ )。


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[ この場所へのリンク ]
 『抽象的 2月 29日説』 および 『具体的 2月 29日説』 について
  YEARFRAC 関数で算出される 「期間日数の年比率」 は、Excel が米国製ソフトということもあり、欧米圏において
  使われている方式 ( 30/360方式 など ) で計算されます。
  「基準:1 ( 実際の日数/実際の日数 )」 は、括弧内の表現からカレンダー通りに計算されて日本でも通用するよう
  に見えますが、日本で一般的な計算方式とは異なる計算を行っていますので、これもまた日本では使わない方が
  良いでしょう。

  日本においては、 『抽象的 2月 29日説』 という方式が一般的です。詳細は下記を参照して下さい。
       「金利及び弁済金額計算に関する法律と実務」抜粋 大阪弁護士会所属 弁護士 服部 廣志
            『金利計算と閏年・端数処理について』      http://www.ilc.gr.jp/journal/000109/

  難しく書かれていますが、『抽象的2月29日説』 を要約すると以下のように計算します。
      a) [開始日〜終了日] から [ y 年 と d 日間 ] という期間を算出する(The remainning final stub period )。
      b) 年比率 = y + ( d ÷ 年間日数[365 or 366] ) で比率を計算する。
      c) 365 と 366 の何れで割るかの判断は、半端日数部分 (d日間) の初日から見て、向こう1年間の間に
          2月29日が有れば 366、無ければ 365 で割ります(向こう1年間の年間日数で割るという事です)。
               2003/5/10 〜 2007/6/20  →  4年 と 41日間 , 4.1120218579
               2003/5/10 〜 2008/6/20  →  5年 と 41日間 , 5.1123287671
          前者は、2007/5/11[含] 〜 6/20 〜 2008/5/10 の間に 2/29 が有るので [ 41÷366 ]
          後者は、2008/5/11[含] 〜 6/20 〜 2009/5/10 の間に 2/29 が無いので [ 41÷365 ]

  尚、『具体的 2月29日説』 は「半端日数部分の期間内に 2月29日が有るか無いか」 で判断する点が
  異なります。2006/3/1 〜 2007/3/10 の場合、
        [2006/3/1 〜 2007/2/28] で1年、[2007/3/1[含]〜2007/3/10] が残りの半端期間(10日間)
  抽象的 の方では [ 10 ÷ 366 ] ですが、具体的 の方では [ 10 ÷ 365 ] です。


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[ この場所へのリンク ]
 『Excel  YEARFRAC 基準:1 (Actual/Actual)』 について
  [Actual /Actual ] と記される、実日数で年比率を計算する欧米圏の算出方式にはISDA , ISMA , AFB など
  幾つかの方式が規定されていますが、Excel の YEARFRAC (基準:1 Actual / Actual ) が行っている計算は
  これらの何れとも合致しません。
  Microsoft に問い合わせたところ、何故この計算方式を採ったのかという理由は、現在 Microsoft 自体でも判ら
  ないとの事です ( 2008/10/22  ) 。
  仕様書がこのような計算手順を指示したのか、仕様が曖昧な為にプログラマーが仕様の曖昧部分を補完しつ
  つ独自にロジックを考えてプログラムしたものなのか?

  YEARFRAC の算出結果から調査したところ、以下の手順で計算を行っていると判明しました。
      (1) 開始日と終了日が 同一年 の場合
            その年の日数(365 or 366)で割る
      (2) 開始日〜終了日が 複数年 に跨る場合
            開始日〜終了日の期間(開始日除く)が
            (a) 1年以下の場合
                  開始日〜終了日(開始日も含む) に[2/29] が含まれれば 366、無ければ 365 で期間日数を割る
            (b) 1年超の場合
                  期間の跨る全ての年(開始日=12/31の場合でも開始年を含む) から[ 年間日数(365 or 366) ]の
                  平均を求め、その値(365.25 , 365.33… 等)で期間日数を割る
     ※ (a)(b)下線の部分は、初日不算入(開始日そのものは期間に含まれない) の条件を考えれば明らかに
          間違った処理(余計なカウント)です。

  【 Excel  YEARFRAC (基準:1 Actual/Actual ) の 実装ロジック 】  [ シミュレーション ]

Public Function YEARFRAC_Act(ByVal StartDate As DateByVal EndDate As DateAs Double
Dim wkDate As Date
Dim dtmLeapDay As Date
Dim dblDaysYear As Double
Dim lngDays As Long

    wkDate = DateAdd("yyyy", 1, StartDate)
    If (Year(StartDate) = Year(EndDate)) Then
        '開始日/終了日が同一年
        If (Month(DateSerial(Year(StartDate), 2, 29)) = 2) Then
            dblDaysYear = 366
        Else
            dblDaysYear = 365
        End If
    ElseIf (EndDate <= wkDate) Then
        '年跨りで1年以下の期間
        '(開始日[含]〜終了日に[2/29]が有れば366、無ければ365で割る)
        If (Month(DateSerial(Year(StartDate), 2, 29)) = 2) Then
            '閏年(年内) or 閏年→平年
            dtmLeapDay = DateSerial(Year(StartDate), 2, 29)
            If (StartDate <= dtmLeapDay) And (dtmLeapDay <= EndDate) Then
                dblDaysYear = 366
            Else
                dblDaysYear = 365
            End If
        ElseIf (Month(DateSerial(Year(EndDate), 2, 29)) = 2) Then
            '平年→閏年
            dtmLeapDay = DateSerial(Year(EndDate), 2, 29)
            If (StartDate <= dtmLeapDay) And (dtmLeapDay <= EndDate) Then
                dblDaysYear = 366
            Else
                dblDaysYear = 365
            End If
        Else
            '平年→平年
            dblDaysYear = 365
        End If
    Else
        '1年超の期間(経過年の平均[年間日数]で割る)
        lngDays = DateSerial(Year(EndDate), 12, 31) - DateSerial(Year(StartDate), 1, 0)
        dblDaysYear = lngDays / (Year(EndDate) - Year(StartDate) + 1)
    End If

    YEARFRAC_Act = (EndDate - StartDate) / dblDaysYear
End Function


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[ この場所へのリンク ]
  『YEARFRAC(基準:1) で 年齢計算 する危うさ』 について
  多くの Excel 解説サイトや Q & A サイト で、YEARFRAC(基準:1) を使って年齢計算する方法が紹介
  されています (時には、[ 基準:0 (NASD) ] を使っていることさえあります) 。計算結果には期待するも
  のと小数部分でのズレが出る為に、INT/ROUND/ROUNDUP/ROUNDDOWN 関数を併用して紹介し
  ている場合が多いですね。

  しかし、これら丸め関数との併用を行なっても、YEARFRAC では常に正しい年齢の計算ができるとは
  限りません。下記の例で説明します (ここでは、誕生日の日に1歳増するものとしています。法律
  通りに誕生日の前日で1歳増とする場合には =YEARFRAC(開始日, 終了日 + 1, 1) とします)。

                       年齢   YEARFRAC      INT  ROUND  ROUNDUP  ROUNDDOWN
  2004/1/30〜2017/1/30  13  13.0007821666   13    13     14       13
  2004/3/30〜2018/3/30  14  13.9979923344   13    14     14       13
  -----------------------------------------------------------------------
  2004/3/30〜2018/3/29  13  13.9952546085   13    14     14       13
  2004/3/30〜2018/3/31  14  14.0007300602   14    14     15       14

  前2つでは、丁度 「誕生日の日」 になっていますので、各々13歳・14歳と計算される筈ですが、どち
  らにも小数部分のズレが出ています。INT/ROUNDUP/ROUNDDOWN では両方とも正しい年齢を得
  ることは出来ていません。ROUND関数による四捨五入では上手くいっているようです。

  このケースから 「ROUND関数と併用すれば問題なし」 と決めてしまうのは早計です。丁度誕生日に
  なる日にだけ計算する訳ではありません。誕生日以外の日で計算する場合もあるのです。

  後2つは、14歳の誕生日の前後の日で計算しています。当然、13歳と14歳として計算されなければ
  なりません。このケースではROUND関数も失格になってしまいました (逆にINT/ROUNDDOWNが上
  手くいっているようにみえます)。

  このように、丸め関数を併用したとしても、扱う日付によって上手くいく場合と駄目な場合があります。つ
  まり、丸め関数を併用したとしても YEARFRAC 関数では 【 常に正しい年齢計算 】 は出来ません。
  何故出来ないのかは、この上で解説している YEARFRAC 関数の計算方式を読んでください (そもそも、
  暦に従った計算ではなく、Excel 独自の怪しい計算なのですから、正しい結果など望むべくもありま
  せん) 。

  キーワード 「YEARFRAC  年齢」 で検索すると、この使い方が如何に氾濫しているかが判ると思います。
  これは MS 自体が間違った例を紹介しているというのも影響しているのでしょう。
      Microsoft Office Online [ VLOOKUP / 使用例3 ]  INTとの併用で年齢計算(基準:1)


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[ この場所へのリンク ]
 『OOo.Calc  YEARFRAC 基準:1 (Actual/Actual)』 について
    参考 : OpenOffice.org Wiki  ( YEARFRAC , Financial date systems )
   この調査は [ Open Office.org 2.4.0 および 3.0.0 ( 現 Apache OpenOffice ) ] で行なっています。

  OOo.Calc の YEARFRAC (Basis : 1, Act/Act) についても算出結果を分析したところ、Excel のものとは
  異なり、下記の算出方法を採っているようです。
      a) [ 開始日〜終了日] から [ y 年 と d 日間 ] という期間を算出する (The remainning final stub period )。
      b) 年比率 = y + ( d ÷ 年間日数 [ 365 or 366 ] ) で比率を計算する。
      c) 365 と 366 の何れで割るかは、開始日が属する年の年間日数とする。
          (開始日=12/31 の場合でも [ 開始日の年] の年間日数で割る)。

  これに ピッタリ と符合する規定は見当たりません。OASIS ODF 仕様 ではYEARFRAC の項にAct/Act は
  AFB 方式 と記載されています。その AFB 仕様を見てみると確かに近いようですが、期間(年数)を数える
  「向き」 と、365/366の何れで割るかの判断方法が上記の調査結果と異なります。ところで、c) のカッコ内
  の条件は、初日不算入の前提から考えれば 「翌年の年間日数」 とするべきところではないかと思えます
  (開始年の日は全く期間に加わっていませんから)。

  なお、上記 a) 〜 c) の仕様だけでは解決しない微妙なズレがありました。そこで、ktDATEDIF [YD] による
  下記6パターンの計算結果を用意して、これと YEARFRAC の結果を比較したところ、[ d日間] の算出過程で
  [ ±1日増減 ] している事が判りました。
            =YEARFRAC (A1;B1;1)
            =ktDATEDIF (A1;B1;"y") + (ktDATEDIF (A1;B1;"yd") / 365)
            =ktDATEDIF (A1;B1;"y") + ((ktDATEDIF (A1;B1;"yd") - 1) / 365)
            =ktDATEDIF (A1;B1;"y") + ((ktDATEDIF (A1;B1;"yd") + 1) / 365)
            =ktDATEDIF (A1;B1;"y") + (ktDATEDIF (A1;B1;"yd") / 366)
            =ktDATEDIF (A1;B1;"y") + ((ktDATEDIF (A1;B1;"yd") - 1) / 366)
            =ktDATEDIF (A1;B1;"y") + ((ktDATEDIF (A1;B1;"yd") + 1) / 366)

            2007/2/28 〜 2008/2/29  …… 「1年1日」      ( ktDATEDIF では 「1年0日」 )
            2004/2/29 〜 2005/2/28  …… 「0年365日」   (        〃             「1年0日」 )
            2006/3/31 〜 2008/3/30  …… 「1年364日」   (        〃             「1年365日」 )
            2004/3/31 〜 2006/3/30  …… 「1年365日」   (        〃             「1年364日」 )  等

  増減パターンを整理すると以下のようになっています( M1D1, M2D2 は各々 開始日/終了日の [月日] )。
      (1) [ 2月末日〜2月末日] の場合、平年/閏年に関わらず 「N 年0日」 と算出するべきところを、±1日
          増減している( 2007/2/28→2008/2/29 が 「1年1日」 、2004/2/29→2005/2/28 が 「0年365日」 、
          初日不算入の計算ならば、この2つは[ 3/1からカウントして2月末日で満1年] なので「1年0日」 )。
      (2) 平年→閏年で、M1D1>M2D2 の場合、d が1日少ない ( M1D1 > 2/28 )。
          ただし、終了年が 開始年の翌年(このケースでは期間が 1年以下 になる)では正しい。
      (3) 閏年→平年で、M1D1>M2D2 の場合、d が1日多い。
          ただし、終了年が [ 閏年( 開始年以外でも) の翌年 ] では正しい。
      (4) 閏年→閏年で、M1D1>M2D2 の場合、dが1日多い (M1D1 < 2/29)。(終了年の前年は[平年])
      (5) 平年→平年で、M1D1>M2D2 の場合、dが1日少ない (M1D1<2/28)。
          ただし、終了年が [ 閏年の翌年 ] 以外では正しい。
      補足: (2) の状況は この ISSUE (バグ情報) でも報告されてます。

  上記のパターンを満たす計算方式を検討した結果、以下のロジックと同等のものであろうと推察されます。
  この計算は、下図の二重線部分(端数期間) の期間日数を下記手順で行なっています。
      ------------(丸y年)--------><----(端数d日間)---->
     -#-------------------#--------*==========#=========*---------#--  ( # : 1月1日 )
                          <--------[a]-------><---[c]--->
                          <--[b]-->
        ( i ) 日数 = [c] + ( [a] - [b] )      [a] [b] [c] は1月1日からの年内通算日(1〜366)
        ( ii ) [a] には、[a] 本来の年間日数ではなく、開始日の年の年間日数(365 or 366) を採用する。
        ( iii ) 上図で、[b] は本来、終了日[c]の前年であるが、何故か、終了日の年の中で算出する。
        上記 (ii) (iii) によって、2/29 の [1日分] が 余計に加算/減算されるという結果になります。

  【 OOo.Calc  YEARFRAC (基準:1 Actual/Actual ) の 実装ロジック 】  [ シミュレーション ]
Public Function ooo_YEARFRAC(ByVal StartDate As DateByVal EndDate As DateAs Double
Dim intYears As Integer
Dim intDays As Integer
Dim SDate As Integer
Dim EDate As Integer

    '年内の通算日(端数の日数計算用)、開始[日]も終了年の中に設定する
    SDate = DatePart("y", DateSerial(Year(EndDate), Month(StartDate), Day(StartDate)))
    EDate = DatePart("y", EndDate)

    If (Format(StartDate, "mmdd") <= Format(EndDate, "mmdd")) Then
        intYears = Year(EndDate) - Year(StartDate)
        '(1/1〜終了日の通算日数) - (1/1〜開始[日]の通算日数)
        intDays = EDate - SDate
    Else
        If (Year(StartDate) + 1 = Year(EndDate)) Then
            '終了日が開始日の翌年(開始月日>終了月日なので1年未満の期間)
            intYears = 0
            intDays = EndDate - StartDate
        Else
            '1年以上の期間
            intYears = Year(EndDate) - Year(StartDate) - 1
            '(開始[日]〜12/31までの日数) + (1/1〜終了の通算日数)
            '年間日数は開始日の[年]を適用する
            If (Month(DateSerial(Year(StartDate), 2, 29)) = 2) Then
                intDays = (366 - SDate) + EDate
            Else
                intDays = (365 - SDate) + EDate
            End If
        End If
    End If

    '端数期間の年間日数は開始日の[年]を適用する
    If (Month(DateSerial(Year(StartDate), 2, 29)) = 2) Then
        ooo_YEARFRAC = intYears + CDbl(intDays) / 366#
    Else
        ooo_YEARFRAC = intYears + CDbl(intDays) / 365#
    End If
End Function

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  OOo.Calc はオープンソフトなので、本物の ソースコード(GetYearFrac & GetDiffParam) が閲覧で
  きました。その ソースコード をVBA風に編集(計算手順はそのまま)したものが下記です。上記の
  想定コードとは異なりますが、行なっている計算の意味合いは同じです。何故結果が間違ってしまう
  のかを理解するには上記の想定コードの方が視覚的で判り易いでしょう。
 【 OOo.Calc の 本物のコード (VBA風アレンジ版) 】
Public Function OOo_YEARFRAC(ByVal StartDate As DateByVal EndDate As DateAs Double
Dim intDaysInYear As Integer
Dim intYears As Integer
Dim intDays As Integer
    If (Month(DateSerial(Year(StartDate), 2, 29)) = 2) Then
        intDaysInYear = 366
    Else
        intDaysInYear = 365
    End If

    If (Format(StartDate, "mmdd") <= Format(EndDate, "mmdd")) Then
        intYears = Year(EndDate) - Year(StartDate)
    Else
        intYears = Year(EndDate) - Year(StartDate) - 1
    End If

    If (intYears = 0) Then
        intDays = EndDate - StartDate
    Else
        intDays = EndDate - DateSerial(Year(EndDate), Month(StartDate), Day(StartDate))
        If (intDays < 0) Then
            intDays = intDays + intDaysInYear
        End If
    End If

    OOo_YEARFRAC = intYears + CDbl(intDays) / CDbl(intDaysInYear)
End Function
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

[ この場所へのリンク ]
 ---- Act/Act (AFB)  ( or Act/365L ) の計算方式について ---
  下記の解説を読むと、Act/Act (AFB)
      「終了日から遡って 1年間・2年間 と数え、最後に残った"開始日から始まる端数期間"
        部分を、 365 or 366 で割る ( その端数期間内に2月29日が有る/無しに拠る ) 。
        なお、2/28 の1年前は、その年が 閏年なら 2/29、平年なら 2/28 とします。 」
  という仕様のようです( The remaining initial stub period ) 。これは、期間を数える向きを逆にした
  『具体的 2月29日説』 といえるでしょう。

  AFB : Association Francaise des. Banques , フランス銀行協会

    ISDA ドキュメント   - Page 3 --  Actual/Actual ( AFB )
    Under the AFB method, the denominator is either 365 (if the calculation period does not contain
    29th February) or 366 (if the calculation period includes 29th February) - where a period of longer
    than one year is involved, two or more calculations are made: interest is calculated for each full
    year, counting backwards from the end of the calculation period, and the remaining initial stub
    period is treated in accordance with the usual rule.When counting backwards for this purpose, if
    the last day of the relevant period is 28th February, the full year should be counted back to the
    previous 28th February unless 29th February exists, in which case, 29th February should be used.

  【 正規 Actual/Actual (AFB) の ロジック 】
Public Function AFB_YEARFRAC(ByVal StartDate As DateByVal EndDate As DateAs Double
Dim wkDate As Date          '応当日
Dim dblFracDays As Double
Dim intYears As Integer
Dim intDays As Integer
Dim LeapDay As Date

    If (Format(EndDate + 1, "mmdd") = "0301") Then
        '終了日=2月末日
        If (Format(StartDate + 1, "mmdd") = "0301") Then
            '2月末日〜2月末日
            intYears = Year(EndDate) - Year(StartDate)
            intDays = 0
        ElseIf (Format((StartDate), "mmdd") <= Format(EndDate, "mmdd")) Then
            ' 1/15〜2月末日 など
            If (Year(StartDate) = Year(EndDate)) Then
                '同一年内
                wkDate = EndDate
                intYears = 0
                intDays = (wkDate - StartDate)
            Else
                '終了日(2月末日)→開始年の2月末日でN年
                wkDate = DateSerial(Year(StartDate), 3, 1) - 1
                intYears = Year(EndDate) - Year(wkDate)
                intDays = wkDate - StartDate
            End If
        Else
            ' 3/15〜2月末日 など
            '終了日(2月末日)→開始日の翌年の2月末日でN年
            wkDate = DateSerial(Year(StartDate) + 1, 3, 1) - 1
            intYears = Year(EndDate) - Year(wkDate)
            intDays = wkDate - StartDate
        End If
    Else
        '終了日≠2月末日
        If (Format((StartDate), "mmdd") <= Format(EndDate, "mmdd")) Then
            If (Year(StartDate) = Year(EndDate)) Then
                '同一年内
                wkDate = EndDate
                intYears = 0
                intDays = wkDate - StartDate
            Else
                '終了日→開始年の応当日でN年
                wkDate = DateSerial(Year(StartDate), Month(EndDate), Day(EndDate))
                intYears = Year(EndDate) - Year(wkDate)
                intDays = wkDate - StartDate
            End If
        Else
            '終了日→開始年の翌年の応当日でN年
            wkDate = DateSerial(Year(StartDate) + 1, Month(EndDate), Day(EndDate))
            intYears = Year(EndDate) - Year(wkDate)
            intDays = wkDate - StartDate
        End If
    End If

    If (Month(DateSerial(Year(StartDate), 2, 29)) = 2) Then
        LeapDay = DateSerial(Year(StartDate), 2, 29)
    ElseIf (Month(DateSerial(Year(wkDate), 2, 29)) = 2) Then
        LeapDay = DateSerial(Year(wkDate), 2, 29)
    Else
        LeapDay = 0
    End If

    If (LeapDay = 0) Then
        dblFracDays = 365#
    Else
        If (StartDate < LeapDay) And (LeapDay <= wkDate) Then
            dblFracDays = 366#
        Else
            dblFracDays = 365#
        End If
    End If

    AFB_YEARFRAC = intYears + (CDbl(intDays) / dblFracDays)
End Function


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[ この場所へのリンク ]
 『Google SpreadSheet  YEARFRAC 基準:1 (Actual/Actual)』 について
  Google SpreadSheet の YEARFRAC 関数についても調査したところ、Google SpreadSheet では [基準:1,Act/Act]
  の計算方法は ISDA 方式で行なっている事が判明しました。
      ISDA 方式 : 期間を 1年間(1月1日〜12月31日)ごとに区切り、各々の年について、
                      その部分での期間日数を 「その年の年間日数(365 or 366)」で割り、
                      その結果を合計する。

  【 Google SpreadSheet  YEARFRAC (基準:1 Actual/Actual ) の 実装ロジック 】  [ シミュレーション ]
Public Function Google_YEARFRAC(ByVal StartDate As DateByVal EndDate As DateAs Double
Dim i As Integer
Dim dblFrac As Double
Dim SDate As Date
Dim EDate As Date

    dblFrac = 0
    For i = Year(StartDate) To Year(EndDate)
        SDate = DateSerial(i, 1, 1)
        EDate = DateSerial(i + 1, 1, 1)
        If (SDate < StartDate) Then
            SDate = StartDate
        End If
        If (EDate > EndDate) Then
            EDate = EndDate
        End If

        If (Month(DateSerial(i, 2, 29)) = 2) Then
            dblFrac = dblFrac + (CDbl(EDate - SDate) / 366#)
        Else
            dblFrac = dblFrac + (CDbl(EDate - SDate) / 365#)
        End If
    Next i
    Google_YEARFRAC = dblFrac
End Function


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[ この場所へのリンク ] 
 ktDATEDIF , DATEDIF(Excel2003以前) , DATEDIF(Excel2007 SP無,SP1 ) での算出結果の違い

  以前 から有名だった
      1998/10/31〜2000/3/21  MD: 19 (誤 Excel2003) ,  21 (正 Excel2007)
      1998/10/31〜2000/3/1   MD: -1 (誤 Excel2003) ,   1 (正 Excel2007)
  という ケースの計算が直った反面で、
      2008/2/6〜2008/3/3      MD: 26 (正 Excel2003) ,  28 (誤 Excel2007)
      2006/12/31〜2007/2/1   MD: 1 (正 Excel2003) ,   -2 (誤 Excel2007)
  といったケースでは、2003以前では正しかったのが、2007では間違った結果を返すようになっています。
  2007での修正によって却って誤計算のパターンが増えたように思えます。
  YD にいたっては、30〜300日も少なく計算されるケースも出て来ました。
      2001/3/1〜2004/1/31    YD : 336 (正 Excel2002) , 336 (正 Excel2007)
      2001/3/1〜2004/2/1      YD : 337 (正 Excel2002) , 305 (誤 Excel2007)
      2001/12/1〜2003/11/1   YD : 335 (正 Excel2002) ,  21 (誤 Excel2007)

( Y , YM , MD )            ktDATEDIF        Excel2003        Excel2007
2008/ 3/31 - 2009/ 2/28    0年11ヶ月 0日    0年10ヶ月28日    0年10ヶ月25日
2008/ 4/30 - 2009/10/31    1年 6ヶ月 0日    1年 6ヶ月 1日    1年 6ヶ月 1日
2008/ 4/30 - 2009/ 3/ 1    0年10ヶ月 1日    0年10ヶ月-1日    0年10ヶ月 2日
2008/ 5/31 - 2009/ 3/ 1    0年 9ヶ月 1日    0年 9ヶ月-2日    0年 9ヶ月 1日
2008/ 2/29 - 2009/ 2/28    1年 0ヶ月 0日    0年11ヶ月30日    0年11ヶ月27日
2008/ 2/ 6 - 2012/ 3/ 3    4年 0ヶ月26日    4年 0ヶ月26日    4年 0ヶ月28日
2006/12/31 - 2007/ 2/ 1    0年 1ヶ月 1日    0年 1ヶ月 1日    0年 1ヶ月-2

( YD )                    ktDATEDIF   Excel2003   Excel2007
2001/ 1/1 - 2004/ 2/28     58          58          58    (平年→閏年)  59の重複
2001/ 1/1 - 2004/ 2/29     59         [59]        [59]
2001/ 1/1 - 2004/ 3/ 1     60         [59]        [59]
2001/ 1/1 - 2004/ 3/ 2     61          60          60

2000/ 1/1 - 2005/ 2/28     58          58          58    (閏年→平年)  59の抜け
2000/ 1/1 - 2005/ 3/ 1     59          60          60
2000/ 1/1 - 2005/ 3/ 2     60          61          61

2001/ 3/1 - 2004/ 1/31    336         336         336    Excel2007 の異様な計算結果
2001/ 3/1 - 2004/ 2/ 1    337         337         305
2001/12/1 - 2003/11/ 1    335         335          21

2001/ 5/1 - 2003/ 1/ 1    245         245         245  
2001/ 5/1 - 2003/ 1/31    275         275         275  
2001/ 5/1 - 2003/ 2/ 1    276         276         244  
2001/ 5/1 - 2003/ 2/28    303         303         271  
2001/ 5/1 - 2003/ 3/ 1    304         304         243  
2001/ 5/1 - 2003/ 3/31    334         334         273  
2001/ 5/1 - 2003/ 4/ 1    335         335         242  
2001/ 5/1 - 2003/ 4/30    364         364         271  
2001/ 5/1 - 2003/ 5/ 1      0           0           0  
2001/ 5/1 - 2003/ 5/31     30          30          30  
2001/ 5/1 - 2003/ 6/ 1     31          31          31  
2001/ 5/1 - 2003/ 6/30     60          60          60  
2001/ 5/1 - 2003/ 7/ 1     61          61          61  
2001/ 5/1 - 2003/ 7/31     91          91          91  
2001/ 5/1 - 2003/12/ 1    214         214         214  
2001/ 5/1 - 2003/12/31    244         244         244  

Excel2007(YD) の異様な計算結果 (その2)


[ この場所へのリンク ]
  2008/3/25: MS はExcel2007 の DATEDIF に不具合があると正式に認めました。
                (従来は非公開関数を理由に不具合の有無自体への言及を避けていました)

  Excel2007(SP無/SP1)が行なっている実装ロジックを解明しました。 ( 2008/12/13 追記 )
     Excel2007(SP無/SP1) DATEDIF(YD) ロジック    ( マクロブック : XL2007_DATEDIF_YD.xlsm )
           〃                    〃   (MD)      〃     ( マクロブック : XL2007_DATEDIF_MD.xlsm )

  2009/7/ 9: Excel2007 SP2 で再検証を行なったところ、[ SP2 & その後のアップデート ] で修正が行なわれた
                ようです( SP2 [ 12.0.6425.1000 ] , KB 969682 (KB 968863, KB 969952 [ 12.0.6504.5001 ] )。
                MD の計算は [2003 以前] に戻りました。YD の計算は 9割方 [2003 以前] に戻りました。
                YD の戻らなかった部分( 開始日が2日以降 & 終了日が閏年の1月 ) が KB 969682 で
                再度修正 [12.0.6504.5001] されますが、それでも 直っていません
                        例: 2000/5/5〜2004/1/4 → 357日 ( 正解: 244日   [ 算出値 = 正解 + 113 ]  )
                        不具合発生条件: 終了年月日が 『 年= 閏年 , 月= 1月 , 日< 開始年月日の[日] 』
                尚、SP1 でも、最新の修正プログラム( KB968863, KB969952 )まで更新を行なえば、この修正が掛ります。

  2010/2/26 : Excel 2010β で確認したところ、上記 Excel2007(SP2 + KB 969682) [12.0.6504.5001] と同じです。

  2010/5/28 : Excel2010(RTM) で確認したところ、残っていた[YD]の一部不一致も修正され Excel2003以前に完全
                  に戻りました (バグが完全に無くなったという意味ではありません。Excel2003以前と同じバグはあります)。

[ この場所へのリンク ]
  2011/4/ 9 : Excel2007SP2 の修正により
                       『[YD の一部] を残して2003以前に戻った ( MD は完全に元に戻った ) 』
                  とアナウンス(2009/7/9)していましたが、[MD の一部] にも修正漏れが残っている事が判りました。
                        例: 2011/3/20 - 2012/1/19 → 143日 ( 正解: 30日   [ 算出値 = 正解 + 113 ]  )
                        不具合発生条件: 終了年月日が 『 年= 閏年 , 月= 1月 , 日< 開始年月日の[日] 』
                  尚、Excel2010 では、この部分も修正されており 2003以前と同じ結果が得られます。

  2011/11/10 : Excel2007SP3 の修正でも、上記 「 MD の修正漏れ」 は修正されていません。
  2012/2/20 : Excel2007(SP3) の DATEDIF 不具合に関するサポート技術情報が公開されました ( KB2678753 )


[ この場所へのリンク ] 
  2014/2/12 : MD および YD ともに 下記条件において [ 算出値 = 正解 + 113 ] となる不具合が残っていましたが、
                    KB2827324 ( 2013/10/8 リリース ) によって、この [ + 113 ] が [ + 164 ] に変わっている
                事が判明しました。(MD/YD とも。発生条件は変わりなし)

                            不具合発生条件: 終了年月日が 『 年= 閏年 , 月= 1月 , 日< 開始年月日の[日] 』 
                    本件の詳細と回避策などについては下記の赤枠内の解説を参照して下さい。


  2014/3/7 : 上記、不具合の更なる デグレード に対して、2012/2/20 にリリース されていた MS-KB KB2678753
                    が 下記 赤枠内の回避策を基にして 解説が修正されました。



[ この場所へのリンク ] 
  上記の不具合( 2014/2/12 ) については、Microsoft テクニカルサポートに調査をして貰ったところ、修正アップデート
  KB2827324 の適用によって発現している事が判明しました。なお、 この妙な誤差は過去の修正アップデートで以下
  のような経歴を持っています。
        ・ SP2 (12.0.6425.1000) ⇒ [ + 164 ]
        ・ SP2 + KB969682 (12.0.6504.5001) ⇒ [ + 113 ]
        ・ SP3 (SP2 + KB969682 が集約 , 12.0.6611.1000) ⇒ [ + 113 ]
        ・ SP3 + KB2760583 (12.0.6679.5000) ⇒ [ + 113 ]  2013/9/10 リリース
        ・ SP3 + KB2827324 (12.0.6683.5002) ⇒ [ + 164 ]  2013/10/8 リリース

  私的な推測ですが、
        この変更は DATEDIF に対しての修正(Issue)ではなく、他の障害への修正が 『DATEDIF も利用
        している日付関連の共通関数』 に及び、それに伴って DATEDIF の算出結果にも影響が出た
  のではないかと想像しています。そうだとすると、今後もアップデートの度に繰り返し値が変わる可能性があります。

  その点も考慮すると、KB2678753 (2014/3/7 本枠内の回避策で修正されています) で紹介している補正式を
        単純に 式内の [ 113 ] を [ 164 ] へ修正する
  という 解決策 では不完全です。

  あまり良い方法ともいえませんし、更に長ったらしいものになりますが、
        (1) そのPC上の Excel2007 で現われる誤差を求める
        (2) その求めた誤差を補正式内の [ 113 ] の位置に当て嵌める
  とすれば、アップデート状況に依らず対応できます。 2011/1/2 〜 2012/1/1 の期間を計算した場合、
        [ YD ] 正解 = 364 , [ MD ] 正解 = 30
  ですので、
        [ YD ]    DATEDIF(DATE(2011,1,2), DATE(2012,1,1), "YD") - 364
        [ MD ]    DATEDIF(DATE(2011,1,2), DATE(2012,1,1), "MD") - 30
  という式で 113 や 164 といった補正値を得る事ができます。

  -- YD --
  ( 2012/2/20 時点での KB2678753 の回避式 )
  =IF(AND(INFO("release")="12.0",MONTH(DATE(YEAR(B1),2,29))=2,MONTH(B1)=1,DAY(A1)>DAY(B1)),
        DATEDIF(A1,B1,"YD") - 113,
        DATEDIF(A1,B1,"YD"))
        ↓
  =IF(AND(INFO("release")="12.0",MONTH(DATE(YEAR(B1),2,29))=2,MONTH(B1)=1,DAY(A1)>DAY(B1)),
        DATEDIF(A1,B1,"YD") - (DATEDIF(DATE(2011,1,2), DATE(2012,1,1), "YD") - 364),
        DATEDIF(A1,B1,"YD"))

  -- MD --
  ( 2012/2/20 時点での KB2678753 の回避式 )
  =IF(AND(INFO("release")="12.0",MONTH(DATE(YEAR(B1),2,29))=2,MONTH(B1)=1,DAY(A1)>DAY(B1)),
        DATEDIF(A1,B1,"MD") - 113,
        DATEDIF(A1,B1,"MD"))
        ↓
  =IF(AND(INFO("release")="12.0",MONTH(DATE(YEAR(B1),2,29))=2,MONTH(B1)=1,DAY(A1)>DAY(B1)),
        DATEDIF(A1,B1,"MD") - (DATEDIF(DATE(2011,1,2), DATE(2012,1,1), "MD") - 30),
        DATEDIF(A1,B1,"MD"))



  尚、この方法を採る場合、Excel2007 以外では誤差値が ゼロ になるので、Excel2007 である判定式
        『 INFO("release")="12.0" 』
  が無くても支障ありません。支障ありませんが、こんな意味不明な式を使う説明(Excel2007限定)にもなりますので
  残しておいた方が良いと思います。

  また、後々、理由を知らない人が [ 2011/1/2 , 2012/1/1 ] の日付を弄ってしまう危険性もあります。そこで、この
  危険を避ける為に 補正値の式の部分を【名前定義】で登録 しておきます。
      -- 名前定義の方法 --
      [ 数式タブ/名前の定義 ]
      名前 : Excel2007YD補正値
          範囲 : ブック
          コメント : Excel2007/DATEDIF(YD)のバグによる誤差を補正する
          参照範囲 : =DATEDIF(DATE(2011,1,2), DATE(2012,1,1), "YD") - 364
      名前 : Excel2007MD補正値
          範囲 : ブック
          コメント : Excel2007/DATEDIF(MD)のバグによる誤差を補正する
          参照範囲 : =DATEDIF(DATE(2011,1,2), DATE(2012,1,1), "MD") - 30


  そうすると、下記式になります。
  -- YD --
  =IF(AND(INFO("release")="12.0",MONTH(DATE(YEAR(B1),2,29))=2,MONTH(B1)=1,DAY(A1)>DAY(B1)),
        DATEDIF(A1,B1,"YD") - Excel2007YD補正値,
        DATEDIF(A1,B1,"YD"))

  -- MD --
  =IF(AND(INFO("release")="12.0",MONTH(DATE(YEAR(B1),2,29))=2,MONTH(B1)=1,DAY(A1)>DAY(B1)),
        DATEDIF(A1,B1,"MD") - Excel2007MD補正値,
        DATEDIF(A1,B1,"MD"))




 Home   Back Page   Next Page

ロゴ(ゴールド)   ロゴ(ゴールド)

角田 桂一 Mail:addinbox@h4.dion.ne.jp CopyRight(C) 2001 Allrights Reserved.