Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP DateTime->diff() doesn't work correctly

Tags:

I met an interesting case, related to the diff() method of DateTime class.

If I try to calculate difference between two dates in months like

$datetime1 = new \DateTime('June 2019');
$datetime2 = new \DateTime('July 2019');
$interval = $datetime1->diff($datetime2);
echo $interval->format('%m');

, as result I get 0.

Why does this happen?

Print_r's:

$datetime1:

DateTime Object ( [date] => 2019-06-01 00:00:00.000000 
[timezone_type] => 3 [timezone] => Europe/Berlin )

$datetime2:

DateTime Object ( [date] => 2019-07-01 00:00:00.000000 
[timezone_type] => 3 [timezone] => Europe/Berlin )

$interval:

DateInterval Object ( [y] => 0 [m] => 0 [d] => 30 [h] => 0 [i] => 0 [s] => 0 [f] => 0 
[weekday] => 0 [weekday_behavior] => 0 [first_last_day_of] => 0 
[invert] => 0 [days] => 30 [special_type] => 0 [special_amount] => 0
 [have_weekday_relative] => 0 [have_special_relative] => 0 )
like image 458
montie Avatar asked Jul 12 '19 12:07

montie


1 Answers

There is big inconsistency with timezone and date handing in PHP

This appears to be a bug (in so far as the datetime format is forced to a GMT* offset, according to this comment).

*(but forcing to GMT seems inconsistent with the results established by the code below)

Setting the server timezone value to any timezone does not effect this script timezone anomaly.

Below are two cases showing what happens in different time zones:


Case 1:

The following code will output a list of results for each time zone:

$tzList = DateTimeZone::listIdentifiers(DateTimeZone::ALL);

print "Current Zone:". print_r(ini_get('date.timezone'),true)."<br>\n<BR>\n";

foreach($tzList as $tzRow) {
    $tz = new DateTimeZone($tzRow);
    //$tz = null;
    $datetime1 = new \DateTime('June 2019', $tz);
    $datetime2 = new \DateTime('July 2019', $tz);
    $interval = $datetime1->diff($datetime2, false);
    echo $interval->format('%a %m') . PHP_EOL. " :: ";

    print print_r($datetime1->getTimezone(),true)."<BR>";
}

The result of this list output shows a high (~60%) rate of 0 and the rest of 1 month .

Please see here: http://sandbox.onlinephpfunctions.com/code/b18ba13deb94d112b12630a12265363fb6c7670b


Case 2:

Setting the timezone AFTER creating the object, results in a consistent answer (albeit incorrect)

$tzList = DateTimeZone::listIdentifiers(DateTimeZone::ALL);

print "Current Zone:". print_r(ini_get('date.timezone'),true)."<br>\n<BR>\n";

foreach($tzList as $tzRow) {
    //$tz = new DateTimeZone($tzRow);
    $tz = null;
    $datetime1 = new \DateTime('June 2019', $tz);
    $datetime2 = new \DateTime('July 2019', $tz);
    $datetime1->setTimezone(new DateTimeZone($tzRow));
    $datetime2->setTimezone(new DateTimeZone($tzRow));
    $interval = $datetime1->diff($datetime2, false);
    echo $interval->format('%a %m') . PHP_EOL. " :: ";

    print print_r($datetime1->getTimezone(),true)."<BR>";
}
 

This output's generated here all all 30 days out; but all 0 months difference.

See code here: http://sandbox.onlinephpfunctions.com/code/7bcc62f4e36f41df71b9cb928de75a53f233d9fd


So it's your choice if you want to use sometimes correct results or universally incorrect rbut consistent results, by setting when you establish the Timezone value in the DateTime objects.


Possible Solution:

If the server timezone is correctly set to UTC "correct" timezone (that naturally returns "1" month in Case 1, then CASE 2 above works consistently across all time zones given to the DateTime objects.

like image 129
Martin Avatar answered Sep 30 '22 19:09

Martin