DateTime alters timestamp during DST winter change

安稳与你 提交于 2019-12-07 09:30:27

问题


Today encountered interesting feature (?) in PHP DateTime::setTimestamp() behavior. During winter DST changes, when 1 hour repeats twice, PHP converts timestamp to always be the second hour. Consider following example:

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

Looking at source PHP tries to convert timestamp to local time (o_O). So two questions:

  • why there's conversion of timestamp? Timestamp is always in UTC and should be local time/timezone agnostic
  • is there any simple solution to keep both timezone id and intended timestamp in DateTime object? (I am aware of possibility to keep everything in UTC and convert to local timezone only when showing)

Found possible bug filed in PHP

UPD 2016-01-04 (in UTC):

Narrowed down problem to DateTime::getTimestamp(). Consider following code:

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

Here timestamp is not altered and code works as expected. Following code that is similar and was given in original example is broken:

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

回答1:


You said:

Timestamp is always in UTC and should be local time, timezone agnostic

That statement is in conflict with itself. Timestamps are in UTC, yes, but that is not local time or time zone agnostic.

Put another way, a timestamp always refers to a specific instant in time. A local time might be ambiguous, but a timestamp is not. If you wanted the first instance of the ambiguous local time, then specify a timestamp that is an hour earlier.

However, your data is slightly incorrect. The timestamp you specified, 1540686600, is actually not 1:30 UTC but rather it corresponds to 2018-10-28T01:30:00+01:00. Therefore, it is indeed the first occurrence of 1:30 on the day of the fall-back DST transition. The second occurrence would be 2018-10-28T01:30:00+00:00, which corresponds to 1540690200.

Regarding your second question:

is there any simple solution to keep both timezone id and intended timestamp in DateTime object?

That's already how DateTime works in PHP. It consists of an internal timestamp, and an associated time zone. That's exactly what you're showing when you call setTimezone.



来源:https://stackoverflow.com/questions/34533604/datetime-alters-timestamp-during-dst-winter-change

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!