php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #52480 Incorrect difference using DateInterval
Submitted: 2010-07-29 04:49 UTC Modified: 2021-04-06 19:51 UTC
Votes:74
Avg. Score:4.4 ± 0.9
Reproduced:62 of 62 (100.0%)
Same Version:20 (32.3%)
Same OS:19 (30.6%)
From: alex dot joyce at staff dot comcen dot com dot au Assigned: derick (profile)
Status: Closed Package: Date/time related
PHP Version: 7.1 OS: irrelevant
Private report: No CVE-ID: None
 [2010-07-29 04:49 UTC] alex dot joyce at staff dot comcen dot com dot au
Description:
------------
Trying to calculate the month difference between two dates accurately.

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

  $date_start = new DateTime('2010-03-01');
  $date_end   = new DateTime('2010-07-29');

  $interval = $date_start->diff($date_end);

  print_r($interval);

Expected result:
----------------
DateInterval Object
(
    [y] => 0
    [m] => 4
    [d] => 29
    [h] => 0
    [i] => 0
    [s] => 0
    [invert] => 0
    [days] => 150
)

Actual result:
--------------
DateInterval Object
(
    [y] => 0
    [m] => 5
    [d] => 0
    [h] => 0
    [i] => 0
    [s] => 0
    [invert] => 0
    [days] => 150
)

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2010-07-29 08:27 UTC] rasmus@php.net
-Status: Open +Status: Feedback
 [2010-07-29 08:27 UTC] rasmus@php.net
Don't you mean 4 months and 28 days in the expected results?  7-3=4, 29-1=28
And that is exactly what I get on my Debian box with PHP 5.3.3:


php >   $date_start = new DateTime('2010-03-01');
php >   $date_end   = new DateTime('2010-07-29');
php > 
php >   $interval = $date_start->diff($date_end);
php > 
php >   print_r($interval);
DateInterval Object
(
    [y] => 0
    [m] => 4
    [d] => 28
    [h] => 0
    [i] => 0
    [s] => 0
    [invert] => 0
    [days] => 150
)

Are you using 5.3.3 built from our sources or are you using a Debian-packaged 
version?
 [2010-07-29 08:52 UTC] alex dot joyce at staff dot comcen dot com dot au
-Status: Feedback +Status: Open
 [2010-07-29 08:52 UTC] alex dot joyce at staff dot comcen dot com dot au
28/29, it doesn't matter. I'm only interested in the months.

I've tried:

1) FreeBSD 6, PHP 5.3.2
2) Debian 4, PHP 5.3.2
3) Debian 6, PHP 5.3.2/PHP 5.3.3

I upgraded that last one to submit this bug report, same fault.

All compiled from source.
 [2010-07-29 09:15 UTC] rasmus@php.net
-Status: Open +Status: Feedback
 [2010-07-29 09:15 UTC] rasmus@php.net
I don't see why I can't reproduce it then.  Try adding a call to 
date_default_timezone_set() to the top of your script and work in UTC to 
eliminate local timezone issues.  

  date_default_timezone_set('UTC');
  $date_start = new DateTime('2010-03-01');
  $date_end   = new DateTime('2010-07-29');
  $interval = $date_start->diff($date_end);
  print_r($interval);
 [2010-07-29 09:35 UTC] degeberg@php.net
-Status: Feedback +Status: Open
 [2010-07-29 09:35 UTC] degeberg@php.net
With the timezone set to Europe/Copenhagen, I get the same results as submitter. When set to UTC, I get the same results on Rasmus. This is on Ubuntu 10.04.

daniel@daniel-laptop:~$ php -v
PHP 5.3.4-dev (cli) (built: Jul 29 2010 09:30:24) 
Copyright (c) 1997-2010 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
 [2010-07-30 01:40 UTC] alex dot joyce at staff dot comcen dot com dot au
Changing the timezone shows a difference.

Australia/Sydney (default): 5 months
UTC: 4 months 28 days
 [2010-07-30 10:46 UTC] derick@php.net
-Status: Open +Status: Assigned -Assigned To: +Assigned To: derick
 [2010-07-30 10:46 UTC] derick@php.net
This is going to be a fun one to fix :-/
 [2011-04-12 16:37 UTC] fischer at wild-east dot de
This happens only when setting a DateTime without the time part or a time below 02:00:00. At least for the 'Europe/Berlin' timezone.
 [2011-06-13 10:51 UTC] petros at rufunka dot com
The problem lies between the last day of February and first day of March. 

At the following example:
$first = new DateTime('2011-03-01');
$second = new DateTime('2011-03-29');
$interval = $second->diff($first);

will get the wrong result.

If I set my timezone to Europe/Stockholm which is +1 GMT then if i set the $first = new DateTime(’2011-03-01 00:59:00′); I still get the wrong result. However an hour value above or equal to +1 ie $first = new DateTime(’2011-03-01 01:00:00′); will give the correct example.

So if you are GMT + 2 you need to have a value above or equal to 2011-03-01 02:00:00.

A quick fix, as mentioned above, is to set your timezone to UTC: date_default_timezone_set(‘UTC’);
and in this case you match the time with the needed in order to get correct results.

Another example with the opposite results is to set your timezone to:
date_default_timezone_set(‘America/Mexico_City’);
$first = new DateTime(’2011-02-28 22:01:00′);
$second = new DateTime(’2011-03-29 03:00:00′);
then the diff will think that you are in the same month.
 [2012-01-31 12:37 UTC] jan at jankramer dot eu
I think this bug is hasn't been fixed yet. 
Below is a reproduction on Ubuntu 11.10 with PHP 5.3.6. 

DateTime Object #1:
(
    [date] => 2011-12-01 00:00:00
    [timezone_type] => 3
    [timezone] => Europe/Amsterdam
)

DateTime Object #2:
(
    [date] => 2012-02-01 00:00:00
    [timezone_type] => 3
    [timezone] => Europe/Amsterdam
)

DateTime Object #3:
(
    [date] => 2011-12-01 12:00:00
    [timezone_type] => 3
    [timezone] => Europe/Amsterdam
)


DateTime Object #4:
(
    [date] => 2012-02-01 12:00:00
    [timezone_type] => 3
    [timezone] => Europe/Amsterdam
)

DateInterval Object #1 & #2
(
    [y] => 0
    [m] => 2
    [d] => 1
    [h] => 0
    [i] => 0
    [s] => 0
    [invert] => 0
    [days] => 62
)

DateInterval Object #3 & #4
(
    [y] => 0
    [m] => 2
    [d] => 0
    [h] => 0
    [i] => 0
    [s] => 0
    [invert] => 0
    [days] => 62
)

The difference in the 'd' attribute is very strange...
 [2012-02-25 12:20 UTC] map at wafriv dot de
I've got the same problem with PHP 5.3.8 on Windows. Like petros at rufunka dot 
com said, there is a difference of 1 hour between Europe/Berlin and UTC.

object(DateTime)[1]
  public 'date' => string '2012-02-25 13:17:52' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'Europe/Berlin' (length=13)
object(DateTime)[2]
  public 'date' => string '2010-08-07 07:01:25' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'Europe/Berlin' (length=13)
object(DateInterval)[3]
  public 'y' => int 1
  public 'm' => int 6
  public 'd' => int 18
  public 'h' => int 6
  public 'i' => int 16
  public 's' => int 27
  public 'invert' => int 1
  public 'days' => int 567
object(DateTime)[4]
  public 'date' => string '2012-02-25 12:17:52' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'UTC' (length=3)
object(DateTime)[1]
  public 'date' => string '2010-08-07 05:01:25' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'UTC' (length=3)
object(DateInterval)[2]
  public 'y' => int 1
  public 'm' => int 6
  public 'd' => int 18
  public 'h' => int 7
  public 'i' => int 16
  public 's' => int 27
  public 'invert' => int 1
  public 'days' => int 567
 [2014-01-17 13:54 UTC] jimmyraynor at gmail dot com
Ran into this issue with PHP 5.4.17 (cli) (built: Aug 25 2013 02:03:38) 
TZ: date.timezone => America/Santiago
Between dates 2013-09-08 and 2014-01-15
Using OS: Mac Os 10.9
 [2014-01-25 19:24 UTC] kulakov74 at yandex dot ru
I stumbled upon this seemingly wrong behaviour too, while studying the manual, and was all about to add a confirming comment but finally realized the reason behind it. The explanation is that the diff() method first converts its operands to UTC. In cases when the converion changes their months the number of days in the month may change too. For ex. 2010-03-01/2010-07-29 in the very 1st example, when in Europe/Copenhagen, actually converts to 2010-02-28 23:00:00/2010-07-28 23:00:00, hence the difference 5 months 0 days. The conversion explains why the number of hours in the time part matters and should be equal or greater than the timezone offset from UTC in order to avoid the effect. 

Another consequence of the UTC conversion is that the function does not respect any DST shifts (it that matters for you). Using strtotime() for the same dates and subtracting the timestamps might yield a different result (+/-1 hour) if a DST switch is within the dates period. 

I also recommend using a DateTimeZone object for DateTime instead of changing the global TZ for all date functions:

$oTZ=new DateTimeZone('Europe/Moscow');
$DT1=new DateTime('2010-03-01', $oTZ);
 [2014-11-19 10:00 UTC] ilantipov at gmail dot com
Had the same issue 
diff between 01-04-2014 and 01-07-2014
: 
DateTime Object
(
    [date] => 2014-04-01 00:00:00.000000
    [timezone_type] => 3
    [timezone] => Europe/Moscow
)
DateTime Object
(
    [date] => 2014-07-01 00:00:00.000000
    [timezone_type] => 3
    [timezone] => Europe/Moscow
)
DateInterval Object
(
    [y] => 0
    [m] => 2
    [d] => 30
    [h] => 0
    [i] => 0
    [s] => 0
    [weekday] => 0
    [weekday_behavior] => 0
    [first_last_day_of] => 0
    [invert] => 1
    [days] => 91
    [special_type] => 0
    [special_amount] => 0
    [have_weekday_relative] => 0
    [have_special_relative] => 0
)
The right interval is 3 month. Not 2 month 30 days. Both dates have same timezone. If I add date_default_timezone_set('UTC') before diff - everything works fine;
 [2015-01-12 09:43 UTC] marcel at berteler dot co dot za
I experience the same issue where the month is incorrectly calculated:

$Dob = new DateTime();
$Dob->setDate(2011, 8, 31);
$Dob->setTime(0,0,0);


$VisitDate = new DateTime();
$VisitDate->setDate(2012, 03, 01);
$VisitDate->setTime(0,0,0);

$interval = $Dob->diff($VisitDate);

echo $interval->m;
echo $interval->d;

Should return 6 & 1 but returns 5 & 30.

Changing the timezones still returns unexpected results:

$Dob = new DateTime();
$Dob->setTimezone(new DateTimeZone('UTC'));
$Dob->setDate(2011, 8, 30);
$Dob->setTime(0,0,0);


$VisitDate = new DateTime();
$VisitDate->setTimezone(new DateTimeZone('UTC'));
$VisitDate->setDate(2012, 2, 1);
$VisitDate->setTime(0,0,0);

$interval = $Dob->diff($VisitDate);

echo $interval->m; (returns 5)
echo $interval->d; (return 2)

2011-08-30 to 2012-03-01 return 6 months and 0 days.

Tried it in on both a Linux and Windows server and same result.
 [2016-02-12 01:56 UTC] pinballfan at outlook dot com
After trying Marcel's code I got the same 'problem' on PHP 5.6 on a 64bit Windows 10 machine.  However unexpected the result, I believe it is consistent with how PHP's datetime rounds dates around.  Using this example:

$date1 = new DateTime('2011-08-31T00:00:00Z');
$date2 = new DateTime('2012-03-01T00:00:00Z');
$interval = $date1->diff($date2);
var_dump($interval);

$interval->m = 5
$interval->d = 30

The expectation might be 6 months and 1 day

What I'm guessing might be happening is that PHP starts adding 1 month like so:
2011-08-31 = starting date
2011-09-31 = starting date + 1 month (but there is no such thing as sept 31!)
2011-10-01 = date now wraps around to 2011-10-01 since
             2011-09-31 doesn't exist
2011-11-01 = + 1 month (2 total)
2011-12-01 = + 1 month (3 total)
2012-01-01 = + 1 month (4 total)
2012-02-01 = + 1 month (5 total)
2012-03-01 = When diff'ing PHP does NOT include the final date maybe like DatePeriod.

So that could be how it arrived at 5m and 30d
 [2016-11-23 07:14 UTC] lebeker at gmail dot com
Still got a problems:

(new DateTime('2016-10-01'))->diff((new DateTime('2016-10-31'))) // 1 month
(new DateTime('2016-11-01'))->diff((new DateTime('2016-11-30'))) // 29 days
either first should be 30 days, or the second one 1 month

(new DateTime('2016-11-01'))->diff((new DateTime('2016-12-01'))) // 30 days
but that's definitely 1 whole month

and what's more interesting
(new DateTime('2016-11-01'))->diff((new DateTime('2016-11-31'))) // 30 days
but there is no such date as '2016-11-31', but that's probably another bug.


PHP 7.0.8-0ubuntu0.16.04.3
 [2017-03-19 09:58 UTC] heiglandreas@php.net
-Status: Assigned +Status: Closed
 [2017-03-19 09:58 UTC] heiglandreas@php.net
This seems to be fixed in supported versions of PHP. Therefore I'm closing this issue.

php > $date_start = new DateTime('2010-03-01');
php >   $date_end   = new DateTime('2010-07-29');
php >
php >   $interval = $date_start->diff($date_end);
php >
php >   print_r($interval);
DateInterval Object
(
    [y] => 0
    [m] => 4
    [d] => 28
    [h] => 0
    [i] => 0
    [s] => 0
    [f] => 0
    [weekday] => 0
    [weekday_behavior] => 0
    [first_last_day_of] => 0
    [invert] => 0
    [days] => 150
    [special_type] => 0
    [special_amount] => 0
    [have_weekday_relative] => 0
    [have_special_relative] => 0
)
 [2017-03-20 11:22 UTC] tomasz dot majerski at o2 dot pl
Don't close this issue. PHP 7.0.6 still not getting expected results...

$date_start = '2017-03-01';
$date_end = '2017-03-31';

DateInterval Object
(
    [y] => 0
    [m] => 1
    [d] => 2
    [h] => 0
    [i] => 0
    [s] => 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
)
 [2017-03-20 11:44 UTC] requinix@php.net
Happens for March https://3v4l.org/R3LLN but not May https://3v4l.org/nblYg. DST?
 [2017-03-23 17:33 UTC] heiglandreas@php.net
-Status: Closed +Status: Re-Opened -Operating System: Debian 5.0.3 +Operating System: irrelevant -PHP Version: 5.3.3 +PHP Version: 7.1
 [2017-03-23 17:33 UTC] heiglandreas@php.net
That (https://3v4l.org/1b6fj) looks like a shift of the month number to me. Derick, could you have a look at that?
 [2017-05-25 23:27 UTC] michael dot moussa at gmail dot com
I have submitted a PR with a test case for this bug:

https://github.com/php/php-src/pull/2539
 [2017-10-24 05:23 UTC] kalle@php.net
-Status: Re-Opened +Status: Assigned
 [2019-01-28 12:03 UTC] cz dot paranoiq at gmail dot com
another funny behavior of diff():

```
$start = new DateTime('1922-03-26 00:00:00');
$end = new DateTime('2000-01-02 00:00:00');
echo $end->diff($start, true)->format('%Y years, %m months, %d days, %h hours, %i minutes, %s seconds');
```

returns some minutes and seconds, although both times are exact midnight. starts to be broken on this start date or this interval length. breaks with another start time since PHP 7.3.2RC (somewhere in 1892)
 [2019-02-06 09:02 UTC] techouse at gmail dot com
I found something weird using timezones yesterday and reported it here https://bugs.php.net/bug.php?id=77571

Basically the DateInterval reports a wrong interval whenever a timezone between UTC+01:00 and UTC+12:00 is applied. I wrote a bunch of tests with all kinds of timezones here https://github.com/techouse/php-date-interval-timezone-bug
 [2021-03-20 21:18 UTC] dolgopolsky dot a at gmail dot com
Still happens for March 1-29 on PHP 7.4
 [2021-04-06 19:51 UTC] derick@php.net
-Status: Assigned +Status: Closed
 [2021-04-06 19:51 UTC] derick@php.net
The fix for this bug has been committed.
If you are still experiencing this bug, try to check out latest source from https://github.com/php/php-src and re-test.
Thank you for the report, and for helping us make PHP better.

Fixed for PHP 8.1.
 [2021-07-25 22:35 UTC] antonino dot spampinato86 at gmail dot com
Excuse me derick can you check, in UTC it's five months from 2010-02-28 13:00:00 to 2010-07-28 13:00:00. Five months in Australia/Sidney time zone from 2010-02-28 13:00:00 to 2010-07-28 12:00:00. Not 4 months and 28 data.

Test script: https://3v4l.org/5KhLZ
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sat Dec 21 17:01:58 2024 UTC