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");