5

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 )
Martin
  • 22,212
  • 11
  • 70
  • 132
montie
  • 494
  • 8
  • 14

3 Answers3

2

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.

Community
  • 1
  • 1
Martin
  • 22,212
  • 11
  • 70
  • 132
  • 1
    Also, another working solution, in this case, is to put concrete date in front of month and year in both of arguments. Respectively: "1 June 2019" and "31 July 2019". – montie Jul 12 '19 at 15:27
1

Can you try it by adding a timezone?

$timezones = [
    'UTC',
    'Europe/Berlin',
    'America/Belize',
    'Asia/Hong_Kong',
];

foreach ($timezones as $timezone) {
    $tz = new DateTimeZone($timezone);
    $datetime1 = new \DateTime('June 2019', $tz);
    $datetime2 = new \DateTime('July 2019', $tz);
    $interval = $datetime1->diff($datetime2);
    echo str_pad($timezone, 20, ' ').' '.$interval->format('months: %M,  day: %D,  days: %a') . PHP_EOL;
}

Result:

UTC                  months: 01,  day: 00,  days: 30
Europe/Berlin        months: 00,  day: 30,  days: 30
America/Belize       months: 01,  day: 00,  days: 30
Asia/Hong_Kong       months: 00,  day: 30,  days: 30
Bram Verstraten
  • 1,414
  • 11
  • 24
1

The problem is in your timezone.

There is a post explaining about it here.

See this example:

<?php

echo "----- Europe/Berlin -----\n";
date_default_timezone_set('Europe/Berlin'); 
$datetime1 = new \DateTime('June 2019');
$datetime2 = new \DateTime('July 2019');
print_r($datetime1);
print_r($datetime2);

$interval = $datetime1->diff($datetime2);
print_r($interval);

echo "%m = " . $interval->format('%m') . PHP_EOL;
echo "%a = " . $interval->format('%a') . PHP_EOL;
echo "%s = " . $interval->format('%s') . PHP_EOL;


echo "\n\n\n----- America/Sao_Paulo -----\n";
date_default_timezone_set('America/Sao_Paulo'); 
$datetime1 = new \DateTime('June 2019');
$datetime2 = new \DateTime('July 2019');
print_r($datetime1);
print_r($datetime2);

$interval = $datetime1->diff($datetime2);
print_r($interval);

echo "%m = " . $interval->format('%m') . PHP_EOL;
echo "%a = " . $interval->format('%a') . PHP_EOL;
echo "%s = " . $interval->format('%s') . PHP_EOL;

And the output:

$ php date_diff.php 
----- Europe/Berlin -----
DateTime Object
(
    [date] => 2019-06-01 00:00:00.000000
    [timezone_type] => 3
    [timezone] => Europe/Berlin
)
DateTime Object
(
    [date] => 2019-07-01 00:00:00.000000
    [timezone_type] => 3
    [timezone] => Europe/Berlin
)
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
)
%m = 0
%a = 30
%s = 0



----- America/Sao_Paulo -----
DateTime Object
(
    [date] => 2019-06-01 00:00:00.000000
    [timezone_type] => 3
    [timezone] => America/Sao_Paulo
)
DateTime Object
(
    [date] => 2019-07-01 00:00:00.000000
    [timezone_type] => 3
    [timezone] => America/Sao_Paulo
)
DateInterval Object
(
    [y] => 0
    [m] => 1
    [d] => 0
    [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
)
%m = 1
%a = 30
%s = 0

In my timezone $interval->format('%m'); is 1.

You can set timezone on your dates to calculate the difference between them.

$datetime1 = new \DateTime('June 2019', new DateTimeZone('UTC'));
$datetime2 = new \DateTime('July 2019', new DateTimeZone('UTC'));
$interval = $datetime1->diff($datetime2);
print_r($interval);
echo "%m = " . $interval->format('%m') . PHP_EOL;

$ php date_diff.php 
DateInterval Object
(
    [y] => 0
    [m] => 1
    [d] => 0
    [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
)
%m = 1
  • 1
    But in the real world there is no difference between month length depending on time zone... – DaszuOne Jul 12 '19 at 13:36
  • I just edited the answer by adding the reference. Look: [https://stackoverflow.com/questions/40354442/why-does-php-datetime-diff-depend-on-time-zones] – jbragagnolo Jul 12 '19 at 14:02