【问题标题】:PHP DateTime add gets wrong on DSTPHP DateTime 添加在 DST 上出错
【发布时间】:2017-10-01 08:24:07
【问题描述】:

我正在用 PHP 实现一个带有时间戳和基于时间戳计算的值的表(计算对我的问题无关紧要)。

在从夏令时到冬令时(夏令时)的转换中使用 add 方法时,我注意到 PHP 的 DateTime 对象中有一个非常奇怪的行为。

在我的示例中,我将 15 分钟添加到时间戳并打印(使用本地格式、unix utc 时间戳和时间戳偏移以秒为单位):

<?php

date_default_timezone_set('Europe/Vienna');

$offset = new DateInterval("PT15M");

foreach([new DateTime("2016-03-27 01:00:00"),
         new DateTime("2016-10-30 01:00:00")] as $dt) {
    $lastTs = NULL;

    for($j = 0; $j < 12; $j++) {
        echo $dt->format('d.M.Y H:i:s P (U)');

        if(!is_null($lastTs))
            echo ' (+' . ($dt->format('U') - $lastTs) . ')';

        $lastTs = $dt->format('U');

        echo "\n";

        $dt->add($offset);
    }

    echo "\n";
}

这个小脚本给了我一个这样的表格(注意 30.Oct.2016 02:15 的巨大跳跃):

27.Mar.2016 01:00:00 +01:00 (1459036800)
27.Mar.2016 01:15:00 +01:00 (1459037700) (+900)
27.Mar.2016 01:30:00 +01:00 (1459038600) (+900)
27.Mar.2016 01:45:00 +01:00 (1459039500) (+900)
27.Mar.2016 03:00:00 +02:00 (1459040400) (+900)
27.Mar.2016 03:15:00 +02:00 (1459041300) (+900)
27.Mar.2016 03:30:00 +02:00 (1459042200) (+900)
27.Mar.2016 03:45:00 +02:00 (1459043100) (+900)
27.Mar.2016 04:00:00 +02:00 (1459044000) (+900)
27.Mar.2016 04:15:00 +02:00 (1459044900) (+900)
27.Mar.2016 04:30:00 +02:00 (1459045800) (+900)
27.Mar.2016 04:45:00 +02:00 (1459046700) (+900)

30.Oct.2016 01:00:00 +02:00 (1477782000)
30.Oct.2016 01:15:00 +02:00 (1477782900) (+900)
30.Oct.2016 01:30:00 +02:00 (1477783800) (+900)
30.Oct.2016 01:45:00 +02:00 (1477784700) (+900)
30.Oct.2016 02:00:00 +02:00 (1477785600) (+900)
30.Oct.2016 02:15:00 +01:00 (1477790100) (+4500)
30.Oct.2016 02:30:00 +01:00 (1477791000) (+900)
30.Oct.2016 02:45:00 +01:00 (1477791900) (+900)
30.Oct.2016 03:00:00 +01:00 (1477792800) (+900)
30.Oct.2016 03:15:00 +01:00 (1477793700) (+900)
30.Oct.2016 03:30:00 +01:00 (1477794600) (+900)
30.Oct.2016 03:45:00 +01:00 (1477795500) (+900)

3 月 27 日,一切正常。但是当回到冬天时,有一个巨大的跳跃。我不认为这是 DST 的工作原理。

相反,我真的很想看到这个输出(在记事本中编辑):

30.Oct.2016 01:45:00 +02:00 (1477784700) (+900)
30.Oct.2016 02:00:00 +02:00 (1477785600) (+900)
30.Oct.2016 02:15:00 +02:00 (1477786500) (+900)
30.Oct.2016 02:30:00 +02:00 (1477787400) (+900)
30.Oct.2016 02:45:00 +02:00 (1477788300) (+900)
30.Oct.2016 02:00:00 +01:00 (1477789200) (+900)
30.Oct.2016 02:15:00 +01:00 (1477789200) (+900)
30.Oct.2016 02:30:00 +01:00 (1477791000) (+900)
30.Oct.2016 02:45:00 +01:00 (1477791900) (+900)
30.Oct.2016 03:00:00 +01:00 (1477792800) (+900)
30.Oct.2016 03:15:00 +01:00 (1477793700) (+900)

现在 02:00 出现了 2 次,但偏移量不同(以及正确的 unix 时间戳)。

需要对我的代码进行哪些更改才能获得如上所示的正确结果?

【问题讨论】:

    标签: php datetime


    【解决方案1】:

    没有办法使用带有Europe/Vienna 时区的 DateTime 对象来实现它。

    PHP 不存储时间戳,而是本地时间 + 时区 Check this answer for details。当您执行$dt-&gt;format('U') 时,它会将本地时间转换为时间戳。 30.Oct.2016 02:15:00 (Europe/Vienna) 可以解析为 2 个时间戳:1477786500(2016 年 10 月 30 日星期日 00:15:00 UTC)和 1477790100(2016 年 10 月 30 日星期日 01:15:00 UTC)。由于模棱两可,PHP 选择了后者,这会破坏您的计算。

    解决方法是使用UTC 时区进行任何日期时间操作,并将其转换为本地时区仅用于输出:

    $utc = new DateTimeZone('utc');
    $viena = new DateTimeZone('Europe/Vienna');
    
    $offset = new DateInterval("PT15M");
    
    foreach([new DateTime("2016-03-26 23:00:00", $utc),
             new DateTime("2016-10-29 23:00:00", $utc)] as $dt) {
        $lastTs = NULL;
    
        for($j = 0; $j < 12; $j++) {
            $local = clone $dt;
            $local->setTimeZone($viena);
            echo $local->format('d.M.Y H:i:s P'); // <== the only place where you need local timezone
            echo $dt->format(' (U)');
    
            if(!is_null($lastTs))
                echo ' (+' . ($dt->format('U') - $lastTs) . ')';
    
            $lastTs = $dt->format('U');
    
            echo "\n";
    
            $dt->add($offset);
        }
    
        echo "\n";
    }
    

    【讨论】:

    • 我刚刚注意到 MySQL 使用 DATETIME 做同样的事情。我是否应该使用 UNIX Timestamps 存储所有时间戳,因为像 DATETIME 这样的抽象根本无法正确计算?
    • 时间戳或带有 UTC 时区的 DateTime 都可以完成这项工作。
    • 你知道为什么 MySQL NOW() 默认不输出 UTC 吗? mysql-devs 是否希望您将本地时间存储到 DATETIME 中?
    猜你喜欢
    • 2020-07-17
    • 1970-01-01
    • 2017-05-28
    • 2011-07-25
    • 2020-03-07
    • 1970-01-01
    • 2012-08-29
    • 2018-03-14
    • 1970-01-01
    相关资源
    最近更新 更多