php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #81458 Regression in PHP 8.1: Incorrect difference after timezone change
Submitted: 2021-09-18 19:56 UTC Modified: 2021-11-18 14:04 UTC
Votes:1
Avg. Score:5.0 ± 0.0
Reproduced:1 of 1 (100.0%)
Same Version:1 (100.0%)
Same OS:0 (0.0%)
From: kylekatarnls at gmail dot com Assigned: derick (profile)
Status: Closed Package: Date/time related
PHP Version: 8.1Git-2021-09-18 (Git) OS:
Private report: No CVE-ID: None
 [2021-09-18 19:56 UTC] kylekatarnls at gmail dot com
Description:
------------
In PHP 8.0 diff()->days returned the total number of days between dates even if one of them were in UTC and not the other

In PHP 8.1 (since beta3 I think) ->d is still OK but ->days is now 0

Test script:
---------------
<?php

$first = (new DateTime('2018-07-01 00:00:00.000000 America/Toronto'))
    ->setTimezone(new DateTimeZone('UTC'));
$second = new DateTime('2018-07-02 00:00:00.000000 America/Toronto');

var_dump($first->diff($second)->days);
var_dump($first->diff($second)->d);


Expected result:
----------------
int(1)
int(1)

Actual result:
--------------
int(0)
int(1)

Patches

Add a Patch

Pull Requests

Pull requests:

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-09-19 14:39 UTC] antonino dot spampinato86 at gmail dot com
Since php 8.1 diff uses the moment instead of the timestamp, D indicates the days not covered by the month while days the days. then day one with zero time and with positive offset in timestamp (UTC) becomes the last of the previous month. For maximum compatibility check between dates that have the same timezone otherwise apply your diff method.
Related bug #52480
 [2021-09-19 14:43 UTC] antonino dot spampinato86 at gmail dot com
Sorry correct:
days
If the DateInterval object was created by DateTime::diff(), then this is the total number of days between the start and end dates. Otherwise, days will be false.

d Number of days.
 [2021-09-19 14:55 UTC] kylekatarnls at gmail dot com
So we should get `false`, not `0`, we still have a bug then.

Moreover, we still have an integer number of days for the diff(), the timezone difference should have no impact there, as in real life you can have UTC vs. User timezone in similar comparisons, and then you will get different output depending on user timezone, and it will change from PHP 8.0 to 8.1, that's very dangerous.

For the library I maintain, then I have no choice but to convert date timezone for each date only for PHP 8.1 (as behavior is safe < 8.1), that will make the library (nesbot/carbon) slower in PHP 8.1, that's a bit sad I think.

And this is a breaking change, that should be carefully documented if you don't fix it.
 [2021-09-19 15:15 UTC] antonino dot spampinato86 at gmail dot com
Carbon or php <8.1 does not use the right code, a date must be calculated from its own moment. This speech is true only if between the same, otherwise offset first normalizes the date and then you convert to UTC or look at how many DST or ST between two dates with different offset and currently the php code does not provide this. It's arithmetic. Perhaps it would make sense to change into fatal error if calculating two dates with different time zones.

Human 2018-07-01 04:00:00 UTC to 2018-07-02 00:00:00 America/Toronto bug moment is 20 hours for diff, if converter to 2018-07-02 04:00:00 is 24 hours (maybe one day if without transitions DST or ST).
 [2021-09-19 19:33 UTC] kylekatarnls at gmail dot com
Again, I may understand the point about `1` being not the correct result, and that `false` would be the correct one.

But why `0`? It's non sense.

I know it's very tempting when facing a regression to blame users expectations or previous versions to be wrong. That looks to me like a confirmation bias.

Please triple-check this `int(0)` value in an objective way. How can it be correct?

If you "can't" calculate, then return `false` please, so we can detect it when checking an interval.

Thanks.
 [2021-09-19 22:20 UTC] antonino dot spampinato86 at gmail dot com
sorry for my english, if you use DateTime :: diff the value is a number sequence. From php 8 it incorrectly calculates the moment, i.e. the date string is the first parameter of DateTime between two dates, the result is 20 instead of 24 hours (one day). It is a bug but also php <8 which converts the date string to UTC, ie it loses or adds hours that expand per day. php <8 previously 2018-07-01 20:00:00 America / Toronto in diff was converted to 2018-07-02 00:00:00 UTC instead of using moment. Wait to hear from maintainer @dereck. i am a simple user i don't work for php :)

<?php

$first = (new DateTime('2018-07-01 00:00:00.000000 America/Toronto'))
    ->setTimezone(new DateTimeZone('UTC')); // 2018-07-01 04:00:00 UTC
$second = new DateTime('2018-07-02 00:00:00.000000 America/Toronto');

var_dump($first->diff($second)->days); //2018-07-01 04:00:00 UTC - 2018-07-02 00:00 Anerica/Toronto = 20 hours
var_dump($first->diff($second)->d);
var_dump($first->diff($second)->h);
 [2021-10-11 07:57 UTC] alec at alec dot pl
This is a bug and a regression. I hope it gets more attention before the final release.

From a user perspective it is obvious that the compared DateTime objects needs to be "converted internally" to the same timezone to do the calculations.
 [2021-10-21 10:48 UTC] cmb@php.net
The following pull request has been associated:

Patch Name: Bug #81458: Add test
On GitHub:  https://github.com/php/php-src/pull/7601
Patch:      https://github.com/php/php-src/pull/7601.patch
 [2021-10-21 10:53 UTC] cmb@php.net
See <https://3v4l.org/RiYdl>.

While there is obviously a behavioral change as of PHP 8.1.0, it
seems to me that the new behavior of ::$days is correct.  In the
given test script there is a difference of 20 hours, and that is
less than a day.  Unfortunately, the documentation is not
particularly clear whether this is to be expected, and why ::$d
has a different result.

Derick, can you please clarify?
 [2021-10-21 10:53 UTC] cmb@php.net
-Assigned To: +Assigned To: derick
 [2021-10-21 13:44 UTC] antonino dot spampinato86 at gmail dot com
https://www.php.net/manual/en/datetime.examples-arithmetic.php

diff from php 8.1 compares the dates for the moment, while previously subtracting or adding hours. The transition problem still exists, well the difference is one hour (backwards or forwards). Between these two dates what is the difference php is less accurate than the human calculation? 2010-03-01 America/Sydney to 2010-07-29 America/Sydney From php 8.1 4 months and 28 days. Never 5 months (old incorrect aritmetic).

https://3v4l.org/5KhLZ
Relative ti bug #52480

it is understandable to restore the old behavior but updating the documentation with the first note the calculation is wrong.
 [2021-10-21 15:13 UTC] antonino dot spampinato86 at gmail dot com
After explaining the difference between the new diff behavior from php 8.1 the algorithm has also changed unfortunately SetTimeZone uses the server time and not the DateTime diff object does not recognize +00: 00 but UTC for this reason forces UTC in the code.

$first = (new DateTime('2018-07-01 00:00:00.000000 America/Toronto'));
$first = (new DateTime('@' . $first->format('U')));
$first = (new DateTime($first->format('Y-m-d H:i:s'), new DateTimeZone('UTC')));
//    ->setTimezone(new DateTimeZone('UTC'));
$second = new DateTime('2018-07-02 00:00:00.000000 America/Toronto');
$result = $first->diff($second);
var_dump($first);
var_dump($second);
var_dump('UTC ' . ((new DateTime('@' . $second->format('U')))->format('Y-m-d H:i:s')));
var_dump($result->days);
var_dump($result->d);
var_dump($result);
 [2021-10-21 15:42 UTC] antonino dot spampinato86 at gmail dot com
days
If the DateInterval object was created by DateTime::diff(), then this is the total number of days between the start and end dates. Otherwise, days will be false.
Before PHP 5.4.20/5.5.4 instead of FALSE you will receive -99999 upon accessing the property.

It is a bug, however a date can be less than a day this can be handled by the user code side.
 [2021-10-21 16:58 UTC] kylekatarnls at gmail dot com
cmb@php.net > No, the difference between those 2 moments is 24 hours, not 20 hours.

No hours are added or subtracted when calling setTimezone, only the timezone change not the moment, it's a meta data that should be used only to format the date, it has no impact on the difference with an other moment.

Sorry but no, the current behavior is not correct. No way, 1 is correct (it's really 1 FULL DAY diff), and 0 is wrong.
 [2021-11-02 15:31 UTC] derick@php.net
Kyle, would you agree to the following definition of what "days" should mean in this context? It was never really defined (https://www.php.net/manual/en/class.dateinterval.php#dateinterval.props.days) in the docs:

- If the timezones are the same┬╣, use the values of year, month, and day to calculate "days"
- If the timezones are not the same, use absolute(floor(($one->unixTime - $two->unixTime) / 86400))

┬╣ A timezone is the same if both $one and $two are:
  - the same timezone type (1, 2, or 3) AND
    - if zonetype 1 (utc offset) or 2 (abbr), the UTC offsets are the same
    - if zonetype 3, the TZIDs (such as UTC / America/Toronto) are the same

In your example, the timezone in $first and $second is not the same, as $first is zonetype 3 and has TZID "UTC", and $second is zonetype 3, but has the TZID "America/Toronto". If would then calculate absolute(floor((1530504000 - 1530417600) / 86400)) === 1.
 [2021-11-02 21:00 UTC] kylekatarnls at gmail dot com
Exactly absolute(floor(($one->unixTime - $two->unixTime) / 86400)) is a good fallback to calendar diff when ambiguous. Thank you.
 [2021-11-05 12:46 UTC] derick@php.net
FWIW, I started working on this, but it's a tad more work than I expected.
 [2021-11-08 09:40 UTC] git@php.net
Automatic comment on behalf of derickr
Revision: https://github.com/php/php-src/commit/904933e9185664148ce5459f93726f54f572b6c3
Log: Fixed bug #81458: Regression: Incorrect difference after timezone change
 [2021-11-08 09:40 UTC] git@php.net
-Status: Assigned +Status: Closed
 [2021-11-17 10:36 UTC] kylekatarnls at gmail dot com
Hello derick,

FYI I now get this returning me days = 0 using master branch, while if I properly understood your specs it should be 1:

```
date_default_timezone_set('UTC');
$a = new DateTime('2018-12-01 00:00');
$b = new DateTime('2018-12-02 00:01');

var_dump($a->diff($b));
```

So it sounds now there is a regression when both are in UTC.
 [2021-11-18 14:04 UTC] derick@php.net
Should be fixed now Kyle, it had nothing to do with a regression, but with a missing "=" -> https://github.com/derickr/timelib/commit/b8885ede02dea903a42b82d1fcb99e708b76e384
 
PHP Copyright © 2001-2021 The PHP Group
All rights reserved.
Last updated: Sun Nov 28 09:03:14 2021 UTC