DST上的PHP DateTime添加错误

时间:2017-05-03 13:13:02

标签: php datetime

我正在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)

On 27. Mar一切看起来都是正确的。但是回到冬天的时候,有一个巨大的跳跃。我不认为这是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时间戳)。

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

1 个答案:

答案 0 :(得分:1)

使用Europe/Vienna时区的DateTime对象无法实现它。

PHP不存储时间戳,但是本地时间+时区Check this answer for details。执行$dt->format('U')时,它会将本地时间转换为时间戳。 30.Oct.2016 02:15:00 (Europe/Vienna)可以解析为2个时间戳:1477786500(Sun,2016年10月30日00:15:00 UTC)和1477790100(Sun,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";
}