strtotimeでの日付計算:"+1 month"の謎に迫る!
PHPのstrtotime関数を使った日付計算について、ちょっとした落とし穴があることを知っていますか?この記事では、"+1 month" などの「相対日付」を指定した場合の挙動について注意が必要なポイントをご紹介します。
1月の1ヶ月後は3月!?
たとえば、1月30日の1ヶ月後は何月何日かを考えてみましょう。strtotimeで書くと以下のようになります:
echo date("Y/m/d", strtotime("+1 month", strtotime("2023/01/30")));
その結果は、"2023/03/02"となります。1月の1ヶ月後が3月となるため、最初は驚くかもしれません。ネット上では、PHPのバグだと言う声も多いようですが、実はこれはPHPの仕様なのです。
「1月30日の1ヶ月後は何月何日か?」という質問に答える際、あなたならどのように考えますか?
例えば仮に、あなたがスマホやSNSのない世界にいて、街で偶然古い友人に遭遇したとします。友人はその時とても急いでおり「1ヶ月後のこの時間に、ここで待ち合わせをしましょう」と言って去っていきました。あなたは何月何日にその場所に行きますか?もし間違えればもう二度と友人に会うことは出来ない状況です。
もし遭遇した日が1月28日ならなんの問題もありません。2月28日にその場所に行くでしょう。
しかし、その日が1月30日なら問題です。2月に30日はないからです。
1月30日を「1月の末日の1日前」と解釈するなら、あなたはおそらく2月27日に行くでしょう(簡単のため、この年は閏年ではないとしましょう)。しかし「1月28日の1ヶ月後」より、「1月30日の1ヶ月後」の方が前でいいのか?と悩むことでしょう。
つまり、「1月30日の1ヶ月後」に正解があるわけではありません。しかし、プログラムの世界では正解が必要です。
結局、PHPにとって正解はこうです。1月を基準にする場合、その1ヶ月後は、31日を足した日とします。2ヶ月後は、31日と28日を足した日(つまり59日を足した日)とします。そのため、1月30日の1ヶ月後は3月2日となるのです。
「-1 month」の場合については、少し複雑な説明が必要なので、また別の機会に詳しく説明しましょう。
ではどう書くのが良いのか?
ここからは私なりの見解ですが、結局PHPでは基準となる日を必ず1日(ついたち)にしてから「+N month」するのが良さそうです。ご存じの通り、任意の日付からその月の1日を求めるには"first day of this month"が使えます。基準となる日が1日ならなんの問題も起きません。
echo date("Y/m/d", strtotime("+1 month", strtotime("2023/01/01")));
// 出力は "2023/02/01"
echo date("Y/m/d", strtotime("+1 month", strtotime("2023/02/01")));
// 出力は "2023/03/01"
echo date("Y/m/d", strtotime("+2 month", strtotime("2023/01/01")));
// 出力は "2023/03/01"
echo date("Y/m/d", strtotime("-1 month", strtotime("2023/01/01")));
// 出力は "2022/12/01"
経理関連のシステムなら「翌月の末日」「翌々月の末日」などが必要となる場合も多いでしょう。その場合は以下のように書きます。
// 翌月の末日
echo date("Y/m/d", strtotime("first day of this month, + 1 month, last day of this month", time()));
// 翌々月の末日
echo date("Y/m/d", strtotime("first day of this month, + 2 month, last day of this month", time()));
このようにカンマで区切ることで、相対日付を複数指定出来ます。
上記では相対日付を3個結合しています。
- その月の1日を求める
- 翌月や翌々月の1日を求める
- その月の末日を求める
まとめ
PHPのstrtotime関数を使う際には、日付計算の仕組みを理解しておくことが重要です。日付の操作は簡単に見えますが、細かい仕様に気をつけないと思わぬ結果になることがあります。気をつけて、正確な日付計算を行いましょう。
Discussion