Do not change the default timezone from UTC in WordPress

I discovered something a bit surprising about WordPress related to timezones: WordPress explicitly sets and expects the default timezone to be UTC (in settings.php) and the date/time functions sometimes rely on the fact that the default timezone is UTC. For instance if you do date_default_timezone_set(get_option('timezone_string')) and then later try to get a GMT timestamp from get_post_time() or get_post_modified_time(), it will fail to give you the right date.

So from calling $timestamp = get_post_time('U', true /*GMT*/), if we step up into core:

function get_post_time( $d = 'U', $gmt = false, $post = null, $translate = false ) { // returns timestamp
	$post = get_post($post);

	if ( $gmt )
		$time = $post->post_date_gmt;
		$time = $post->post_date;

	$time = mysql2date($d, $time, $translate);
	return apply_filters('get_post_time', $time, $d, $gmt);

You can see that it is going to pass post_date_gmt into mysql2date() but that this date/time (e.g. 2013-02-19 18:22:42) does not include with it any timezone information (a sad fact about the WordPress posts schema). So then mysql2date() is passed this timezone-free date/time:

function mysql2date( $format, $date, $translate = true ) {
	if ( empty( $date ) )
		return false;

	if ( 'G' == $format )
		return strtotime( $date . ' +0000' );

	$i = strtotime( $date );

	if ( 'U' == $format )
		return $i;

	if ( $translate )
		return date_i18n( $format, $i );
		return date( $format, $i );

And the strtotime() is going to interpret that timezone-agnostic date as the time in the current timezone! So even though the original date/time is coming from $post->post_date_gmt, it is getting passed into strtotime() with a default timezone not being UTC/GMT, and so the end resulting date/time returned is not GMT as originally requested.

If you find your code does this, the quick solution to the problem is to just reset the default timezone to UTC once you’ve finished working in the other timezone:

// do some stuff

But a more elegant solution would be to move to using DateTime objects and specify the DateTimeZone for each.

Anyway, this is something to be aware of, as I had to pull out a few hairs (and I don’t intend to go bald).

See also: Default timezone hardcoded as UTC? (Stack Exchange)