PHP wrong DateTime::diff() returns wrong DateInterval

坚强是说给别人听的谎言 提交于 2020-01-03 20:05:14

问题


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.


回答1:


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).




回答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.



来源:https://stackoverflow.com/questions/24931166/php-wrong-datetimediff-returns-wrong-dateinterval

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!