4

Consider this code where we want to add or substract one second:

date_default_timezone_set("Europe/Amsterdam");

$time = 1477789199;
echo $time . '  -  ' . date('r', $time) . "\n";
// 1477789199  -  Sun, 30 Oct 2016 02:59:59 +0200

This is correct, as this timestamp is still just within DST (daylight savings time / summer time).

But now let's add one second to the timestamp integer, and exit DST:

$new = $time + 1;
echo $new . '  -  ' . date('r', $new);
// 1477789200  -  Sun, 30 Oct 2016 02:00:00 +0100

Hooray! PHP sees that one second later there is no DST anymore and shows a proper time string.

But what if we didn't add a second to the timestamp integer, but we used strtotime() to add that one second:

$new = strtotime('+1 second', $time);
echo $new . '  -  ' . date('r', $new);
// 1477792800  -  Sun, 30 Oct 2016 03:00:00 +0100

Yikes! We just went ahead by more than one hour instead of one second. And it doesn't even matter if you add one second, one hour, one day or one year you will always get one extra hour with it. Even if you add multiple years, you will only get one extra hour, which is weird because we enter and exit DST every year but you only get one extra hour regardless of how many years you add

But once we exit DST in October and subtract one second, all goes fine...

But then again. If we were in March and we have just entered DST, and we subtract one second, we observe exactly the same in reverse.


Wait, what ?! So ... ?

echo strtotime('+ 1 second - 1 second', 1477789199); // echoes 1477792799

Whoa ...


To me this sounds like a bug. Or is this 'by design'? Does anyone even know if this is documented somewhere, or whether it needs to be reported?

bwoebi
  • 23,637
  • 5
  • 58
  • 79
nl-x
  • 11,762
  • 7
  • 33
  • 61
  • Oops. If I checked the PHP bug pool before posting, I would have found a similar issue is reported in 2012 with no follow up: https://bugs.php.net/bug.php?id=62185 – nl-x Sep 21 '16 at 16:54
  • 1
    From the [doc](http://php.net/manual/en/function.strtotime.php): `Using this function for mathematical operations is not advisable. It is better to use DateTime::add() and DateTime::sub() in PHP 5.3 and later, or DateTime::modify() in PHP 5.2. ` – Dominique Lorre Sep 21 '16 at 17:08
  • Reported at https://bugs.php.net/bug.php?id=73138 – nl-x Sep 21 '16 at 17:30
  • The bug report considers version 5.6.26. Do you get it in another version? – SaidbakR Sep 21 '16 at 20:27
  • @nl-x Thanks for reporting, I assigned it to the maintainer (Derick Rethans)... It is somewhat "by very buggy design" :-D – bwoebi Sep 21 '16 at 20:37
  • @PaulCrovella You're over-complicating it. It is very simple actually. IF the clock time changes, then the offset should be adjusted. Also why would I only work in UTC timezone? The timezone is set in my php.ini and takes care of my loca(lisa)tion. – nl-x Sep 22 '16 at 08:23
  • 1
    @sємsєм yes, 5.3 too, and I now even read that someone has it in php7 too – nl-x Sep 22 '16 at 09:31

1 Answers1

1

The behavior is "well documented" .... in a test:

See https://bugs.php.net/bug.php?id=30532 (which also presents your expected result as expected) and the related test file (which asserts that the current behavior is correct) https://github.com/php/php-src/blob/master/ext/date/tests/bug30532.phpt

<?php date_default_timezone_set("America/New_York");

echo date('Y-m-d H:i:s T', strtotime('2004-10-31 EDT +1 hour'))."\n";
echo date('Y-m-d H:i:s T', strtotime('2004-10-31 EDT +2 hours'))."\n";
echo date('Y-m-d H:i:s T', strtotime('2004-10-31 EDT +3 hours'))."\n";
/* 2004-10-31 01:00:00 EDT
   2004-10-31 01:00:00 EST
   2004-10-31 02:00:00 EST */

echo date('Y-m-d H:i:s T', strtotime('2004-10-31 +1 hour'))."\n";
echo date('Y-m-d H:i:s T', strtotime('2004-10-31 +2 hours'))."\n";
echo date('Y-m-d H:i:s T', strtotime('2004-10-31 +3 hours'))."\n";
/* 2004-10-31 01:00:00 EDT
   2004-10-31 02:00:00 EST
   2004-10-31 03:00:00 EST */

Note that in the former case the timezone (here: EDT) is being passed directly to the string, in the latter case it isn't.

In general strtotime is taking the timestamp (i.e. of 2004-10-31 - or in your specific case: the passed timestamp), converted to a representation with individual parameters, ignoring DST (i.e. individual hours, minutes, seconds, day, month, year etc.), the operation is applied to it and then converted back to the timestamp.

In particular:

echo date('r', strtotime('+ 0 second', 1477789199));
#> Sun, 30 Oct 2016 02:59:59 +0100

strtotime() throws the timezone after conversion away, i.e. only takes

Sun, 30 Oct 2016 02:59:59

and then applies the primary applicable timezone to your timezone location (i.e. Europe/Amsterdam), ending up with CET (primary!) - CEST is also possible, but only second choice.

Now, look back at the test above, just specify the originating timezone explicitly.

Thus, if you wish it to behave the way you need it:

echo date('r', strtotime('CEST', 1477789199));
#> Sun, 30 Oct 2016 02:59:59 +0200
echo date('r', strtotime('CEST + 1 second', 1477789199));
#> Sun, 30 Oct 2016 02:00:00 +0100

In fact, prepending 'CEST ' will be fine for all the times (as it will always fallback to CET if CEST is not matching and there's no overlap on CET -> CEST transition).

bwoebi
  • 23,637
  • 5
  • 58
  • 79