以下の例は、DST(夏時間) への移行や、 異なった日を持つ月に関する 日付/時刻 の算術の罠をいくつか紹介します。
例1 DateTimeImmutable::add/sub は、経過時間を含む間隔を加算する
DST (夏時間) への移行タイミングをまたいで PT24H を足すと、 (ほとんどのタイムゾーンでは) 23/25時間を足しているように見える
<?php
$dt = new DateTimeImmutable("2015-11-01 00:00:00", new DateTimeZone("America/New_York"));
echo "Start: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->add(new DateInterval("PT3H"));
echo "End: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;上の例の出力は以下となります。
Start: 2015-11-01 00:00:00 -04:00 End: 2015-11-01 02:00:00 -05:00
例2 DateTimeImmutable::modify と strtotime を使い、個々のコンポーネントの値をインクリメント/デクリメントする
DST (夏時間) の移行のタイミングをまたいで24時間を足すと、日付/時刻 文字列に正確に24時間足されることになります (開始時刻または終了時刻が移行ポイント上にある場合を除く)。
<?php
$dt = new DateTimeImmutable("2015-11-01 00:00:00", new DateTimeZone("America/New_York"));
echo "Start: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("+24 hours");
echo "End: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;上の例の出力は以下となります。
Start: 2015-11-01 00:00:00 -04:00 End: 2015-11-02 00:00:00 -05:00
例3 DateInterval::createFromDateString は、コンポーネントの値をインクリメント/デクリメントする間隔を作る
DateInterval::createFromDateString() は 日付/時刻 パーサーを使います。
作成された間隔の時刻部分は、個々のコンポーネントの値をインクリメント/デクリメントします。
一方、DateInterval::__construct() で作成した間隔の場合、
時刻部分は経過時間を表します。そのため、その時刻部分にタイムゾーンの遷移が含まれる場合、
両者は同じ結果になりません。
日付部分はどちらも同じように適用されるので、P1D と
1 day は常に同じ結果になります。
<?php
$dt = new DateTimeImmutable("2015-11-01 00:00:00", new DateTimeZone("America/New_York"));
echo "Start: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
echo "PT24H: ", $dt->add(new DateInterval("PT24H"))->format("Y-m-d H:i:s P"), PHP_EOL;
echo "24 hours: ", $dt->add(DateInterval::createFromDateString("24 hours"))->format("Y-m-d H:i:s P"), PHP_EOL;上の例の出力は以下となります。
Start: 2015-11-01 00:00:00 -04:00 PT24H: 2015-11-01 23:00:00 -05:00 24 hours: 2015-11-02 00:00:00 -05:00
例4 時刻の加減算で、日付がオーバーフロー/アンダーフローする可能性がある
1月31日 + 1ヶ月 のような計算をすると、(うるう年のときは) 3月2日、 または (通常の年は) 3月3日になります。
<?php
echo "Normal year:\n"; // February has 28 days
$dt = new DateTimeImmutable("2015-01-31 00:00:00", new DateTimeZone("America/New_York"));
echo "Start: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("+1 month");
echo "End: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
echo "Leap year:\n"; // February has 29 days
$dt = new DateTimeImmutable("2016-01-31 00:00:00", new DateTimeZone("America/New_York"));
echo "Start: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("+1 month");
echo "End: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;上の例の出力は以下となります。
Normal year: Start: 2015-01-31 00:00:00 -05:00 End: 2015-03-03 00:00:00 -05:00 Leap year: Start: 2016-01-31 00:00:00 -05:00 End: 2016-03-02 00:00:00 -05:00
次の月の最後の日を得るために(つまり、オーバーフローを避けるため)、
last day of フォーマットも利用できます。
<?php
echo "Normal year:\n"; // February has 28 days
$dt = new DateTimeImmutable("2015-01-31 00:00:00", new DateTimeZone("America/New_York"));
echo "Start: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("last day of next month");
echo "End: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
echo "Leap year:\n"; // February has 29 days
$dt = new DateTimeImmutable("2016-01-31 00:00:00", new DateTimeZone("America/New_York"));
echo "Start: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("last day of next month");
echo "End: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;上の例の出力は以下となります。
Normal year: Start: 2015-01-31 00:00:00 -05:00 End: 2015-02-28 00:00:00 -05:00 Leap year: Start: 2016-01-31 00:00:00 -05:00 End: 2016-02-29 00:00:00 -05:00