Beware gmmktime()

Events happening in the community are now at Drupal community events on www.drupal.org.
karens's picture

I was doing some research on the 2,357th report of a timezone error in date or calendar or event, one of those errors that not everyone can reproduce, and found some very interesting info about gmmktime() -- a function that is used extensively in all these modules.

The launchpad was this issue: http://drupal.org/node/147392. Here dgtlmoon was proposing to change some code from:

<?php
$now
= (time() - date("Z"));
?>

to:
<?php
$now
= (gmmktime()) ;
?>

I told him there should be no reason for such a change because time() should create the current time, adjusted by the server's timezone, date('Z') should adjust it for the timezone difference, and gmmktime() should be the current time with no timezone adjustment. In other words, they should be the same.

He ran some tests and reported that they were NOT the same on his server, so I set up a very simple 2 line test script and ran it on my own server to determine the difference between time() and gmmktime() on a server that is not set to GMT. I would expect the difference to be the timezone offset, and in php 4, that's what it is. But on php 5 time() and gmmktime() produce identical results. Whoa!!

Aha, I thought. This is maybe a php 5 bug that needs to be reported. So I went to the zend site and searched through bugs and found a number of interesting ones for various versions of php. I also found that although all arguments for mktime() and gmmktime() are stated to be optional, apparently they are not. Basically, it looks like gmmktime() may be the source behind at least some of the timezone errors on the issue queues.

Here are some of the things I found:

From the php issue queue http://bugs.php.net/bug.php?id=30096:

Bug #30096 - a mysterious 1 hour discrepancy in the results for gmmktime() depending on whether it is being calculated during daylight savings time or not.

Another issue on the php issue queue http://bugs.php.net/bug.php?id=36367:

Bug #36367 gmmktime() returns GMT offset in wrong direction -- if 2 hours should be added, instead 2 hours are subtracted.

From the Zend newsletter at http://devzone.zend.com/article/464-Zend-Weekly-Summaries-Issue-210:

Derick...agreed that the problem Vladimir had identified was real - gmmktime() returns bogus results due to some confusion over DST.

Another Zend newsletter: http://devzone.zend.com/node/view/id/1517

Last week's discussion about mktime()'s newfangled E_STRICT error message turned out to be the key that opened a can of worms..Effectively, that boiled down to silently making mktime() with no arguments a synonym for time()...Pierre pointed out that gmmktime() will also throw an E_STRICT warning now. He couldn't see any reason not to just fix both functions in the way he'd suggested; it was obvious from a simple search that a lot of PHP applications use mktime() in what is now deemed the 'wrong' way... Pierre pointed out that his fix would force mktime() without arguments to use the exact same internal code as time(), and there would be next to no advantage in choosing one over the other if he got his way...Derick stuck to his [guns], too, writing a careful explanation of the way gmmktime() without arguments is broken in PHP 4 and correctly handled in PHP 5.1 and up.

Comments

you said "time() should

mfb's picture

you said "time() should create the current time, adjusted by the server's timezone" but that's not correct, time() gives you the number of seconds since January 1 1970 00:00:00 GMT -- this will be the same no matter where you are or what timezone you're using.

with the optional arguments, often times you don't want to leave them out because weird things will happen. for instance you need to choose a generic day or hour that is always valid.

I don't think that is right,

karens's picture

I don't think that is right, according to Zend, time() is equivalent to mktime(), an the mktime() documentation says

Arguments may be left out in order from right to left; any arguments thus omitted will be set to the current value according to the local date and time.

while the gmmktime() documentation says

Like mktime(), arguments may be left out in order from right to left, with any omitted arguments being set to the current corresponding GMT value.

Also, note that all parameters are supposed to be optional, so gmmktime() should be creating the current GMT date and time() and mktime() should be creating the current local date. So they should be different, and they are different in php4 but are not different in php5.

mktime() and gmmktime accept

mfb's picture

mktime() and gmmktime accept different things (times in local timezone vs. GMT) but create the same thing, a unix timestamp. as does time(), which doesn't accept any parameters.

The values you put in to the mktime functions, as well as any you leave out, are assumed to be local timezone hours, days, etc. for mktime() and GMT hours, days, etc. for gmmktime().

They all create unix timestamps, which are timezone agnostic; just a count of seconds; a unix timestamp in one timezone is the same as the unix timestamp in another. If the unix timestamps are different then they are different/non-simultaneous times.

if you're not passing any parameters, there's no reason to use mktime() or gmmktime(), you just use time() which I'd assume uses a little less resources.

I was using time(), it was

karens's picture

I was using time(), it was some of the proposed patches that were using gmmktime().

However, you're right about time() -- my understanding was that it would produce a different result depending on the timezone of the server and it does not.

time(), mktime(), and gmmktime() tests

karens's picture

I ran some tests to see what happened by changing between php4 and php5 on negative, positive and gmt server settings, with and without arguments, and came up with the following. I just changed my server timezone, then switched back and forth between php4 and php5, restarting apache each time. The server offset (date('Z')) that was zero in php4 ended up as 3600 in php5, while all other offsets matched. I have no explanation for that.I goofed on that value, the timezone offset is the same in all versions. I was trying for GMT but got 1 hour of DST instead.

Actual local time starting June 26, 2007 at 8:46 AM, negative timezone


Php version: 4.4.7>
Server offset: -18000>

Function Timestamp gmdate('r', $timestamp) date('r', $timestamp)
time() 1182865560 Tue, 26 Jun 2007 13:46:00 +0000 Tue, 26 Jun 2007 08:46:00 -0500
mktime() 1182865560 Tue, 26 Jun 2007 13:46:00 +0000 Tue, 26 Jun 2007 08:46:00 -0500
gmmktime() 1182847560 Tue, 26 Jun 2007 08:46:00 +0000 Tue, 26 Jun 2007 03:46:00 -0500
mktime(1, 1, 1) 1182837661 Tue, 26 Jun 2007 06:01:01 +0000 Tue, 26 Jun 2007 01:01:01 -0500
gmmktime(1, 1, 1) 1182819661 Tue, 26 Jun 2007 01:01:01 +0000 Mon, 25 Jun 2007 20:01:01 -0500


Php version: 5.2.2>
Server offset: -18000>

Function Timestamp gmdate('r', $timestamp) date('r', $timestamp)
time() 1182865596 Tue, 26 Jun 2007 13:46:36 +0000 Tue, 26 Jun 2007 08:46:36 -0500
mktime() 1182865596 Tue, 26 Jun 2007 13:46:36 +0000 Tue, 26 Jun 2007 08:46:36 -0500
gmmktime() 1182865596 Tue, 26 Jun 2007 13:46:36 +0000 Tue, 26 Jun 2007 08:46:36 -0500
mktime(1, 1, 1) 1182837661 Tue, 26 Jun 2007 06:01:01 +0000 Tue, 26 Jun 2007 01:01:01 -0500
gmmktime(1, 1, 1) 1182819661 Tue, 26 Jun 2007 01:01:01 +0000 Mon, 25 Jun 2007 20:01:01 -0500

Change server to GMT


Php version: 4.4.7>
Server offset: 3600>

Function Timestamp gmdate('r', $timestamp) date('r', $timestamp)
time() 1182865859 Tue, 26 Jun 2007 13:50:59 +0000 Tue, 26 Jun 2007 14:50:59 +0100
mktime() 1182865859 Tue, 26 Jun 2007 13:50:59 +0000 Tue, 26 Jun 2007 14:50:59 +0100
gmmktime() 1182869459 Tue, 26 Jun 2007 14:50:59 +0000 Tue, 26 Jun 2007 15:50:59 +0100
mktime(1, 1, 1) 1182816061 Tue, 26 Jun 2007 00:01:01 +0000 Tue, 26 Jun 2007 01:01:01 +0100
gmmktime(1, 1, 1) 1182819661 Tue, 26 Jun 2007 01:01:01 +0000 Tue, 26 Jun 2007 02:01:01 +0100


Php version: 5.2.2>
Server offset: 3600>

Function Timestamp gmdate('r', $timestamp) date('r', $timestamp)
time() 1182865902 Tue, 26 Jun 2007 13:51:42 +0000 Tue, 26 Jun 2007 14:51:42 +0100
mktime() 1182865902 Tue, 26 Jun 2007 13:51:42 +0000 Tue, 26 Jun 2007 14:51:42 +0100
gmmktime() 1182865902 Tue, 26 Jun 2007 13:51:42 +0000 Tue, 26 Jun 2007 14:51:42 +0100
mktime(1, 1, 1) 1182816061 Tue, 26 Jun 2007 00:01:01 +0000 Tue, 26 Jun 2007 01:01:01 +0100
gmmktime(1, 1, 1) 1182819661 Tue, 26 Jun 2007 01:01:01 +0000 Tue, 26 Jun 2007 02:01:01 +0100

Change server to positive timezone


Php version: 4.4.7>
Server offset: 36000>

Function Timestamp gmdate('r', $timestamp) date('r', $timestamp)
time() 1182865982 Tue, 26 Jun 2007 13:53:02 +0000 Tue, 26 Jun 2007 23:53:02 +1000
mktime() 1182865982 Tue, 26 Jun 2007 13:53:02 +0000 Tue, 26 Jun 2007 23:53:02 +1000
gmmktime() 1182901982 Tue, 26 Jun 2007 23:53:02 +0000 Wed, 27 Jun 2007 09:53:02 +1000
mktime(1, 1, 1) 1182783661 Mon, 25 Jun 2007 15:01:01 +0000 Tue, 26 Jun 2007 01:01:01 +1000
gmmktime(1, 1, 1) 1182819661 Tue, 26 Jun 2007 01:01:01 +0000 Tue, 26 Jun 2007 11:01:01 +1000


Php version: 5.2.2>
Server offset: 36000>

Function Timestamp gmdate('r', $timestamp) date('r', $timestamp)
time() 1182865946 Tue, 26 Jun 2007 13:52:26 +0000 Tue, 26 Jun 2007 23:52:26 +1000
mktime() 1182865946 Tue, 26 Jun 2007 13:52:26 +0000 Tue, 26 Jun 2007 23:52:26 +1000
gmmktime() 1182865946 Tue, 26 Jun 2007 13:52:26 +0000 Tue, 26 Jun 2007 23:52:26 +1000
mktime(1, 1, 1) 1182783661 Mon, 25 Jun 2007 15:01:01 +0000 Tue, 26 Jun 2007 01:01:01 +1000
gmmktime(1, 1, 1) 1182819661 Tue, 26 Jun 2007 01:01:01 +0000 Tue, 26 Jun 2007 11:01:01 +1000

I have my server set to UTC

mfb's picture

I have my server set to UTC by simply removing the /etc/localtime file, which causes the operating system to default to UTC. It works the same on both PHP4 and PHP5:
% php -r "print date(r);"
Tue, 26 Jun 2007 15:32:26 +0000
% php-4.4.7/sapi/cli/php -r "print date(r);"
Tue, 26 Jun 2007 15:32:34 +0000

If I change to british summer time it also works the same on PHP4 and PHP5:
% export TZ=Europe/London
% php -r "print date(r);"
Tue, 26 Jun 2007 16:33:01 +0100
% php-4.4.7/sapi/cli/php -r "print date(r);"
Tue, 26 Jun 2007 16:33:10 +0100

I guess I should just read

mfb's picture

I guess I should just read the site rather than what landed in my inbox :p

date('r') would be the same

karens's picture

date('r') would be the same as date('r', time()), and in my tests there was no difference between php4 and php5 on that either. The problem is we can't assume people have any control over their server timezone (many don't) so the best we can do is adjust for it, which we usually do by using date('Z') to adjust results.

My main point is that gmmktime() is not reliable/consistant between php4 and php5 if no arguments are supplied, and if you read some of the bug reports I linked to above you'll see that gmmktime() can also produce wrong results in some php versions even if arguments are supplied, so we just have to be very careful how we use it.

Anyway, looking at the results in my table above, I need to go back through all my Date and Calendar code and make sure I've made the right use of all these functions, and I'm starting to do that now.

Is the issue that you're

mfb's picture

Is the issue that you're storing dates in local server time? It would be preferable to store dates in GMT, this also makes the site and database more portable between servers.

In my testing at least the latest versions of PHP4 and PHP5 have identical behavior of gmmktime() with no arguments. You do have to be careful how you use both mktime and gmmktime, for instance some months don't have certain days (february) and some days don't have certain hours (when clocks are set forward) so weird things can happen if you don't supply a valid time.

I'm trying to store gmt, not

karens's picture

I'm trying to store gmt, not local time, so any time that didn't happen it was a mistake that needs to be corrected.

In my testing at least the latest versions of PHP4 and PHP5 have identical behavior of gmmktime() with no arguments.

But in my testing they don't, as you can see above. Anyway, results are not consistent across systems and versions, which is my point.

OK I did find a difference

mfb's picture

OK I did find a difference between the latest versions of PHP4 and PHP5, if my server environment is not UTC, if values are not passed in to gmmktime(). In this case you need to use time(). To get a GMT date in some format, given any unix timestamp, use gmdate(). Hopefully gmmktime() does work the same everywhere when at least one valid parameter is passed in...

gmmktime() doesn't work the

karens's picture

gmmktime() doesn't work the same in all versions even when you pass in valid parameters. One problem noted was that if you don't pass in the dst parameter (the last optional parameter that practically no one uses) you can have dst errors in some versions. There were bug report and comments on the php.net site reporting this. So a good rule of thumb may be to just avoid gmmktime() whenever possible.

Some results when parameters are given

emok's picture

I know the discussion is old, but I thought this info could fit here.
The following was run with the date_php4 module, when server date is 2008-07-18T19:55:15 +7200. Timezone is Europe/Stockholm with DST (daylight saving time). I fear some results may differ if run with the server clock on standard time (during winter).

PHP-version                 |    4.3.11  |    5.2.3   |
# A date during summer:
gmmktime(0,0,0,6,1,2008, 0) | 1212282000 | 1212278400 | # +3600 seconds in PHP4, completely wrong!
gmmktime(0,0,0,6,1,2008   ) | 1212278400 | 1212278400 | #same
gmmktime(0,0,0,6,1,2008, 1) | 1212278400 | 1212274800 | # +3600 seconds in PHP4, as when standard time in PHP5
# A date during winter:
gmmktime(0,0,0,2,1,2008, 0) | 1201824000 | 1201824000 | same
gmmktime(0,0,0,2,1,2008   ) | 1201824000 | 1201824000 | same
gmmktime(0,0,0,2,1,2008, 1) | 1201820400 | 1201820400 | same
# PHP4 differs from PHP5, as already noted by others, when no arguments are given:
time()                      | 1216403715 | 1216403715
gmmktime()                  | 1216410915 | 1216403715
PHP API                     |   20020918 |   20041225
PHP Extension               |   20020429 |   20060613
Zend Extension              |   20021010 |  220060519
System:    Linux 2.6.11-1.35_FC3 #1 i686 | FreeBSD 6.2-RELEASE FreeBSD 6.2-RELEASE #0 i386

For the summer-date where the versions differ, I believe PHP5 is correct. The is_dst-parameter (0, 1 or omitted) tells how to treat input (standard time or DST), while timestamps always can be thought of as using standard time.

With mktime and the parameters used above, there is no difference between PHP4 and PHP5.

I got "off by +1 hour"-errors when viewing datetimes since date_php4.inc. When I changed line 888 (July 10, 2008) into

<?php
   
if (gmmktime(0,0,0,6,1,2008, 0) == 1212282000) {
     
//Seems to be running PHP (like 4.3.11).
      //At least if current local timezone is Europe/Stockholm with DST in effect, skipping the ,0 helps:
     
return @gmmktime($hr, $min, $sec, $mon, $day, $year); //without is_dst-parameter at the end
   
}
    return @
gmmktime($hr, $min, $sec, $mon, $day, $year, 0); // original line 888
?>

the problems were solved. I am however not sure if the check always works as intended, and if it will work during winter.
I'll probably report an issue about this later, but I feel very uncertain whether my change would work everywhere.

Event Management Systems

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds: