Date/Time Arithmetic
The following examples show some pitfalls of Date/Time arithmetic with regard to DST transitions and months having different numbers of days.
Examples
Date/Time Arithmetic
The following examples show some pitfalls of Date/Time arithmetic with regard to DST transitions and months having different numbers of days.
DateTimeImmutable::add/sub add intervals which cover elapsed time
Adding PT24H over a DST transition will appear to add 23/25 hours (for most timezones).
<?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;The above example will output:
Start: 2015-11-01 00:00:00 -04:00
End: 2015-11-01 02:00:00 -05:00DateTimeImmutable::modify and strtotime increment or decrement individual component values
Adding +24 hours over a DST transition will add exactly 24 hours as seen in the date/time string (unless the start or end time is on a transition point).
<?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;The above example will output:
Start: 2015-11-01 00:00:00 -04:00
End: 2015-11-02 00:00:00 -05:00DateInterval::createFromDateString creates intervals which increment or decrement component values
DateInterval::createFromDateString uses the date/time parser, and the time part of the resulting interval increments or decrements the individual component values. For an interval created with DateInterval::__construct the time part covers elapsed time instead, so the two are not interchangeable when a timezone transition falls within that time part. The date part is applied the same way by both, so P1D and 1 day always give the same result.
<?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;The above example will output:
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:00Adding or subtracting times can over- or underflow dates
Like where January 31st + 1 month will result in March 2nd (leap year) or 3rd (normal year).
<?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;The above example will output:
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:00To get the last day of the next month (i.e. to prevent the overflow), the last day of format is available.
<?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;The above example will output:
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