2010-05-08

DateTime::add(), DateTime::sub()での月の計算

PHPのstrtotime()やDateTime::modify()で月の計算をすると余った日数が無視されずそのまま加算されてしまうので使い勝手が悪い(例えば 2010-03-31 + 1month == 2010-04-31 == 2010-04-30 + 1 day == 2010-05-01 であり、2010-03-31 - 1month == 2010-02-31 == 2010-02-28 + 3 day == 2010-03-03)んですが、DateTime::add(), DateTime::sub()だともしかしたら余った日数を無視するようになっているんじゃないかと思って試してみたところ…同じ結果でした(がっかり...)。

$date = new DateTime();
$interval = new DateInterval('P1M');
$format = 'Y-m-d';

echo 'PHP: ' . PHP_VERSION . PHP_EOL;
echo 'date: ' . $date->setDate(2010, 3, 31)->format($format) . PHP_EOL;
echo ' +1m: ' . $date->setDate(2010, 3, 31)->add($interval)->format($format) . PHP_EOL;
echo ' -1m: ' . $date->setDate(2010, 3, 31)->sub($interval)->format($format);
(結果)
PHP: 5.3.0
date: 2010-03-31
+1m: 2010-05-01
-1m: 2010-03-03

DateIntervalのもとになっているのがISO 8601のdurationなんですが、ISO 8601:2000の和訳のJIS X 0301:2002をざっと見ても時間長(duration)の計算に関する記述がないようなので、そもそも定義されていないのかもしれません。

同じようにISO 8601のdurationがもとになっているXML Schema DatatypesのdurationだとE.2 Commutativity and AssociativityTime durations are added by simply adding each of their fields, respectively, without overflow. (時間長は各フィールドの単純な加算によってそれぞれあふれずに加えられる?)となっていて余った日数を切り捨てるようになっているみたいなんですが…。

とりあえず自力で余った日数を切り捨てるしかなさそうです。