PHP DateTime add在DST上出错

PHP DateTime add在DST上出错,php,datetime,Php,Datetime,我正在用PHP实现一个带有时间戳和基于时间戳的计算值的表(对于我的问题,计算并不重要) 我注意到PHP的DateTime对象在从夏季时间到冬季时间(夏令时)的转换中使用add方法时有一个非常奇怪的行为 在我的示例中,我向时间戳添加15分钟并打印它(使用本地格式、unix utc时间戳和时间戳偏移量(以秒为单位): 27日。一切看起来都是正确的。但是当回到冬天的时候,有一个巨大的跳跃。我不认为DST是这样工作的 相反,我真的很想看到这个输出(在记事本中编辑): 现在02:00出现了2次,但偏移量不

我正在用PHP实现一个带有时间戳和基于时间戳的计算值的表(对于我的问题,计算并不重要)

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

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

27日。一切看起来都是正确的。但是当回到冬天的时候,有一个巨大的跳跃。我不认为DST是这样工作的

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

现在02:00出现了2次,但偏移量不同(并且对应unix时间戳)


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

使用带有
欧洲/维也纳
时区的DateTime对象无法实现

PHP不存储时间戳,而是本地时间+时区。当您执行
$dt->format('U')
时,它会将本地时间转换为时间戳。
2016年10月30日02:15:00(欧洲/维也纳)
可解析为两个时间戳: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";
}
$utc=新的日期时区(“utc”);
$viena=新日期时区(“欧洲/维也纳”);
$offset=新的日期间隔(“PT15M”);
foreach([新日期时间(2016-03-26 23:00:00),$utc),
新日期时间(“2016-10-29 23:00:00”,$utc)]为$dt){
$lastTs=NULL;
对于($j=0;$j<12;$j++){
$local=clone$dt;
$local->setTimeZone($viena);
echo$local->format('d.M.Y H:i:sp');//format('U');
如果(!为null($lastTs))
回显“(+”($dt->format('U')-$lastTs)。”;
$lastTs=$dt->format('U');
回音“\n”;
$dt->add($offset);
}
回音“\n”;
}

我刚刚注意到MySQL使用DATETIME做了同样的事情。我是否应该使用UNIX时间戳存储所有时间戳,因为任何抽象(如DATETIME)都无法正确计算?时间戳或带有UTC时区的DATETIME都可以完成这项工作。您知道为什么MySQL NOW()默认情况下不输出UTC吗?mysql开发者希望您将本地时间存储到DATETIME中吗?
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)
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)
$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";
}