Down the Rabbit Hole: WordPress and Timezones August 21 2013
“Why, sometimes I’ve believed as many as six impossible things before breakfast.”
― Lewis Carroll, Alice in Wonderland
A few months ago, we were working on the WooCommerce Pre-Orders extension and had to think about how we wanted to store the release date & time. Like most concepts that look simple and easy at first, this turned out to be somewhat complex. Throughout development, I ended up learning quite a bit about working with timezones inside WordPress. This article should help you understand how to work with timezones inside WordPress better and give you some time-saving shortcuts.
So back to our Pre-Orders example. First, store admins expect the date & time to be displayed in the same timezone as they’ve set for the site, but this makes determining when a pre-order needs to be released (and automatically charging all those saved credit cards) complicated and potentially unreliable. If the site timezone is changed at some point, there’s no way to know what the old timezone was, unless we store it along with the date & time itself. If it’s stored, then we need to do some calculations each time we check if the pre-order needs to be released. Not really our best option.
Instead let’s save the release date & time as a unix timestamp, which has no timezone (technically it’s UTC, but not exactly). This really simplifies our process to check if a pre-order should be released — a simple call to time()
and we can compare it against the release timestamp. Easy-peasy!
But wait, now we have to convert the release date & time entered by the admin for the pre-order into a unix timestamp. The first try was something like $timestamp = strtotime( $release_datetime );
, but this is wrong because strtotime
uses the default timezone set in PHP which may or may not be the same as the actual site timezone set within WordPress.
What we need to do is grab the site timezone and then create a unix timestamp of the release date & time:
try { // get datetime object from site timezone $datetime = new DateTime( $datetime_string, new DateTimeZone( get_option( 'timezone_string' ) ); // get the unix timestamp (adjusted for the site's timezone already) $timestamp = $datetime->format( 'U' ); } catch ( Exception $e ) { // you'll get an exception most commonly when the date/time string passed isn't a valid date/time }
This makes use of the neat DateTime class, which is available in PHP 5.2+ (making it safe for use with WP) and works great except for one small problem. When the site timezone is set to a UTC offset instead of a timezone, get_option( 'timezone_string' );
is blank, which throws an exception. Argh. Now we need some reliable way to get the site’s timezone string even if it’s set to a UTC offset. It turns out that WordPress has no built-in function to make this happen. Let’s make one:
/** * Returns the timezone string for a site, even if it's set to a UTC offset * * Adapted from http://www.php.net/manual/en/function.timezone-name-from-abbr.php#89155 * * @return string valid PHP timezone string */ function wp_get_timezone_string() { // if site timezone string exists, return it if ( $timezone = get_option( 'timezone_string' ) ) return $timezone; // get UTC offset, if it isn't set then return UTC if ( 0 === ( $utc_offset = get_option( 'gmt_offset', 0 ) ) ) return 'UTC'; // adjust UTC offset from hours to seconds $utc_offset *= 3600; // attempt to guess the timezone string from the UTC offset $timezone = timezone_name_from_abbr( '', $utc_offset ); // last try, guess timezone string manually if ( false === $timezone ) { $is_dst = date( 'I' ); foreach ( timezone_abbreviations_list() as $abbr ) { foreach ( $abbr as $city ) { if ( $city['dst'] == $is_dst && $city['offset'] == $utc_offset ) return $city['timezone_id']; } } } // fallback to UTC return 'UTC'; }
Much better! Now we can just replace our code above with this new function:
try { // get datetime object from site timezone $datetime = new DateTime( $datetime_string, new DateTimeZone( wp_get_timezone_string() ); // get the unix timestamp (adjusted for the site's timezone already) $timestamp = $datetime->format( 'U' ); } catch ( Exception $e ) { // you'll get an exception most commonly when the date/time string passed isn't a valid date/time }
Boom. A WordPress-friendly way to convert a date & time string into a unix timestamp. Let’s consider the opposite direction now. What if we need to convert a unix timestamp (remember unix timestamps are in UTC timezone) back into a timestamp adjusted the site’s timezone? We’ll need to add the timezone offset (in seconds) to the unix timestamp:
try { // get datetime object from unix timestamp $datetime = new DateTime( "@{$timestamp}", new DateTimeZone( 'UTC' ) ); // set the timezone to the site timezone $datetime->setTimezone( new DateTimeZone( wp_get_timezone_string() ) ); // return the unix timestamp adjusted to reflect the site's timezone return $timestamp + $datetime->getOffset(); } catch ( Exception $e ) { // something broke }
This works by creating a new DateTime
object from the stored timestamp. The @
in this "@{$timestamp}"
syntax means that the string is a timestamp. The timezone for the object is then set to the site’s timezone, which ensures that the DateTime::getOffset()
method returns the proper offset (in seconds) for the site’s timezone. As an example, this will return -14400
when the site’s timezone is set to EST or UTC-4.
However, timestamps aren’t very useful for displaying to a customer, so let’s use the ever-useful date_i18n()
and the date_format
option to format the date properly for display:
date_i18n( get_option( 'date_format' ), $timestamp );
This will display something like “August 21, 2013″. If you wanted to add on the time, you could use the time_format
option along with date()
function:
date( get_option( 'time_format' ), $timestamp );
That’s it! Have questions about timezones and WordPress? Let us know in the comments!
Photo Credit: leoplus via Compfight cc
The post Down the Rabbit Hole: WordPress and Timezones appeared first on SkyVerge.