LoginSignup
0
0

More than 5 years have passed since last update.

感覚的な月の数の数え方

Last updated at Posted at 2016-12-26

これは Delphi Advent Calendar 補欠記事です。

感覚的な月の数の数え方

まずは、この図を見てください。
僕のつたない Excel 力で作り上げた図です!!

qiita1.png

ここで数字の振ってある部分を見てみます。

  1. 2016/11/01 ~ 2017/03/31 ⇒ 5か月
  2. 2018/01/15 ~ 2018/02/15 ⇒ 2か月
  3. 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
の期間を求める場合、下図のように年を跨いでいます。

qiita2.png

上図で始まりの年は

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.

qiita2.png

と、このようになり、最初に数えた月数と一致しました!

それでは!

0
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
0
0