Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DateTime using incorrect timezone

In my application I'm trying to calculate the time left until midnight GMT (UK time). At the moment I'm doing this:

$now = new DateTime();
$timeToMidnight = $now->setTimezone(new DateTimeZone('Europe/London'))->diff(new DateTime('tomorrow'))->format('%h hours, %i minutes and %s seconds');

The code is working, however it seems to be one hour behind (using GMT -1). At the moment the time is 11:49PM and the output is this:

1 hours, 10 minutes and 36 seconds

I've double checked my php.ini and I also have that timezone set as GMT:

date.timezone = Europe/London

This is also confirmed by checking phpinfo().

What gives? Why isn't my application using the correct timezone?

like image 505
John Dorean Avatar asked Oct 26 '13 22:10

John Dorean


Video Answer


1 Answers

I tested this on Linux PHP 5.5.5, with Europe/London set as the timezone in php.ini. I actually set the clock back four hours to do this, too. The minimal code I used to reproduce was:

$d = new DateTime('tomorrow');
echo $d->format('c e');

The (correct) output was:

2013-10-27T00:00:00+01:00 Europe/London

I am going to look for a bug in PHP or a bug in the timezone data. To find out which, we'll see what some other program makes of midnight in London tonight. Epoch Converter tells me this should have a Unix timestamp of 1382828400. To double check that timestamp, I ran in PHP:

$d = new DateTime('27-10-2013');
echo $d->format('U'); 

It also returned 1382828400. So, let's see what it's supposed to show...

TZ=Europe/London date --date="@1382828400" +%c

The output was:

Sun 27 Oct 2013 12:00:00 AM BST

Correct! So tzdata is fine. So let's look at PHP.

I ran your sample code, along with the date command, and got the following output:

1 hours, 29 minutes and 53 seconds
Sat Oct 26 21:30:07 UTC 2013
Sat Oct 26 22:30:07 BST 2013

This is, of course, correct.

I think at this point we have ruled out bugs in both tzdata and PHP, and need to look at configuration issues and programmer expectations.


First, as I noted previously, Europe/London is not UTC, which has no concept of summer time and thus does not change twice per year. Since it doesn't cause such problems, it's a best practice for servers to run on UTC, regardless of what time zone their users are in, and a best practice for programs to use UTC internally and then convert to/from local time zones for display and user input only.

My best guess is that your server's running PHP is actually set to use UTC and not Europe/London as its default time zone. This is the only configuration in which I could reproduce your issue. The results of that test were:

date.timezone = UTC

2 hours, 24 minutes and 36 seconds
Sat Oct 26 21:35:24 UTC 2013
Sat Oct 26 22:35:24 BST 2013

Going forward, you should work in UTC (and with Unix timestamps) wherever practical, and convert to local time as early in processing user input, and as late in displaying it, as you can. An edge case like this one, where summer time is about to end, may be an exception, but you have to be extra careful to ensure that each new DateTime object you construct has the correct time zone set when you construct it, and also be aware that they will have issues like this.

See also the huge and informative Daylight saving time and time zone best practices


Finally, to "fix" your code, let's do this:

$tz = new DateTimeZone('Europe/London');
$now = new DateTime('now', $tz);
$midnight = new DateTime('tomorrow', $tz);
$timeToMidnight = $now->diff($midnight);
echo $timeToMidnight->format('%h hours, %i minutes and %s seconds');
like image 70
Michael Hampton Avatar answered Sep 26 '22 23:09

Michael Hampton