TIP: dealing with dates, times, and timezones in Drupal7 (and PHP)

Keywords

No matter how careful you are, it's very easy - especially with a site involving multiple stages and multiple admins, coders, and end-user authors - to get dates and times mixed up in any web framework. I'm going to send you to Drupal.org first, but then we are going to look into how PHP handles dates and times.

Don't rely on the descriptions regarding dates and times in the various Drupal admin browser interface pages involved with dates and times, read this page, all of it, top-to-bottom, now, first (then come back, because this site is funnier, because it includes Drupal heresy): Date Locale & Time Zone settings.

Pay particular attention to the section on Time Zone settings unique to each date field instance !

Ok, to PHP date/time handling. Once you get a hold of the PHP: DateTime class it's quite easy.

For those of you who have done some basic uni physics, I'd like you to think of special relativity (that's right, the stuff from Einstein). It's often misunderstood as meaning that "everything is relative", which would not be of much use, because then we would never know anything useful about anything. It has often been stated that it might have been better to call it (in English) the Theory of Invariance (as described in this nice article by Craig Rusbult, Ph.D.), because although the equations of relativity describe various components that change, they change in such a way that certain invariant quantities (such as the speed of light) do not change.

PHP handles dates/times and timezones in a similar; behind the scenes, there is an invariant, which for all intents and purposes you can call UTC "universal time". PHP happily allows you to create a DateTime object from a wide range of strings using:

public static DateTime DateTime::createFromFormat ( string $format , string $time [, DateTimeZone $timezone ] )

Note that nice DateTimeZone parameter !

Now PHP knows how to transform the timezone, without changing the "universal time" behind the scenes:

public DateTime DateTime::setTimezone ( DateTimeZone $timezone )

Note that in doing so you are NOT changing the actual datetime invariant ! You are merely changing the output you would see from the $date->format($format) function:

public string DateTime::format ( string $format )

You can even transform to the timezone object corresponding 'UTC' if you wish, but there's really nothing special about it from the point of view of dealing with the DateTime object, it does not care, it just sees 'UTC' as what in maths would be called an eigenvalue (in this case corresponding to our choice of invariance point). It's as if the setTimezone method is rotating the earth so we can "see" the time from a different perspective (via the format method).

Here's an example from an actual Drupal project I have been working on. A colleague had (just as I prefer it) written some Profile2 data fields to database using the field-specific setting Site's time zone:

When entering data into the field, the data entered is assumed to be in the site's time zone. When the data is saved to the database, it is converted to UTC. When retrieved from the database, the data is converted to the Site's time zone for anonymous users or the User's time zone for logged in users when User-configurable time zones is enabled.

Now be very careful about that bit 'When retrieved from the database' ! It means here in fact when Drupal retrieves it from the database and passes it through the full date field conversion cycle. The date in this case was indeed in UTC (in the actual database table), and I was given access to it in that form, like this:

2015-02-12 05:34:13

Which is in PHP date format:

'Y-m-d H:i:s'

But I am in the timezone 'Australia/Sydney' in summer (near Bondi Beach in fact, although I don't get to boogie board as much as I'd like), which is +11 hours ahead, and that time was in fact written in our local time at 2015-02-12 16:34:13 (which I am quite sure of, because I was not working at 5:34AM in the morning).

Our client needed the date to be sent to their marketing system in 'Australia/Sydney' time. And they also needed it a different format, for processing it at their end in their marketing system. PHP to the rescue, like this:

php > $date_db_string = '2015-02-12 05:34:13';
php > $timezone_local = new DateTimeZone('Australia/Sydney');
php > $timezone_utc = new DateTimeZone('UTC');
php > $datetime = DateTime::createFromFormat('Y-m-d H:i:s',$date_db_string, $timezone_utc);
php > print_r($datetime);
DateTime Object
(
    [date] => 2015-02-12 05:34:13.000000
    [timezone_type] => 3
    [timezone] => UTC
)
php > $datetime->setTimezone($timezone_local);
php > print_r($datetime);
DateTime Object
(
    [date] => 2015-02-12 16:34:13.000000
    [timezone_type] => 3
    [timezone] => Australia/Sydney
)
php > echo $datetime->format('m/d/Y h:i:s A');
02/12/2015 04:34:13 PM

And there you have it, the date time in the correct format (and correct time for Sydney) 02/12/2015 04:34:13 PM, being the afternoon, and a far more civilised time to work.

Notice how the [date] and [timezone] above always BOTH change within the DateTime object as the timezone is changed, but the implied invariant "universal time", behind the scenes, under the hood, in fact stays the same.

Drupal too leverages this facility, and has the ability to serve dates according to a single Drupal system time, or according to user specific timezones, if user specific timezones are permitted under Home > Administration > Configuration > Regional and Language:

/admin/config/regional/settings

There you can set the Default time zone and whether:

[ ] Users may set their own time zone.
GOTCHA: Note that it is NOT under Date Time !:
/admin/config/regional/date-time

That's just for the various formats.

Programmatically you can get at the date_default_timezone($check_user = TRUE):

Returns a timezone name to use as a default.

Parameters

bool $check_user: (optional) Whether or not to check for a user-configured timezone. Defaults to TRUE.

Return value

string The default timezone for a user, if available, otherwise the site.

Finally, Drupal7 also offers a massive range of date manipulation functions via the date_api.module.