4

I have an issue with a difference of two Datetime. Here is the command line to display the DateInterval object :

php -r "\$a = new Datetime('first day of 4 months ago midnight'); \$b = new Datetime('first day of 1 month ago midnight'); var_dump(\$a->diff(\$b));"

And here the DateInterval output :

class DateInterval#3 (15) {
  public $y =>      int(0)
  public $m =>      int(3)
  public $d =>      int(3)
  public $h =>      int(0)
  public $i =>      int(0)
  public $s =>      int(0)
  public $weekday =>               int(0)
  public $weekday_behavior =>      int(0)
  public $first_last_day_of =>     int(0)
  public $invert =>                int(0)
  public $days =>                  int(92)
  public $special_type =>               int(0)
  public $special_amount =>             int(0)
  public $have_weekday_relative =>      int(0)
  public $have_special_relative =>      int(0)
}

Edit: The first and second Datetime:

class DateTime#1 (3) {
  public $date =>
  string(19) "2014-03-01 00:00:00"
  public $timezone_type =>
  int(3)
  public $timezone =>
  string(13) "Europe/Zurich"
}

class DateTime#2 (3) {
  public $date =>
  string(19) "2014-06-01 00:00:00"
  public $timezone_type =>
  int(3)
  public $timezone =>
  string(13) "Europe/Zurich"
}

Notice the 3 days! I'm on PHP 5.5.8 but I'm sure that this DateInterval had 0 months a few days ago. The DateInterval output 0 days in PHP 5.4.28 and the 5.5.14. I'm not sure that the PHP version has an effect.

In both cases, the days property is 92.

Qantas 94 Heavy
  • 15,750
  • 31
  • 68
  • 83
rgazelot
  • 518
  • 6
  • 14
  • FYI: `$d` gives `0` on PHP 5.5.9 (WIN x86) – zamnuts Jul 24 '14 at 10:27
  • I'm running PHP 5.5.15RC1 and get `$d` as `0` – Laurence Jul 24 '14 at 10:27
  • Can you provide the output of a `var_dump($a,$b);`? – zamnuts Jul 24 '14 at 10:29
  • `PHP 5.4.9-4ubuntu2.4 (cli)` - same result. `$m:3`/`$d:3`/`$days:92` – Paul T. Rawkeen Jul 24 '14 at 10:29
  • How can I reproduce the problem? – Alexander Gelbukh Jul 24 '14 at 10:32
  • ``PHP 5.4.29-3+deb.sury.org~precise+1 (cli)`` return ``$m:3/$d:3/$days:92`` – ins0 Jul 24 '14 at 10:34
  • It's somewhat debatable what the correct answer is anyway. *"x months y days"* depends on how you define "a month", which is either anything from 28 to 31 days, **or** simply the numeric difference of the month numbers. Either way, if I tell you *"You'll die in 3 months and 5 days"*... when exactly are you going to die? Is that a different timespan than *"You'll die 3 months and 5 days after you meet the love of your life."*? – deceze Jul 24 '14 at 10:37
  • @deceize _March, 6_ is definitely exactly three month ago of _June, 6_, there is nothing debatable. It looks like it’s a PHP bug, which involves timezone settings (see Paul’s answer below.) – Aleksei Matiushkin Jul 24 '14 at 10:43
  • @mudasobwa But `DateInterval` expresses a *timespan*. If you say *March 6* to *June 6* is a *3 months 0 days interval*, then taking that interval and applying it to another date should yield the same result. So, *November 30th* plus *3 months 0 days* is what exactly? *February 30th*? Nope. I hope you see the problem I'm trying to express here. There's inherently a lot of problems with this stuff to begin with. – deceze Jul 24 '14 at 11:07
  • Just linking a similar QA: http://stackoverflow.com/questions/9163378/php-weird-dateinterval-length-calculation – zamnuts Jul 24 '14 at 12:24

2 Answers2

5

Providing insight into Paul T. Rawkeen's answer, the problem with DateTime::diff is that it first converts the timezone to UTC before computation.

<?php

$zurich = new DateTimeZone('Europe/Zurich');
$utc = new DateTimeZone('UTC');

$a = new DateTime('first day of 4 months ago midnight',$zurich);
$b = new DateTime('first day of 1 month ago midnight',$zurich);

var_dump($a,$b);

$a->setTimezone($utc);
$b->setTimezone($utc);

var_dump($a,$b);

?>

Gives the following:

object(DateTime)[3]
  public 'date' => string '2014-03-01 00:00:00' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'Europe/Zurich' (length=13)
object(DateTime)[4]
  public 'date' => string '2014-06-01 00:00:00' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'Europe/Zurich' (length=13)
object(DateTime)[3]
  public 'date' => string '2014-02-28 23:00:00' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'UTC' (length=3)
object(DateTime)[4]
  public 'date' => string '2014-05-31 22:00:00' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'UTC' (length=3)

The 3 day discrepancy is now very clear, after the timezone is converted from Europe/Zurich to UTC the dates are now 2014-02-28 23:00:00 and 2014-05-31 22:00:00 for $a and $b respectively.


The solution is to work entirely in UTC and convert before displaying the DateTime:

<?php

$zurich = new DateTimeZone('Europe/Zurich');
$utc = new DateTimeZone('UTC');

$a = new DateTime('first day of 4 months ago midnight',$utc);
$b = new DateTime('first day of 1 month ago midnight',$utc);

var_dump($a,$b);

$a->setTimezone($zurich);
$b->setTimezone($zurich);

var_dump($a,$b);

?>

Notice that all days are now 01, albeit the hours are now a little different (see the note at the end of this answer):

object(DateTime)[3]
  public 'date' => string '2014-03-01 00:00:00' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'UTC' (length=3)
object(DateTime)[4]
  public 'date' => string '2014-06-01 00:00:00' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'UTC' (length=3)
object(DateTime)[3]
  public 'date' => string '2014-03-01 01:00:00' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'Europe/Zurich' (length=13)
object(DateTime)[4]
  public 'date' => string '2014-06-01 02:00:00' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'Europe/Zurich' (length=13)

To offer a bit of insight into this phenomenon, note the following:

  • February has 28 days (in 2014)
  • May has 31 days
  • DST starts on March 30: +01:00
  • Europe/Zurich is UTC+02:00

When converting from Europe/Zurich to UTC one must consider more than just the year, month and day, but the hours, minutes, and seconds too. If this was any other day than the first of any month, this problem would not occur, however the hours would still be 23:00 and 22:00 (pre the examples above).

Community
  • 1
  • 1
zamnuts
  • 9,492
  • 3
  • 39
  • 46
2

This thing depends on DateTimeZone you provide.

If you set Europe/Zurich or any EEST time you will get the described result.

If GMT/UTC for e.g., you will get $d = 0.


You can use global time zone definition along your project to avoid such problems (if it suits you)

date_default_timezone_set( "Europe/Zurich" );

or define required time zone for DateTime objects.


UPD: as was mentioned below in comment, by @mudasobwa, this problem is mentioned here about 3 years ago.

Community
  • 1
  • 1
Paul T. Rawkeen
  • 3,994
  • 3
  • 35
  • 51