Things I Learned While Writing a Timezone Aware Website in PHP

I’ve just finished my first PHP application that had to handle dates and time spans correctly across multiple timezones and I thought I would share a couple of the things that I learned.

Store Everything in GMT

My first piece of advice is to store EVERYTHING in Greenwich Mean Time (GMT). The reason for this is that if you try to store datetimes in the timezone that the user is operating in it’s going to cause problems when you try to aggregate data for that day in a different timezone. You’ll have to create code to check for each timezone individually. Fortunately, GTM isn’t too difficult to manage because PHP and MySQL both have functions that make it easy to work with (more on that later). You will have to do a lot of conversions from user time to GTM but it’s not terribly difficult if you create a helper function in your user model.

Today isn’t Always Today

In my application I had to deal with a lot of time spans. Specifically, I was looking at what events occurred in the user’s local timezone during a specific day. This doesn’t sound complicated but it really is when you factor timezones into the mix. Today in Eastern Standard Time is actually part of today and three hours of yesterday in Pacific Standard Time. This caused a lot of logic where I defined what ‘today’ is based on a time range like 2013-03-01 05:00:00 GMT to 2014-03-02 05:00:00 GMT (this is one ‘day’ in EST expressed in GMT).

Daylight Savings Time Sucks

Did you ever wonder why when you set the timezone in OSX or Linux it does it based on a city?

This is due to the crazy rules due to Daylight Savings Time (DST). In the same timezones, the rules for DST can vary from country to country, at the state and county levels, and change depending on the year (us American’s will remember the DST start and end dates changed recently). This is why you’ll get the crazy list of timezones that PHP has listed in their List of Supported Timezones.

I highly recommend using these timezones as the user’s timezone of choice and then use the functions below determine if the user is currently under DST and what their offset is. $tzId is the Timezone name for the user’s timezone.

function getDST($tzId, $time = null) {
    if($time == null){
        $time = gmdate('U');
    }

    $tz = new DateTimeZone($tzId);

    $transition = $tz->getTransitions($time);

    return $transition[0]['isdst'];
}

function getOffset($tzId, $time = null) {
    if($time == null){
        $time = gmdate('U');
    }

    $tz = new DateTimeZone($tzId);

    $transition = $tz->getTransitions($time);

    return $transition[0]['offset'];
}

Timezones Suck

Being an American I assumed that all timezones offsets were integer values. It turns out that there are actually some timezone that operate on half hour changes and even two that offset by 45 minutes. I have a hard enough time working with people in Mountain Time so I can’t even fathom working with someone that has a non-integer hour offset. :-)

This really shouldn’t affect your program if you’re calculating the offset using seconds but just realize that this can happen and it’s a total mind F when you see it. :-)

Timezone Specific Functions Suck

PHP is really build to make dates and times easy to work with but I ran into a couple problems when I moved my code from my development VM (set for a timezone of GMT) to my client’s server (set to EST). This was because (like most things it made sense in hindsight) the PHP date(), time(), and strtotime() functions all operate in the timezone specified by the date.timezone setting in php.ini.

So you don’t repeat my mistakes here is how to fix these problems before you have them.

date()

Use gmdate() instead. It uses the same formatting options as date() but it creates GMT based strings.

time()

I haven’t been able to find a direct replacement for this function but you can use gmdate('U') instead to get the same result.

strtotime()

This can be used normally except you will need to append GMT to all your strings. So instead of having:

strtotime('2012-03-01 01:23:45');
strtotime($somedate);

You would need to write:

strtotime('2012-03-01 01:23:45 GMT');
strtotime($somedate . ' GMT');

MySQL

Because my application logic knew what timezone the current user was located in I stayed away from doing any time manipulation using the MySQL functions. I did however need to insert timestamps occasionally and found that utc_timestamp() works well.

Enjoy and let me know your experiences in the comments below.

Update #1

It was pointed out (first by Reddit user kenlubin), that it’s much easier to user PHP’s DateTime class instead of mucking with the date functions (taken verbatim from the Reddit comment).

$utc_time = time retrieved from the database

$date_obj = new DateTime($utc_time, new DateTimeZone("UTC"));
$date_obj->setTimeZone("America/Los_Angeles");
$display_date = $date_obj->format("Y-m-d H:i:s");

12 thoughts on “Things I Learned While Writing a Timezone Aware Website in PHP

  1. // System is in Germany, timezone is CET (UTC+1).
    // Convert to CST6CDT, DST on (UTC-5)
    define(SYSTEM_TZ, ini_get(‘date.timezone’));
    $time = ’18:15′;
    $dateObj = new DateTime($time, new DateTimeZone(SYSTEM_TZ));
    $dateObj->setTimeZone(new DateTimeZone(“Pacific/Chatham”));
    echo $dateObj->format(‘Y-m-d H:i:s T’) . “\n\n”;
    $dateObj->setTimeZone(new DateTimeZone(“CST6CDT”));
    echo $dateObj->format(‘Y-m-d H:i:s T’) . “\n\n”;

    // Output: 12:15 CDT

    Or what if you want to convert to the weird Chatham Islands, NZ, UTC+12:45 time zone:

    $dateObj->setTimeZone(new DateTimeZone("Pacific/Chatham"));
    echo $dateObj->format('Y-m-d H:i:s T') . "\n\n";
    // Output: 2013-03-18 07:00:00 CHADT

    Isn’t this a heck of a better solution than this blog article’s?

  2. A couple of other things:

    A day is not always 24 hours (86400 seconds) long. In places that observe DST, one day a year will be shorter and one day will be longer.

    Local time (during DST) is not always 1 hour later, on Lord Howe Island it’s 30 minutes later.

  3. Pingback: Laravel the Timemaster – time convertions with DateTime PHP |

  4. How do you get the user’s timezone in the first place?

    Do you calculate it from their IP number using some clever geolocation service? Or try to guess it somehow from the browser’s offset using JavaScript’s getTimezoneOffset()?

    Or do you just present the user with a big list of timezone strings in a whopping great big drop-down list, and let them pick it manually?

    • We guess based on getTimezoneOffset when they initially create their account but we allow them to change it with the huge select.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>