+n month の罠

PHP Advent Calendar 2013 - Qiita の 23 日目です。
strtotime の話をします。

+n month の罠

日時を扱うのは大抵どの言語でも難しいです。PHPでももちろんそう。

PHP で日付の足し算をするのには strtotime を使います。

echo date('Y-m-d', strtotime('2013-01-31 +1 day')) . PHP_EOL; //=> 2013-02-01

strtotime はすごく便利ですが、うかつに月の計算をすると予期しない結果が返ってきます。

echo date('Y-m-d', strtotime('2013-01-31 +1 month')) . PHP_EOL; //=> 2013-03-03
echo date('Y-m-d', strtotime('2013-02-28 +1 month')) . PHP_EOL; //=> 2013-03-28
echo date('Y-m-d', strtotime('2013-03-31 +1 month')) . PHP_EOL; //=> 2013-05-01

1月の1ヶ月後が3月、3月の1ヶ月後が5月になっています。これは一体。。。

nヶ月後っていつなのか

冷静に考えてみると、月の日数って月によって違うし、nヶ月後の日付って人間の意思なしには決められないものなんですね。
1月31日の1ヶ月後はいつなのか、30日後にしたいとき、31日後にしたいとき、2月の月末にしたいとき、いろいろあると思います。

ちゃんと +n month する

この +n month の罠にハマらないために、n月 +1 month が n+1 月となることを期待して +n month するときは月初を起点にすることを心がけましょう。

echo date('Y-m-d', strtotime('2013-01-01 +3 month')) . PHP_EOL; //=> 2013-02-01
echo date('Y-m-d', strtotime('2013-02-01 +3 month')) . PHP_EOL; //=> 2013-03-01
echo date('Y-m-d', strtotime('2013-03-01 +3 month')) . PHP_EOL; //=> 2013-04-01


なぜ、これだとうまくいくのか。PHP は +1 month するとき、現在月の日数を足し算するからです。うるう年とかも考慮されてるのでご安心を。

echo date('Y-m-d', strtotime('2013-01-31 +1 month')) . PHP_EOL; //=> 2013-03-03
echo date('Y-m-d', strtotime('2013-02-28 +1 month')) . PHP_EOL; //=> 2013-03-28
echo date('Y-m-d', strtotime('2013-03-31 +1 month')) . PHP_EOL; //=> 2013-05-01
echo date('Y-m-d', strtotime('2013-01-31 +31 day')) . PHP_EOL; //=> 2013-03-03
echo date('Y-m-d', strtotime('2013-02-28 +28 day')) . PHP_EOL; //=> 2013-03-28
echo date('Y-m-d', strtotime('2013-03-31 +31 day')) . PHP_EOL; //=> 2013-05-01


とまあこの件に関しては PHP の仕様がおかしいとはあまり思わないんですが、にしても PHP の日付・時刻周りはひどすぎですね。なんか使いやすいライブラリないのかな。