これは Delphi Advent Calendar 補欠記事です。
感覚的な月の数の数え方
まずは、この図を見てください。
僕のつたない Excel 力で作り上げた図です!!
ここで数字の振ってある部分を見てみます。
- 2016/11/01 ~ 2017/03/31 ⇒ 5か月
- 2018/01/15 ~ 2018/02/15 ⇒ 2か月
- 2016/11/01 ~ 2019/03/15 ⇒ 29か月
最初に期間を、矢印の右側は日月表示の月の部分を数えた値です。
最初の2番の例で言えば 1/15~2/15 という期間で1月から2月にまたがっているため2か月と数えました。
この期間が何か月なのか?を数える関数は Delphi にあるのでしょうか?
MonthsSpan 関数
System.DateUtils に MonthSpan という関数があるのですが、この関数は 30.4375 日で1か月と数える仕組みになっています。
例えば、1/15 から 2/4 は、間が 20 日しかありません。
そのため、上記の関数では1か月未満の 0.65708 という値が出てきます。
つまり上記の関数を使う場合 1/15 は 1/1 にして 2/4 は 2/28 にして計算しなくてはなりません。
function ToDateTime(const Y, M, D: Word): TDateTime;
begin
Result := EncodeDate(Y, M, D);
end;
var
S, E: TDateTime;
begin
// 1/15 ~ 2/4
S := ToDateTime(2016, 1, 15);
E := ToDateTime(2016, 2, 4);
Writeln(Round(MonthSpan(S, E))); // -> 1
// 1/1 ~ 2/28
S := ToDateTime(2016, 1, 1);
E := ToDateTime(2016, 2, 28);
Writeln(Round(MonthSpan(S, E))); // -> 2
end;
単純に見た目の月日が1月から2月にまたがってるので期間は2か月!という値を取り出す関数はありません。
作ってみた
見た目の月の期間を取得する関数を作ってみました。
uses
System.SysUtils,
System.DateUtils;
function SimpleMonthSpan(NowDate, ThenDate: TDateTime): Integer;
var
Y1, M1, D1: Word;
Y2, M2, D2: Word;
DY: Integer;
SwapDate: TDateTime;
begin
// 必ず ThenDate の方が小さくなるようにする
if (NowDate < ThenDate) then
begin
SwapDate := NowDate;
NowDate := ThenDate;
ThenDate := SwapDate;
end;
// 年月日に分解
DecodeDate(NowDate, Y1, M1, D1);
DecodeDate(ThenDate, Y2, M2, D2);
DY := Y1 - Y2;
// 年が同じ場合と年が違う場合に分けて計算
if (DY = 0) then
Result := M1 - M2 + Ord(M1 > M2)
else
Result := (DY - 1) * 12 + M1 + 13 - M2;
end;
年またぎ処理
コードの解説は最後の if 文の部分だけで充分そうなので、そこだけ。
同じとしかそうじゃないかを判断して分けています。
同じ年の場合
同じ年の方は解りやすいですね。
単純に月同士の引き算をして、同じ月でなければ、1を足しています。
Result := M1 - M2 + Ord(M1 > M2)
年をまたいでいる場合
例えば
2016/9~2017/4
の期間を求める場合、下図のように年を跨いでいます。
上図で始まりの年は
12 - 9 + 1 = 4
これを一般化すると
12 - 月a + 1
となります。
終わりの年はそのまま
月b
のそれぞれを足せば良いことになります。
結果
12 - 月a + 1 + 月b
↓
月b + 13 - 月a
となります。
終始のどちらでもない年は、そのまま12ヶ月を足すだけです。
よって else 文では下記の様になります。
Result := (DY - 1) * 12 + M1 + 13 - M2;
実際に計算してみる
※番号は最初の図の番号に対応しています
var
S, E: TDateTime;
begin
// 1番
S := ToDateTime(2016, 11, 1);
E := ToDateTime(2017, 3, 31);
Writeln(Format('Span = %d', [SimpleMonthSpan(S, E)]));
// 2番
S := ToDateTime(2018, 1, 15);
E := ToDateTime(2018, 2, 15);
Writeln(Format('Span = %d', [SimpleMonthSpan(S, E)]));
// 3番
S := ToDateTime(2016, 11, 1);
E := ToDateTime(2019, 3, 15);
Writeln(Format('Span = %d', [SimpleMonthSpan(S, E)]));
end.
と、このようになり、最初に数えた月数と一致しました!
それでは!