Timezones are fickle

I’ve been trying to work out a system to be able to cleanly switch between IST (Israel Standard Time, GMT+2:00) and IDT (Israel Daylight savings Time, GMT+3:00) on command. The logical way to do this, in my opinion, is to have two separate files in /usr/share/zoneinfo, say IsraelIST and IsraelIDT, and copy (or link) the relevant one as /etc/localtime. The trick is creating the IsraelIDT file:
My first guess was the following zic source-file:

# Zone    NAME                GMTOFF  RULES/SAVE  FORMAT  [UNTIL]
Zone      IsraelIDT           2:00    01:00       IDT

Now, this almost works. The problem is that both is_dst is set and timezone = -10800 (3 hours – should be 2, as it should represent local standard time), so some software double-compensates here for a grand total of GMT+4:00. After some research (walking through __tzfile_read gave the biggest hint), it turns out that timezone is set according to the minimal local time type which is transitioned into. So I came up with this file:

# Rule  NAME    FROM  TO    TYPE  IN   ON       AT    SAVE  LETTER/S
Rule    ZionIDT min   1939  -     Jan  1        00:00 1:00  D
Rule    ZionIDT 1939  only  -     Jan  1        00:00 0:00  S
Rule    ZionIDT 1940  max   -     Jan  1        00:00 1:00  D

# Zone    NAME                GMTOFF  RULES/SAVE  FORMAT  [UNTIL]
Zone      IsraelIDT           2:00    ZionIDT     I%sT

Sounds about right, nay? Even my handy little pyzdump confirms that it looks about how I want it to:

./pyzdump.py /usr/share/zoneinfo/IsraelIDT
Transitions:
['At Sat Dec 31 23:00:00 1938, switch to IST',
 'At Sun Dec 31 22:00:00 1939, switch to IDT']
Types:
[<TZType IDT: UTC+10800 dst=True>, <TZType IST: UTC+7200 dst=False>]

(pardon the lack of syntax highlighting – my prettyprinter fails me)

However, it still doesn’t work. A test program:

int main() {
    tzset();
    time_t t = time(NULL);
    printf("Timezone name is %s, timezone=%ld\n", __tzname[1], timezone);
    printf("The time is %s", ctime(&t));
    printf("Timezone name is %s, timezone=%ld\n", __tzname[1], timezone);
    return 0;
}

And its results, as run at 14:42:17 UTC, which is 19:42:17 IDT:

Timezone name is IDT, timezone=-7200
The time is Sat Apr 18 14:42:17 2009
Timezone name is UTC, timezone=0

Or, as I described it to a friend:

Me: Hi computer, do you know what timezone are we in?
Computer: Yeah, it’s Israel Daylight Savings time, GMT+2:00 for standard time.
Me: OK, and what time is it?
Computer: 14:42
Me: No, that’s 3 hours late. What timezone are we in?
Computer: Umm… UTC?
Me: You just said IDT.
Computer: Nuh-uh.

I’ll get to the bottom of this eventually :/

Addendum: It seems that the problem is even more complicated. For the following timezone file, C programs seem to work fine:

# Rule  NAME    FROM  TO    TYPE  IN   ON       AT    SAVE  LETTER/S
Rule    ZionIDT min   1939  -     Jan  1        00:00 1:00  D
Rule    ZionIDT 1939  only  -     Jan  1        00:00 0:00  S
Rule    ZionIDT 1940  2030  -     Jan  1        00:00 1:00  D
Rule    ZionIDT 2030  max   -     Jan  1        00:00 0:00  S

# Zone    NAME                GMTOFF  RULES/SAVE  FORMAT  [UNTIL]
Zone      IsraelIDT           2:00    ZionIDT     I%sT

However, Python programs still show timezone = -10800. Examining Python’s code, I found this:

        if( janzone < julyzone ) {
            /* DST is reversed in the southern hemisphere */
            PyModule_AddIntConstant(m, "timezone", julyzone);
            PyModule_AddIntConstant(m, "altzone", janzone);
            PyModule_AddIntConstant(m, "daylight",
                        janzone != julyzone);
            PyModule_AddObject(m, "tzname",
                       Py_BuildValue("(zz)",
                             julyname, janname));
        } else {
            PyModule_AddIntConstant(m, "timezone", janzone);
            PyModule_AddIntConstant(m, "altzone", julyzone);
            PyModule_AddIntConstant(m, "daylight",
                        janzone != julyzone);
            PyModule_AddObject(m, "tzname",
                       Py_BuildValue("(zz)",
                             janname, julyname));
        }

And since June and July have the same timezone in our case, there's a good chance that this is what's going wrong.

The moral of the story seems to be this - I should go with the first, simplest "always-DST" solution. Programs should ignore the timezone variable, as in our context it isn't reliable. In general, all internal time handling should be done in UTC; when reading times from the outside world, if they are in local time - use mktime. If they are in a specified timezone, use timegm and compensate manually. I'd love to hear better ideas in the comments.

Saturday, April 18th, 2009 Asides

5 Comments to Timezones are fickle

  1. Good luck with that, Haboob! ;)

  2. Roberto on April 18th, 2009
  3. I don’t get it. Why do you need two timezone files. The Asia/Tel_Aviv time zone automatically adjusts for DST. No changes needed on behalf of the user.

    Also, storing all data in UTC sounds right, until a country decides to change its timezone rules. Then, suddenly your 9am meeting appears to be at 10am, since it was pinned to UTC and not local time. Time with timezone is a partial solution (that is, keep local time, UTC offset, and timezone).

  4. Alon Altman on April 18th, 2009
  5. I state (and now added emphasis to the fact) that I want to be able to toggle DST on command. Some organizations don’t switch to DST the same time as everyone else ;) (oddly enough, some of those organizations don’t just go ahead and use Zulu time…).

    Recurring events are indeed an interesting pitfall that needs to be taken into consideration here, thanks!

  6. Ohad Lutzky on April 18th, 2009
  7. I don’t understand the first statement:
    “… IST (Israel Standard Time, GMT+2:00) and IDT (Israel Daylight savings Time, GMT+3:00) …”

    GMT (afaik) is UTC + DST, so whenever one refers to GMT, daylight saving time is automatically applied, unlike UTC.
    So Israel’s time zone is always GMT+2:00 (except that it is meaningless since the GMT DST is different than Israel’s DST), but can be UTC+2:00 or UTC+3:00…

    Am I wrong?

  8. Itai Bar-Haim on October 31st, 2009
  9. Confusing, isn’t it? According to every source I’ve found, GMT is either UTC or UT1, which are at most 0.9 apart (the difference is in leap seconds). GMT does not include daylight savings time – for example, the timezone in the UK is GMT during the winter and BST (which is UTC+1) during summer.

    It’s exactly these subtle terminological misconceptions that cause so many timezone-related software bugs.

  10. Ohad Lutzky on November 1st, 2009

Leave a comment