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 微信小程序协议怎么写
  • ¥15 c语言怎么用printf(“\b \b”)与getch()实现黑框里写入与删除?
  • ¥20 怎么用dlib库的算法识别小麦病虫害
  • ¥15 华为ensp模拟器中S5700交换机在配置过程中老是反复重启
  • ¥15 java写代码遇到问题,求帮助
  • ¥15 uniapp uview http 如何实现统一的请求异常信息提示?
  • ¥15 有了解d3和topogram.js库的吗?有偿请教
  • ¥100 任意维数的K均值聚类
  • ¥15 stamps做sbas-insar,时序沉降图怎么画
  • ¥15 买了个传感器,根据商家发的代码和步骤使用但是代码报错了不会改,有没有人可以看看