Sorry, I am still chewing on this bone. I looked at core.localize.php, and particularly, this function:
// Takes a Unix timestamp as input and returns it as GMT
function set_gmt($now = '')
{
if ($now == '')
{
$now = time();
}
$time = mktime( gmdate("H", $now),
gmdate("i", $now),
gmdate("s", $now),
gmdate("m", $now),
gmdate("d", $now),
gmdate("Y", $now),
-1 // this must be explicitly set or some FreeBSD servers behave erratically
);
// mktime() has a bug that causes it to fail during the DST "spring forward gap"
// when clocks are offset an hour forward (around April 4). Instead of returning a valid
// timestamp, it returns -1. Basically, mktime() gets caught in purgatory, not
// sure if DST is active or not. As a work-around for this we'll test for "-1",
// and if present, return the current time. This is not a great solution, as this time
// may not be what the user intended, but it's preferable than storing -1 as the timestamp,
// which correlates to: 1969-12-31 16:00:00.
if ($time == -1)
{
return $this->set_gmt();
}
else
{
return $time;
}
}
set_gmt() claims to take a Unix timestamp and convert it to GMT. However, it’s really encoding a GMT time by converting it into the local timezone.
If you call set_gmt() with no arguments, it starts with a call to time(). time() returns a Unix epoch timestamp, the number of seconds since Jan 1, 1970 UTC. The timestamp is already GMT, not local server time. If you called time() on a bunch of Unix servers anywhere in the world at the exact same time, they would all return the same timestamp, assuming they are set to the correct time. Their local timezone doesn’t matter.
set_gmt() then disassembles the timestamp into its parts with the gmdate() calls, and creates a new timestamp with mktime(). But mktime() assumes that the inputs are in local time. So the function ends up adding the difference between local time and GMT. I tried this in a little test program and saw that if I use the “now” time, the output time is 4 hours ahead (I am in EDT now). Here’s the output of my program:
1214535912 Thu, 26 Jun 2008 23:05:12 -0400 # time(), date('r', time())
1214550312 Fri, 27 Jun 2008 03:05:12 -0400 # set_gmt(), date('r', set_gmt())
So set_gmt() is encoding the GMT time by converting it to a time in the local timezone. So if it’s 11pm here and 3am in Greenwich, this function returns a time that says it’s 3am here.
Since all the functions in core.localize.php use this same scheme, they are internally consistent, even though they use this conversion, and EE presents times that are consistent. The problem comes when I try to relate these special EE timestamps to the way epoch timestamps are usually used, which is always GMT by default. Then all the timestamps in the EE database are ahead of what they might be by the difference between the current time zone and GMT.
And, if the installation moves timezones (by moving the server or the data), the times will all be off, forever, because when they were put in the database they were offset from GMT based on the original timezone. So it seems to me it would be better to store only real GMT times in the database.
I am sorry to be so pedantic. EE has been using timestamps this way for years, I suppose. Changing it is a big deal. Now that I understand how they are different than what I expected, I can adjust my own code accordingly. But both the documentation and the code say “time is stored in GMT”: I interpreted that in quite a different way, and maybe others have too.