DateTime изменяет временную метку во время зимнего перехода на летнее время

Сегодня встретил интересную особенность (?) В PHP DateTime::setTimestamp() поведение. Во время зимних изменений летнего времени, когда 1 час повторяется дважды, PHP конвертирует метку времени, чтобы всегда быть вторым часом. Рассмотрим следующий пример:

<?php

$date = new \DateTime("now", new \DateTimeZone("UTC"));
//2018-10-28T01:30:00 UTC, in London DST happens
$date->setTimestamp(1540686600);

echo $date->getTimestamp() . "\n";
//1540686600
echo $date->format('c') . "\n";
//2018-10-28T00:30:00+00:00

$date->setTimezone(new \DateTimeZone("Europe/London"));

echo $date->getTimestamp() . "\n";
//1540690200

$date->setTimezone(new \DateTimeZone("UTC"));

echo $date->getTimestamp() . "\n";
//1540690200

echo $date->format('c') . "\n";
//2018-10-28T01:30:00+00:00

Смотря на источник PHP пытается преобразовать метку времени в местное время (o_O). Итак, два вопроса:

  • почему есть преобразование метки времени? Временная метка всегда в UTC и должна быть независимой от местного времени / часового пояса.
  • Есть ли простое решение, чтобы сохранить идентификатор часового пояса и предполагаемую временную метку в DateTime объект? (Я знаю о возможности сохранить все в UTC и конвертировать в местный часовой пояс только при показе)

Найденный возможная ошибка подал в PHP

UPD 2016-01-04 (в UTC):

Сузили проблему до DateTime::getTimestamp(), Рассмотрим следующий код:

<?php

$date = new \DateTime("now", new \DateTimeZone("UTC"));
//2018-10-28T00:30:00 UTC
$date->setTimestamp(1540686600);
echo $date->format('c') . "\n"; //2018-10-28T00:30:00+00:00

$date->setTimezone(new \DateTimeZone("Europe/London"));
echo $date->format('c') . "\n"; //2018-10-28T01:30:00+01:00

$date->setTimezone(new \DateTimeZone("UTC"));
echo $date->format('c') . "\n"; //2018-10-28T00:30:00+00:00
echo $date->getTimestamp() . "\n"; //1540686600

Здесь метка времени не изменяется, и код работает как ожидалось. Следующий код, который похож и был приведен в оригинальном примере, не работает:

<?php

$date = new \DateTime("now", new \DateTimeZone("UTC"));
//2018-10-28T00:30:00 UTC
$date->setTimestamp(1540686600);
echo $date->format('c') . "\n"; //2018-10-28T00:30:00+00:00

$date->setTimezone(new \DateTimeZone("Europe/London"));
echo $date->format('c') . "\n"; //2018-10-28T01:30:00+01:00
//-------------- the only line that was added ------------------
echo $date->getTimestamp() . "\n"; //1540690200
//-------------- end of added line -----------------------------

$date->setTimezone(new \DateTimeZone("UTC"));
echo $date->format('c') . "\n"; //2018-10-28T01:30:00+00:00
echo $date->getTimestamp() . "\n"; //1540690200

4

Решение

Вы сказали:

Отметка времени всегда указывается в UTC и должна соответствовать местному времени, независимо от часового пояса.

Это утверждение противоречит само себе. Отметки времени указаны в UTC, да, но это не зависит от местного времени или часового пояса.

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

Тем не менее, ваши данные немного неверны. Отметка времени, которую вы указали, 1540686600, на самом деле это не 1:30 UTC, а скорее соответствует 2018-10-28T01:30:00+01:00, Следовательно, это действительно первый Появление 1:30 в день резервного перехода DST. Вторым случаем будет 2018-10-28T01:30:00+00:00что соответствует 1540690200,

По поводу вашего второго вопроса:

Есть ли простое решение, чтобы сохранить идентификатор часового пояса и предполагаемую метку времени в объекте DateTime?

Вот уже как DateTime работает в PHP. Он состоит из внутренней метки времени и соответствующего часового пояса. Это именно то, что вы показываете, когда вы звоните setTimezone,

3

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

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

По вопросам рекламы [email protected]