1
votes

I had an issue the last day in our report tool, which is very strange. There are a bunch of entries about DateTime, diff and timezones on stackoverflow. But I haven't found an explanation why this is happening.

Specs

  • PHP 7.4.13
  • System timezone CET +0100 -> date +"%Z %z"

Issue

To bulk create reports, I need the interval in days, months and years between two dates. For this purpose I'm using DateTime->diff().

But the result is different, if the system timezone is different:

date_default_timezone_set('UTC');
$dateA1 = new \DateTime('2020-12-01 00:00:00');
$dateA2 = new \DateTime('2020-12-31 23:59:59');
$diffA = $dateA1->diff($dateA2);
print($diffA->format("Year: %Y Month: %M Day: %D"). PHP_EOL); 
// Outputs: Year: 00 Month: 00 Day: 30
date_default_timezone_set('CET');
$dateA1 = new \DateTime('2020-12-01 00:00:00');
$dateA2 = new \DateTime('2020-12-31 23:59:59');
$diffA = $dateA1->diff($dateA2);
print($diffA->format("Year: %Y Month: %M Day: %D"). PHP_EOL); 
// Outputs: Year: 00 Month: 01 Day: 00

Why? I'm comparing two dates of the same timezone. If I calculate the difference between a dateA and dateB in the same timezone I expect the same result if the two dates are in another timezone. I feel like I'm missing something?

If I check another date 2021-01-01 00:00:00 & 2021-01-31 23:59:59, the issue gets weirder (See full example).

Full Example

# UTC
date_default_timezone_set('UTC');

$dateA1 = new \DateTime('2020-12-01 00:00:00'); // Tue, 01 Dec 2020 00:00:00 +0000
$dateA2 = new \DateTime('2020-12-31 23:59:59'); // Thu, 31 Dec 2020 23:59:59 +0000
$diffA = $dateA1->diff($dateA2);
print($diffA->format("Year: %Y Month: %M Day: %D"). PHP_EOL); // Outputs: Year: 00 Month: 00 Day: 30

$dateB1 = new \DateTime('2021-01-01 00:00:00'); // Fri, 01 Jan 2021 00:00:00 +0000
$dateB2 = new \DateTime('2021-01-31 23:59:59'); // Sun, 31 Jan 2021 23:59:59 +0000
$diffB = $dateB1->diff($dateB2);
print($diffB->format("Year: %Y Month: %M Day: %D"). PHP_EOL); // Outputs: Year: 00 Month: 00 Day: 30

# CET
date_default_timezone_set('CET');

$dateC1 = new \DateTime('2020-12-01 00:00:00'); // Tue, 01 Dec 2020 00:00:00 +0100
$dateC2 = new \DateTime('2020-12-31 23:59:59'); // Thu, 31 Dec 2020 23:59:59 +0100
$diffC = $dateC1->diff($dateC2);
print($diffC->format("Year: %Y Month: %M Day: %D"). PHP_EOL); // Outputs: Year: 00 Month: 01 Day: 00 <-- Why?

$dateD1 = new \DateTime('2021-01-01 00:00:00'); // Fri, 01 Jan 2021 00:00:00 +0100
$dateD2 = new \DateTime('2021-01-31 23:59:59'); // Sun, 31 Jan 2021 23:59:59 +0100
$diffD = $dateD1->diff($dateD2);
print($diffD->format("Year: %Y Month: %M Day: %D"). PHP_EOL); // Outputs: Year: 00 Month: 00 Day: 30

Thanks for your help & best regards Roman

1
I haven't analysed your code but PHP was known issues with time diff code across DST boundaries (you should be able to find open tickets in PHP bug tracker). Switching to UTC before doing the maths is probably a must. - Álvaro González
If you look in the comment section of php.net, a lot of weird behaviours like yours are reported there php.net/manual/de/datetime.diff.php - Alexander Dobernig
This smells like a bug. If nothing else, the docs say "the return value more specifically represents the interval to apply to the original object ($this or $originObject) to arrive at the $targetObject" -- however, if you add the diff in the above code to the starting date, you do not get the end date. - Jon

1 Answers

0
votes

This seems to be a bug for a long time .. or it has reappeared.

on php.net you find the following comment:

csongor at halmai dot hu 4 years ago Be careful, the behaviour depends on the time zones in a weird way.

<?php
    function printDiff($tz) {
        $d1 = new DateTime("2015-06-01", new DateTimeZone($tz));
        $d2 = new DateTime("2015-07-01", new DateTimeZone($tz));
        $diff = $d1->diff($d2);
        print($diff->format("Year: %Y Month: %M Day: %D"). PHP_EOL);
    }
    printDiff("UTC");
    printDiff("Australia/Melbourne");
?>

The result is different:

Year: 00 Month: 01 Day: 00
Year: 00 Month: 00 Day: 30