PHP错误DateTime::diff()返回错误的DateInterval


PHP wrong DateTime::diff() returns wrong DateInterval

我有一个问题,两个Datetime的差异。下面是显示DateInterval对象的命令行:

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));"

这里的DateInterval输出:

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"
}

注意这3天!我在PHP 5.5.8,但我敢肯定,这个日期间隔有0个月前几天。PHP 5.4.28和5.5.14版本的DateInterval输出为0天。我不确定PHP版本是否有影响。

在这两种情况下,days属性都是92

让我们深入了解Paul T. Rawkeen的答案,DateTime::diff的问题在于它在计算之前首先将时区转换为UTC。

<?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);
?>

给出如下:

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)

3天的差异现在非常清楚,在时区从Europe/Zurich转换为UTC后,$a$b的日期现在分别为2014-02-28 23:00:002014-05-31 22:00:00


解决方案是完全在UTC中工作,并在显示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);
?>

请注意,所有的日子现在都是01,尽管时间现在有点不同(参见本回答末尾的注释):

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)

要深入了解这种现象,请注意以下内容:

  • 2月有28天(2014年)
  • 5月31天
  • 夏时制开始于3月30日:+01:00
  • 欧洲/苏黎世为UTC+02:00

当从欧洲/苏黎世转换到UTC时,不仅要考虑年、月和日,还要考虑小时、分和秒。如果这一天不是每个月的第一天,则不会发生此问题,但是时间仍然是23:00和22:00(在上面的示例之前)。

这取决于您提供的DateTimeZone

如果您设置Europe/Zurich或任何EEST时间,您将得到所描述的结果。

如果GMT/UTC for example,你将得到$d = 0


您可以在项目中使用全局时区定义来避免此类问题(如果适合的话)

date_default_timezone_set("Europe/Zurich");

或定义DateTime对象所需的时区。


UPD:正如@mudasobwa在下面的评论中提到的,这个问题在这里大约三年前就提到了