dpmpa26468 2017-03-28 08:59
浏览 96
已采纳

DST没有在某些相对时间内计算

Last Sunday the European Union switched from CET (+0100) to CEST (+0200). I'm writing code to apply an increment to a date and it isn't working properly because time zone transition is only properly taken into account with some relative formats:

  • '+x minutes' omits the missing hour
  • '+x hours' doesn't

Here's my test code:

echo 'Time zone database: ' . timezone_version_get() . PHP_EOL;
echo PHP_EOL;

date_default_timezone_set('Europe/Madrid');

$start = new DateTime('2017-03-26 01:59:00');
$increments = array(
    '+2 minutes' => '2017-03-26 03:01:00',
    '+2 hours'   => '2017-03-26 04:59:00',
);

echo 'Start:        ' . $start->format('r') . PHP_EOL;
foreach ($increments as $increment => $expected_string) {
    echo '>>> ' . $increment . PHP_EOL;

    $expected_end = new DateTime($expected_string);
    $actual_end = clone $start;
    $actual_end->modify($increment);

    echo 'Expected end: ' . $expected_end->format('r') . PHP_EOL;
    echo 'Actual end:   ' . $actual_end->format('r') . PHP_EOL;
    echo ($expected_end->format('c')===$actual_end->format('c')  ? 'OK' : 'ERROR') . PHP_EOL;
    echo PHP_EOL;
}

(run online)

Time zone database: 2016.3

Start:        Sun, 26 Mar 2017 01:59:00 +0100
>>> +2 minutes
Expected end: Sun, 26 Mar 2017 03:01:00 +0200
Actual end:   Sun, 26 Mar 2017 03:01:00 +0200
OK

>>> +2 hours
Expected end: Sun, 26 Mar 2017 04:59:00 +0200
Actual end:   Sun, 26 Mar 2017 03:59:00 +0200
ERROR

Since relative formats are often so counter-intuitive I'm not sure whether I'm getting some documented behaviour or it's a bug.

Can you shed some light on it?

  • 写回答

1条回答 默认 最新

  • dpppic5186 2017-03-28 09:21
    关注

    It can't be a relative format misunderstanding because behaviour is erratic within the same format:

    date_default_timezone_set('Europe/Madrid');
    
    $start = new DateTime('2017-03-26 01:59:00');
    $increments = array(
        '+60 minutes'   => '2017-03-26 03:59:00',
        '+61 minutes'   => '2017-03-26 04:00:00',
    );
    
    echo 'Start:        ' . $start->format('r') . PHP_EOL;
    foreach ($increments as $increment => $expected_string) {
        echo '>>> ' . $increment . PHP_EOL;
    
        $expected_end = new DateTime($expected_string);
        $actual_end = clone $start;
        $actual_end->modify($increment);
    
        echo 'Expected end: ' . $expected_end->format('r') . PHP_EOL;
        echo 'Actual end:   ' . $actual_end->format('r') . PHP_EOL;
        echo ($expected_end->format('c')===$actual_end->format('c')  ? 'OK' : 'ERROR') . PHP_EOL;
        echo PHP_EOL;
    }
    

    (run online)

    Start:        Sun, 26 Mar 2017 01:59:00 +0100
    >>> +60 minutes
    Expected end: Sun, 26 Mar 2017 03:59:00 +0200
    Actual end:   Sun, 26 Mar 2017 03:59:00 +0200
    OK
    
    >>> +61 minutes
    Expected end: Sun, 26 Mar 2017 04:00:00 +0200
    Actual end:   Sun, 26 Mar 2017 03:00:00 +0200
    ERROR
    

    In other words, adding 61 minutes produces an earlier date than adding 60.

    In short, PHP does not handle time zone transitions properly. There's an issue ticket that acknowledges it and even an RFC from 2011 that analyses possible fixes.

    (Credit goes to @Alex Blex for this information.)

    It's worth noting that good old Unix-timestamp based functions are affected too:

    <?php
    
    date_default_timezone_set('Europe/Madrid');
    
    $start = strtotime('2017-03-26 01:59:00');
    $increments = array(
        '+60 minutes'   => '2017-03-26 03:59:00',
        '+61 minutes'   => '2017-03-26 04:00:00',
    );
    
    echo 'Start:        ' . date('r', $start) . PHP_EOL;
    foreach ($increments as $increment => $expected_string) {
        echo '>>> ' . $increment . PHP_EOL;
    
        $expected_end = strtotime($expected_string);
        $actual_end = strtotime($increment, $start);
    
        echo 'Expected end: ' . date('r', $expected_end) . PHP_EOL;
        echo 'Actual end:   ' . date('r', $actual_end) . PHP_EOL;
        echo ($expected_end===$actual_end ? 'OK' : 'ERROR') . PHP_EOL;
        echo PHP_EOL;
    }
    

    (run online)

    Start:        Sun, 26 Mar 2017 01:59:00 +0100
    >>> +60 minutes
    Expected end: Sun, 26 Mar 2017 03:59:00 +0200
    Actual end:   Sun, 26 Mar 2017 03:59:00 +0200
    OK
    
    >>> +61 minutes
    Expected end: Sun, 26 Mar 2017 04:00:00 +0200
    Actual end:   Sun, 26 Mar 2017 03:00:00 +0200
    ERROR
    

    Workaround

    Use UTC, of course :)

    You can either use UTC internally for all calculations or switch to UTC before performing date maths. The latter (the most verbose case) implies something like:

    <?php
    
    date_default_timezone_set('Europe/Madrid');
    
    $start = new DateTime('2017-03-26 01:59:00');
    $increments = array(
        '+60 minutes'   => '2017-03-26 03:59:00',
        '+61 minutes'   => '2017-03-26 04:00:00',
    );
    
    echo 'Start:        ' . $start->format('r') . PHP_EOL;
    $local = $start->getTimezone();
    $utc = new DateTimeZone('UTC');
    foreach ($increments as $increment => $expected_string) {
        echo '>>> ' . $increment . PHP_EOL;
    
        $expected_end = new DateTime($expected_string);
        $actual_end = clone $start;
        $actual_end->setTimezone($utc);
        $actual_end->modify($increment);
        $actual_end->setTimezone($local);
    
        echo 'Expected end: ' . $expected_end->format('r') . PHP_EOL;
        echo 'Actual end:   ' . $actual_end->format('r') . PHP_EOL;
        echo ($expected_end->format('c')===$actual_end->format('c')  ? 'OK' : 'ERROR') . PHP_EOL;
        echo PHP_EOL;
    }
    

    (run online)

    Start:        Sun, 26 Mar 2017 01:59:00 +0100
    >>> +60 minutes
    Expected end: Sun, 26 Mar 2017 03:59:00 +0200
    Actual end:   Sun, 26 Mar 2017 03:59:00 +0200
    OK
    
    >>> +61 minutes
    Expected end: Sun, 26 Mar 2017 04:00:00 +0200
    Actual end:   Sun, 26 Mar 2017 04:00:00 +0200
    OK
    

    If you use UTC everywhere you don't need any of this, just a final ->setTimezone() when displaying to end user.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 MATLAB动图的问题
  • ¥15 求差集那个函数有问题,有无佬可以解决
  • ¥15 【提问】基于Invest的水源涵养
  • ¥20 微信网友居然可以通过vx号找到我绑的手机号
  • ¥15 寻一个支付宝扫码远程授权登录的软件助手app
  • ¥15 解riccati方程组
  • ¥15 display:none;样式在嵌套结构中的已设置了display样式的元素上不起作用?
  • ¥15 使用rabbitMQ 消息队列作为url源进行多线程爬取时,总有几个url没有处理的问题。
  • ¥15 Ubuntu在安装序列比对软件STAR时出现报错如何解决
  • ¥50 树莓派安卓APK系统签名