ДСТ не учитывается в определенные относительные времена

В прошлое воскресенье Европейский Союз переключился с 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;
}

(запустить онлайн)

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

поскольку относительные форматы часто настолько нелогичны, что я не уверен, что получаю какое-то задокументированное поведение или это ошибка.

Можете ли вы пролить свет на это?

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;
}

(запустить онлайн)

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 не обрабатывает переходы часового пояса должным образом. Там есть оформить билет что признает это и даже RFC с 2011 года, который анализирует возможные исправления.

(Кредит идет в @ Алекс Блекс для этой информации.)

Стоит отметить, что старые добрые функции, основанные на метках времени 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;
}

(запустить онлайн)

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;
}

(запустить онлайн)

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() при отображении конечному пользователю.

0

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector