4

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
mente
  • 2,746
  • 1
  • 27
  • 33
  • 2
    The PHP timezone handling is totaly crap and already costs me days. Consider `keep everything in UTC and convert to local timezone only when showing` seriously. – Axel Amthor Dec 30 '15 at 16:55
  • 2
    See also http://stackoverflow.com/a/2532962/2908724 – bishop Dec 30 '15 at 17:00
  • @Axel, while personally I think PHP is "totally crap", when it comes to time zones, PHP actually has a leg up compared to some other languages. Consider python, which is a superior language (IMHO), but requires library support for time zones, and has some API nuance that create traps for developers (like pytz localize vs tz replace). Also, the argument for UTC all the things falls apart when you get into advanced scenarios like *scheduling*. – Matt Johnson-Pint Dec 30 '15 at 17:02
  • @MattJohnson I only do not agree with the __scheduling__ part: at least for this being precise, I used to convert everything to UTC and let the scheduler run in UTC as well. – Axel Amthor Dec 30 '15 at 17:29
  • @Axel - Most human events are based on a local time. Consider if you set a daily alarm clock by UTC only, then it will go off an hour early or an hour late after a switch to/from daylight saving time. ([Apple famously, made this mistake with the iPhone in 2010](http://venturebeat.com/2010/11/01/iphone-daylight-savings-time-alarm-bug-plagues-users-in-europe-us/)). See also: http://stackoverflow.com/a/19627330/634824 – Matt Johnson-Pint Dec 30 '15 at 18:09
  • @AxelAmthor - I'll change my mind, based on mente's comments in my answer. :) – Matt Johnson-Pint Dec 30 '15 at 21:58

1 Answers1

3

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.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • What is the difference between "`UTC`" and "`Europe/London`" - AFAIK Greenwich is same as London? – Axel Amthor Dec 30 '15 at 17:00
  • 1
    @AxelAmthor Europe/London obeys DST rules, while UTC by definition does not. See http://stackoverflow.com/a/16900553/2908724 – bishop Dec 30 '15 at 17:26
  • Exactly. London (and Greenwich) only follow GMT for part of the year. The other part is BST (British Summer Time), which use UTC+01:00. – Matt Johnson-Pint Dec 30 '15 at 18:07
  • Thanks for your comment! Yeah, I've pasted UTC time incorrectly. Fixed that. Saying `timestamp is timezone agnostic` I meant that whenever you change timezone timestamp should stay the same as it's always in UTC. That's what surprised me in PHP. When I set _first_ occurrence of 01:30 that corresponds to specific time in UTC I expect it to stay like this whenever timezone is set – mente Dec 30 '15 at 20:50
  • @mente - Yes, indeed it looks like a bug. I didn't notice before, but even in your code, calling `setTimezone` to change the zone from UTC to London should *not* affect the value returned by `getTimestamp`. I think I'll have to agree with Axel then about PHP being a mess with time zones. The intent is good, but it looks like the implementation is buggy. – Matt Johnson-Pint Dec 30 '15 at 21:57
  • 1
    @mente - You should answer this question yourself, explaining the results with reference to the bug. It will be helpful for others. – Matt Johnson-Pint Dec 30 '15 at 21:59
  • I hate answering myself. Maybe someone has a quick and dirty solution. Let's see – mente Dec 31 '15 at 07:40
  • Narrowed down problem further. You can check updated question – mente Jan 04 '16 at 14:25