【问题标题】:Why do DateTime method diff behave differently in PHP (timezone related)?为什么 DateTime 方法差异在 PHP(时区相关)中表现不同?
【发布时间】:2020-06-16 10:09:29
【问题描述】:

我希望了解为什么不能信任这个简单的 DateTime 操作 (2020-07-01)->diff(2020-07-31)

<?php

$timeZones = ['GMT', 'Europe/Paris', 'America/Denver', 'Asia/Qatar', 'Australia/Sydney'];

echo('Doing (2020-07-01)->diff(2020-07-31)' . "\n\n");

foreach ($timeZones as $timeZone) {

    date_default_timezone_set($timeZone);

    $startDate = new \DateTime('2020-07-01');

    $calcDateA = (new \DateTime('2020-07-01'))->modify('+1 month -1 day');
    $calcDateB = (new \DateTime('2020-07-01'))->modify('+30 days');
    $calcDateC = new \DateTime('2020-07-31');

    $diffA = $startDate->diff($calcDateA);
    $diffB = $startDate->diff($calcDateB);
    $diffC = $startDate->diff($calcDateC);

    echo("Using $timeZone\n");

    echo('days (A): ' . $diffA->d . "\n");
    echo('months (A): ' . $diffA->m . "\n");

    echo('days (B): ' . $diffB->d . "\n");
    echo('months (B): ' . $diffB->m . "\n");

    echo('days (C): ' . $diffC->d . "\n");
    echo('months (C): ' . $diffC->m . "\n\n");
}

此代码在任何 PHP 版本中执行时,都会输出以下内容:

Doing (2020-07-01)->diff(2020-07-31)

Using GMT

days (A): 30
months (A): 0

days (B): 30
months (B): 0

days (C): 30
months (C): 0

Using Europe/Paris

days (A): 0
months (A): 1

days (B): 0
months (B): 1

days (C): 0
months (C): 1

Using America/Denver

days (A): 30
months (A): 0

days (B): 30
months (B): 0

days (C): 30
months (C): 0

Using Asia/Qatar

days (A): 0
months (A): 1

days (B): 0
months (B): 1

days (C): 0
months (C): 1

Using Australia/Sydney

days (A): 0
months (A): 1

days (B): 0
months (B): 1

days (C): 0
months (C): 1

我感觉内部计算是使用 UTC 进行的,有人忘记补偿时区。任何人都可以确认?

谢谢。

【问题讨论】:

    标签: php datetime timezone


    【解决方案1】:

    简短回答:是的,这是一个错误。

    我已经简化了您的测试用例,使其更加明显:

    $timeZones = ['GMT', 'Europe/Paris'];
    foreach ($timeZones as $timeZone) {
        date_default_timezone_set($timeZone);
        $startDate = new \DateTime('2020-07-01');
        $calcDateA = (new \DateTime('2020-07-01'))->modify('+1 month -1 day');
        $diffA = $startDate->diff($calcDateA);
        echo("Using $timeZone\n");
        echo $startDate->format('r'), "\n";
        echo $calcDateA->format('r'), "\n";
        echo $diffA->format('Days: %d / Months: %m'), "\n\n";
    }
    
    Using GMT
    Wed, 01 Jul 2020 00:00:00 +0000
    Fri, 31 Jul 2020 00:00:00 +0000
    Days: 30 / Months: 0
    
    Using Europe/Paris
    Wed, 01 Jul 2020 00:00:00 +0200
    Fri, 31 Jul 2020 00:00:00 +0200
    Days: 0 / Months: 1
    

    我的猜测是,正如您所建议的,计算是在 UTC 中完成的:

    $utc = new DateTimeZone('UTC');
    foreach ($timeZones as $timeZone) {
        date_default_timezone_set($timeZone);
        $startDate = new \DateTime('2020-07-01');
        $startDate->setTimezone($utc);
        $calcDateA = (new \DateTime('2020-07-01'))->modify('+1 month -1 day');
        $calcDateA->setTimezone($utc);
        $diffA = $startDate->diff($calcDateA);
        echo("Using $timeZone\n");
        echo $startDate->format('r'), "\n";
        echo $calcDateA->format('r'), "\n";
        echo $diffA->format('Days: %d / Months: %m'), "\n\n";
    }
    

    这会导致某些上下文丢失:

    Using GMT
    Wed, 01 Jul 2020 00:00:00 +0000
    Fri, 31 Jul 2020 00:00:00 +0000
    Days: 30 / Months: 0
    
    Using Europe/Paris
    Tue, 30 Jun 2020 22:00:00 +0000
    Thu, 30 Jul 2020 22:00:00 +0000
    Days: 0 / Months: 1
    

    PHP 错误跟踪器中有几张票。在one of them 中,一位贡献者解释道:

    这与 DST 转换无关(仅一次)。但发生的事情是 PHP首先将其转换为UTC时间,最后进行比较 2016-12-31 23:00 到 2017-09-30 22:00 - 然后算法得到 更加混乱和混乱。

    这段代码很复杂,很遗憾需要重写。它会 花点时间。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-02-12
      • 2017-03-14
      • 2021-08-03
      • 2017-08-24
      • 1970-01-01
      • 2015-09-15
      • 1970-01-01
      相关资源
      最近更新 更多