The IANA Time Zone Database (and Java)

07 May 2023

The IANA Time Zone Database, (or TZDB), in its own words:

“contains code and data that represent the history of local time for many representative locations around the globe. It is updated periodically to reflect changes made by political bodies to time zone boundaries, UTC offsets, and daylight-saving rules.”

One small example from the TZDB is the following “zone rule” entry from the Asia data file:

1
2
3
4
5
6
7
8
9
# Cambodia
# Christmas I
# Laos
# Thailand
# Vietnam (northern)
# Zone   NAME           STDOFF   RULES   FORMAT	[UNTIL]
Zone	 Asia/Bangkok   6:42:04  -       LMT    1880
                        6:42:04  -       BMT    1920 Apr # Bangkok Mean Time
                        7:00     -       +07

This indicates that an adjustment was made to the listed zones on 1st April 1920. The adjustment brought those zones into line with GMT offsets (which later became UTC offsets).

Java uses this database for its java.time classes. You can inspect the rules by using ZoneRulesProvider and related classes.

For example:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private static void reportTimeZoneRules() {

    String timeZoneName = "Asia/Bangkok";

    ZoneRules zoneRules = ZoneRulesProvider.getRules(timeZoneName, false);
    List<ZoneOffsetTransition> historicTransitions = zoneRules.getTransitions();

    System.out.println("\nIn " + timeZoneName + ":\n");

    historicTransitions.forEach(transition -> {
        LocalDateTime when = transition.getDateTimeBefore();
        Duration dur = transition.getDuration();
        String move = dur.isNegative() ? "moved back" : "moved forward";
        ZoneOffset before = transition.getOffsetBefore();
        ZoneOffset after = transition.getOffsetAfter();
        boolean isDST = zoneRules.isDaylightSavings(transition.getInstant());

        System.out.println(" - on " + when.toLocalDate() + " at " + when.toLocalTime());
        System.out.println(" - the clocks " + move + " by " + durationDescr(dur)
                + (isDST ? " (daylight saving)" : ""));
        System.out.println(" - from TZ offset " + before + " to offset " + after);
        System.out.println();

        ZoneOffsetTransition nextTransition = zoneRules.nextTransition(transition.getInstant());
        if (nextTransition != null) {
            long gap = nextTransition.getInstant().toEpochMilli() - transition.getInstant().toEpochMilli();
            if (gap > 1000L * 60L * 60L * 24L * 365L) {
                System.out.println(" *** no changes for a while ***");
                System.out.println();
            }
        }

    });
}

The above code handles historic zone changes. Java can also show future planned zone changes using ZoneOffsetTransitionRule (not used here).

The above code uses a small helper class to describe a duration, in words:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
private static String durationDescr(Duration dur) {
    long days = Math.abs(dur.toDaysPart());
    long hours = Math.abs(dur.toHoursPart());
    long mins = Math.abs(dur.toMinutesPart());
    long secs = Math.abs(dur.toSecondsPart());

    StringBuilder sb = new StringBuilder();
    if (days != 0) {
        sb.append(days).append(days == 1 ? " day " : " days ");
    }
    if (hours != 0) {
        sb.append(hours).append(hours == 1 ? " hr " : " hrs ");
    }
    if (mins != 0) {
        sb.append(mins).append(mins == 1 ? " min " : " mins ");
    }
    if (secs != 0) {
        sb.append(secs).append(secs == 1 ? " sec " : " secs ");
    }
    return sb.toString().trim();
}

An example output from the above code (for the same TZDB example we started with) is:

1
2
3
4
5
In Asia/Bangkok:

 - on 1920-04-01 at 00:00
 - the clocks moved forward by 17 mins 56 secs
 - from TZ offset +06:42:04 to offset +07:00

In this particular case, this is the only entry in the TZDB for the Asia/Bangkok zone.

But other zones will have much longer lists of historic (and future planned) zone change events.

A small subset from the America/Denver listing is shown below. It shows various changes, starting with an adjustment of 4 seconds in November 1883, followed by various changes related to the adoption (and abandonment) of daylight saving time.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
In America/Denver:

- on 1883-11-18 at 12:00:04
- the clocks moved back by 4 secs
- from TZ offset -06:59:56 to offset -07:00

*** no changes for a while ***

- on 1918-03-31 at 02:00
- the clocks moved forward by 1 hr (daylight saving)
- from TZ offset -07:00 to offset -06:00

- on 1918-10-27 at 02:00
- the clocks moved back by 1 hr
- from TZ offset -06:00 to offset -07:00

- on 1919-03-30 at 02:00
- the clocks moved forward by 1 hr (daylight saving)
- from TZ offset -07:00 to offset -06:00

- on 1919-10-26 at 02:00
- the clocks moved back by 1 hr
- from TZ offset -06:00 to offset -07:00

- on 1920-03-28 at 02:00
- the clocks moved forward by 1 hr (daylight saving)
- from TZ offset -07:00 to offset -06:00

- on 1920-10-31 at 02:00
- the clocks moved back by 1 hr
- from TZ offset -06:00 to offset -07:00

- on 1921-03-27 at 02:00
- the clocks moved forward by 1 hr (daylight saving)
- from TZ offset -07:00 to offset -06:00

- on 1921-05-22 at 02:00
- the clocks moved back by 1 hr
- from TZ offset -06:00 to offset -07:00

*** no changes for a while ***

- on 1942-02-09 at 02:00
- the clocks moved forward by 1 hr (daylight saving)
- from TZ offset -07:00 to offset -06:00

*** no changes for a while ***

- on 1945-09-30 at 02:00
- the clocks moved back by 1 hr
- from TZ offset -06:00 to offset -07:00

*** no changes for a while ***

- on 1965-04-25 at 02:00
- the clocks moved forward by 1 hr (daylight saving)
- from TZ offset -07:00 to offset -06:00

- on 1965-10-31 at 02:00
- the clocks moved back by 1 hr
- from TZ offset -06:00 to offset -07:00