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

时间:2017-03-28 08:59:17

标签: php datetime dst

上周日,欧盟从CET(+0100)转为CEST(+0200)。我正在编写代码以将增量应用于日期并且它无法正常工作,因为时区转换仅适用于某些相对格式:

  • '+x minutes'省略了缺失的小时
  • '+x hours'

这是我的测试代码:

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

由于relative formats通常是如此反直觉,我不确定我是否会收到一些记录在案的行为,或者这是一个错误。

你能说清楚吗?

1 个答案:

答案 0 :(得分:0)

它可能不是一种相对格式的误解,因为行为在同一格式下是不稳定的:

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

换句话说,添加61分钟会产生比添加60更早的日期。

简而言之, PHP无法正确处理时区转换。有一个issue ticket承认它,甚至2011年的RFC分析可能的修复。

(此信息将转至@Alex Blex。)

值得注意的是,基于Unix时间戳的旧功能也会受到影响:

<?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

解决方法

当然使用UTC:)

您可以在内部使用UTC进行所有计算,也可以在执行日期数学之前切换到UTC。后者(最详细的案例)意味着:

<?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

如果你在任何地方都使用UTC,那么在向最终用户展示时,只需要最后->setTimezone()