'PHP Date Diff Behaving Differently in PHP 8 (from 7) with Cloned Date

I have a WordPress plugin and we are upgrading our hosting from PHP 7 to 8. We have function which creates a DateTime object from a serialised date string in the session and compares it to now using the DateTime diff function. In PHP 7 this works (gives e.g. 1 second). In PHP 8 it gives us minus one year plus 11 months, 29 days, 23 hours, zero(!) minutes and 1 second!

I am running the following script using the PHP CLI.

<?php
$d = new DateTime();
echo '$d->format("r"):   ' . $d->format('r') . "\n";

sleep(1);

$n = new DateTime( $d->format('r') );

$diff_d = $d->diff( new DateTime() );
$diff_n = $n->diff( new DateTime() );

echo "\n";
echo 'print_r($diff_d)' . "\n";
print_r($diff_d);
echo "\n";
echo 'print_r($diff_n)' . "\n";
print_r($diff_n);
echo "\n";
echo 'd' . "\n";
print_r($d);
echo "\n";
echo 'n' . "\n";
print_r($n);

I can see how to fix the particular instance of the problem (we should probably be putting a Unix Timestamp into the session, not a date string anyway). But I'm worried I can't rely on the date diff function anymore as it's used other places - or is it creating a DateTime object from a string which is even more worrying.

Output on PHP 7

The script above is in foo.php. Running on a CentOS 7 server. First I show PHP version then run the script. This output looks correct to me.

$ php --version
PHP 7.3.33 (cli) (built: Nov 16 2021 11:18:28) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.33, Copyright (c) 1998-2018 Zend Technologies
$ php -f foo.php
$d->format("r"):   Tue, 24 May 2022 17:35:16 +0300

print_r($diff_d)
DateInterval Object
(
    [y] => 0
    [m] => 0
    [d] => 0
    [h] => 0
    [i] => 0
    [s] => 1
    [f] => 0.000213
    [weekday] => 0
    [weekday_behavior] => 0
    [first_last_day_of] => 0
    [invert] => 0
    [days] => 0
    [special_type] => 0
    [special_amount] => 0
    [have_weekday_relative] => 0
    [have_special_relative] => 0
)

print_r($diff_n)
DateInterval Object
(
    [y] => 0
    [m] => 0
    [d] => 0
    [h] => 0
    [i] => 0
    [s] => 1
    [f] => 0.41655
    [weekday] => 0
    [weekday_behavior] => 0
    [first_last_day_of] => 0
    [invert] => 0
    [days] => 0
    [special_type] => 0
    [special_amount] => 0
    [have_weekday_relative] => 0
    [have_special_relative] => 0
)

d
DateTime Object
(
    [date] => 2022-05-24 17:35:16.416318
    [timezone_type] => 3
    [timezone] => Europe/Helsinki
)

n
DateTime Object
(
    [date] => 2022-05-24 17:35:16.000000
    [timezone_type] => 1
    [timezone] => +03:00
)

Output on PHP 8

The script above is in foo.php. Also running on a CentOS 7 server. First I show PHP version then run the script. This output looks very wrong to me.

$ php --version
PHP 8.1.6 (cli) (built: May 11 2022 01:14:18) (NTS gcc x86_64)
Copyright (c) The PHP Group
Zend Engine v4.1.6, Copyright (c) Zend Technologies
$ php -f foo.php
$d->format("r"):   Tue, 24 May 2022 17:35:05 +0300

print_r($diff_d)
DateInterval Object
(
    [y] => 0
    [m] => 0
    [d] => 0
    [h] => 0
    [i] => 0
    [s] => 1
    [f] => 0.001302
    [weekday] => 0
    [weekday_behavior] => 0
    [first_last_day_of] => 0
    [invert] => 0
    [days] => 0
    [special_type] => 0
    [special_amount] => 0
    [have_weekday_relative] => 0
    [have_special_relative] => 0
)

print_r($diff_n)
DateInterval Object
(
    [y] => -1
    [m] => 11
    [d] => 29
    [h] => 23
    [i] => 0
    [s] => 1
    [f] => 0.363031
    [weekday] => 0
    [weekday_behavior] => 0
    [first_last_day_of] => 0
    [invert] => 0
    [days] => 0
    [special_type] => 0
    [special_amount] => 0
    [have_weekday_relative] => 0
    [have_special_relative] => 0
)

d
DateTime Object
(
    [date] => 2022-05-24 17:35:05.361724
    [timezone_type] => 3
    [timezone] => Europe/Helsinki
)

n
DateTime Object
(
    [date] => 2022-05-24 17:35:05.000000
    [timezone_type] => 1
    [timezone] => +03:00
)

Even if it, for some reason, is going back a year and then forward then shouldn't it be 59 minutes, not zero?

It seems to be connected to the DateTime object created from the string (in that diffing the original DateTime object against now is fine). But when I dump out $d and $n the only differences are geographical timezone vs numerical offset and the lack of milliseconds - but they are representing the same instant as each other (up to milliseconds) as far as I can see.

Can someone explain why this is happening, so I know how worried about the rest of the code I should be?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source