Yesterday I observed a weird DateInterval
bug where adding 5 weeks would always equal 35 days but the difference in months and days would not be correct.
After some digging I figured out it was related to timezones and only affected timezones ranging from Europe/London
eastward all the way to Pacific/Auckland
, however as soon as I used Pacific/Honolulu
the issue was gone.
All the tests in tests/Timezones/America
and tests/Timezones/Atlantic
pass. Another weird thing is that Europe/London
passes all tests where it does not adhere to daylight savings time (UTC+01:00
).
From this I draw a conclusion that all timezones that are greater than UTC+00:00
fail because of this additional time.
My work colleague found out that this feature worked correctly in PHP 5.3.0, 5.3.1 and 5.3.2, however in 5.3.3 we can already observe this bug in action. See for yourself.
<?php
date_default_timezone_set('UTC');
$date1 = DateTime::createFromFormat('Y-m-d H:i:s', '2019-04-01 00:00:00'); // 2019-04-01 00:00:00.0 UTC (+00:00)
$date2 = clone $date1;
$date2->modify('+5 week'); // 2019-05-06 00:00:00.0 UTC (+00:00)
$differenceDateInterval = $date1->diff($date2); // interval: + 1m 5d; days: 35
print_r($differenceDateInterval);
This prints
DateInterval Object
(
[y] => 0
[m] => 1
[d] => 5 <-- this is correct
[h] => 0
[i] => 0
[s] => 0
[f] => 0
[weekday] => 0
[weekday_behavior] => 0
[first_last_day_of] => 0
[invert] => 0
[days] => 35 <--- note the amount of 35 days
[special_type] => 0
[special_amount] => 0
[have_weekday_relative] => 0
[have_special_relative] => 0
)
<?php
date_default_timezone_set('Europe/London');
$date3 = DateTime::createFromFormat('Y-m-d H:i:s', '2019-04-01 00:00:00'); // 2019-04-01 00:00:00.0 Europe/London (+01:00)
$date4 = clone $date3;
$date4->modify('+5 week'); // 2019-05-06 00:00:00.0 Europe/London (+01:00)
$differenceDateInterval2 = $date3->diff($date4); // interval: + 1m 4d; days 35
This prints
DateInterval Object
(
[y] => 0
[m] => 1
[d] => 4 <-- this is wrong and should be 5
[h] => 0
[i] => 0
[s] => 0
[f] => 0
[weekday] => 0
[weekday_behavior] => 0
[first_last_day_of] => 0
[invert] => 0
[days] => 35 <--- this is still the correct amount of 35 days
[special_type] => 0
[special_amount] => 0
[have_weekday_relative] => 0
[have_special_relative] => 0
)
Because when we add the DateInterval
with the Europe/London
timezone to a DateTime
that also has Europe/London
the addition is wrong and therefore unreliable.
<?php
date_default_timezone_set('Europe/London');
$date5 = DateTime::createFromFormat('Y-m-d H:i:s', '2019-04-01 00:00:00'); // 2019-04-01 00:00:00.0 Europe/London (+01:00)
$date6 = clone $date5;
$date6->modify('+5 week'); // 2019-05-06 00:00:00.0 Europe/London (+01:00)
$differenceDateInterval3 = $date5->diff($date6); // interval: + 1m 4d; days 35
$date7 = clone $date5;
$date7->add($differenceDateInterval3); // 2019-05-05 00:00:00.0 Europe/London (+01:00)
print_r($differenceDateInterval3);
print_r($date7);
This prints
DateInterval Object
(
[y] => 0
[m] => 1
[d] => 4 <-- this is wrong and should be 5
[h] => 0
[i] => 0
[s] => 0
[f] => 0
[weekday] => 0
[weekday_behavior] => 0
[first_last_day_of] => 0
[invert] => 0
[days] => 35 <--- this is still the correct amount of 35 days
[special_type] => 0
[special_amount] => 0
[have_weekday_relative] => 0
[have_special_relative] => 0
)
and
DateTime Object
(
[date] => 2019-05-05 00:00:00.000000
[timezone_type] => 3
[timezone] => Europe/London
)
Even though it SHOULD be
DateTime Object
(
[date] => 2019-05-06 00:00:00.000000
[timezone_type] => 3
[timezone] => Europe/London
)
Check the tests in the tests
directory of this repo and run them with phpunit
.
- I found that the DateInterval tests in php-src use
date_default_timezone_set('UTC')
. Maybe that's why nobody from the PHP core dev team never spotted this bug. - It is probably related to this bug from 2010 (that still has not been fixed 😱).