Skip to content
AZ Tools

TZif Zoneinfo File Inspector

A TZif file is the compiled form of a time zone: the thing at /usr/share/zoneinfo/Europe/Berlin, the thing TZ= points at, the thing shipped inside a JDK or copied into a container image. It is opaque, it has no name inside it, and people move it around without any way to check it. This reader opens one in the browser and prints what is actually there, per RFC 8536. The header gives the version and the six counts, and it shows them for both blocks side by side, because a version 2 or later file contains a complete 32-bit block first and a 64-bit block after it. The first is a legacy stub that every modern reader skips; on a slim file it has been reduced to a single type and no transitions at all, while the real data sits in the second block. The local time type table gives each offset, its DST flag, its abbreviation, and the standard/wall and UT/local indicators that say how the tz source expressed the rule that produced it. The transition table lists every switch with the offset before and after, the abbreviation that takes over and whether it enters DST; transition times are signed 64-bit seconds, so files legitimately contain 1901 and 2037 dates and one or two sentinel entries that change nothing. The POSIX footer is parsed into fields rather than echoed: standard and daylight abbreviations, both offsets with the POSIX sign flipped back to the normal one, and the start and end rules in M, J or zero-based form, resolved to concrete instants for the reference year shown. That footer is the whole point of a slim file, where nothing else describes next spring. Leap-second records are listed when a file carries them, which only the right/ tree does. What it cannot do is tell you which zone this is — no TZif file stores its own name, and an alias like Asia/Istanbul is a byte-for-byte copy of Europe/Istanbul — and it cannot detect corruption, because the format has no checksum: a flipped byte in the middle of the table simply moves a transition.

How to use

  1. Drop a compiled zone file on the box — copy one out of /usr/share/zoneinfo, out of a container with docker cp, or out of a JDK.
  2. Read the summary first: the version, how many transitions the file holds, and whether the future is enumerated (fat), left to the POSIX rule (slim) or fixed for ever.
  3. Compare the two header columns. A slim file shows a nearly empty 32-bit block next to a full 64-bit one; that difference is the format working as designed, not damage.
  4. Check the reference instant and the next transition to see what this file would do today and when it changes next.
  5. Open the POSIX footer section for a slim file: the M, J or zero-based rules there are the only thing that governs any date past the last transition.

Frequently asked questions

Why does the header show two sets of counts?
Because a version 2 or later file really does contain the data twice. It opens with a complete version 1 block whose transition times are 32-bit, and only then comes a second header and a 64-bit block, followed by the POSIX footer. The duplication exists so that a reader written before 2004 finds a file it understands instead of failing, and RFC 8536 tells modern readers to skip straight to the second block. That is why the counts may differ: with zic's slim output the first block is cut down to a single local time type and no transitions at all, while the second one holds the real data. If a tool ever reports a zone as having no history, it is usually reading the legacy block by mistake — which some libraries still do.
What is the difference between a fat and a slim zone file?
Both describe the same zone; they differ in how much of the future is written out. A fat file enumerates every transition up to 2037, so Europe/Berlin lists roughly 140 of them and you can read next October's change straight from the table. A slim file stops after the last transition that cannot be derived from a rule and leaves everything after it to the POSIX footer string, which is why it can be a tenth of the size. Since 2020 zic defaults to slim, but distributions differ: Debian and Ubuntu still ship fat files, Alpine and several container base images ship slim ones. A reader that ignores the footer works fine on a fat file and silently gets next year wrong on a slim one, which is exactly the kind of bug that shows up in one image and not another.
How do I read the POSIX footer, and what do M, J and the bare number mean?
The footer is the same string a TZ variable can hold: standard abbreviation, standard offset, then optionally the daylight abbreviation, its offset and the two rules that switch between them. Its offsets are written the POSIX way round, positive west of Greenwich, so CET-1 means UTC+01:00 and this tool flips the sign back. A rule can take three forms. Mm.w.d is a month, a week (5 meaning the last one) and a weekday, so M3.5.0 is the last Sunday in March. Jn is the nth day of the year counting from 1 and never counting 29 February, so J60 is always 1 March. A bare n counts from 0 and does count 29 February, so 59 is 1 March in a common year and 29 February in a leap year. The bare form is rare and worth avoiding: implementations disagree about it, and Python's zoneinfo resolves it one day later than glibc does.
Why does Etc/GMT+5 report an offset of -05:00?
Because the Etc names follow the POSIX sign convention, which is the opposite of the one everybody else uses: in a TZ string, positive means west of Greenwich. So Etc/GMT+5 is the zone five hours behind UTC, which is written -05:00 in ISO 8601 and in every user interface, and Etc/GMT-5 is five hours ahead. The tz database keeps these names precisely because POSIX requires that behaviour, and warns that they are a trap. This tool reports offsets the ISO way, so a file whose name says +5 and whose offset says -05:00 is not a contradiction and not a bug — it is the naming convention showing through.
What are the leap-second records, and why do the transition times look wrong in a right/ file?
The tz database can be compiled twice: the ordinary posix/ tree, where a timestamp is a plain count of non-leap seconds, and the right/ tree, where the leap seconds since 1972 are stored as records and timestamps count real elapsed seconds. Most systems only ship the first, so a leap-second table here means you are holding a right/ file, or one someone compiled with zic -L. In such a file the transitions are shifted by the accumulated correction — a switch that should read 01:00:00 reads 01:00:27, because 27 leap seconds have been inserted since 1972 — and the correction column counts up to that same 27. Mixing a right/ file into a system expecting posix/ times is a classic way to be exactly 27 seconds off.
Can it tell me which zone this file is, or whether the file is corrupt?
No to both, and both limits are properties of the format. A TZif file contains no name: the identity of a zone comes entirely from its path in the database, which is why a link such as Asia/Istanbul is a byte-for-byte copy of, or a symlink to, Europe/Istanbul and cannot be distinguished from it by content. The best you can do is recognise a zone by its fingerprint — the abbreviations, the offsets and the dates of the transitions — which is what the tables here are for. There is also no checksum anywhere in the format, so a flipped byte in the transition table does not make the file invalid, it just silently moves a transition or repoints it at a different type. Only a length that no longer matches the counts is detectable, and that is what this tool refuses as truncated.

Related tools