When an application reports the wrong UTC offset, read the compiled Linux TZif file's binary header before blaming the zone data. This guide checks the header and the resulting rules with zdump, without changing the file. The examples use Ubuntu's tzdata package version 2026c-0ubuntu0.24.04.1 and glibc zdump 2.39, but the TZif layout is a shared format.
Allow about fifteen minutes. You need a shell, a readable file below /usr/share/zoneinfo, and the xxd and zdump commands. All commands here are read-only. Do not open a zone file in an editor or overwrite it: a damaged file can make applications calculate local time incorrectly until the package is repaired.
Zone names are identifiers such as Europe/London and America/New_York. They normally map to files under /usr/share/zoneinfo. Start by checking the path and file type:
$ file /usr/share/zoneinfo/Europe/London
/usr/share/zoneinfo/Europe/London: timezone data (fat), version 2, 8 gmt time flags, 8 std time flags, no leap seconds, 242 transition times, 8 local time types, 17 abbreviation chars
That output is a useful first checkpoint, but file is not the format specification: it reports facts it recognises in this particular file. If the path is a symbolic link, follow it only as far as needed to identify the installed data; do not assume every zone has the same size or transition count.
Checkpoint: the requested zone should exist and be readable. If it does not, list nearby names with find /usr/share/zoneinfo -type f or check the spelling and capitalisation. Avoid treating files such as posix, right and leap-seconds.list as interchangeable with an ordinary geographic zone.
A TZif file begins with a 44-byte header. The first four bytes must be the ASCII magic value TZif. The next byte is the format version: a NUL for version 1, or 2, 3 or 4 for later formats. The remaining header contains fifteen reserved zero bytes and six four-byte counts.
Inspect exactly those first 44 bytes:
$ xxd -g1 -l 44 /usr/share/zoneinfo/Europe/London
00000000: 54 5a 69 66 32 00 00 00 00 00 00 00 00 00 00 00 TZif2...........
00000010: 00 00 00 00 00 00 00 08 00 00 00 08 00 00 00 00 ................
00000020: 00 00 00 f2 00 00 00 08 00 00 00 11 ............
Here, 54 5a 69 66 is TZif, and the following 32 is the ASCII character 2. The six counts are stored as big-endian, unsigned four-byte values at offsets 20, 24, 28, 32, 36 and 40:
tzh_ttisutcnt: UT/local indicators.tzh_ttisstdcnt: standard/wall-clock indicators.tzh_leapcnt: leap-second records.tzh_timecnt: transition times.tzh_typecnt: local time types.tzh_charcnt: abbreviation bytes.For this file, the last six values are 8, 8, 0, 242, 8 and 17. Do not read them as little-endian integers: the format stores multi-byte integers in network byte order, so reversing the bytes produces convincing but wrong counts.
After the header comes a variable-length data block. It contains the transition timestamps, one-byte indexes selecting a local time type, the type records themselves, abbreviation strings, optional leap-second pairs, and the two kinds of indicators. A type record contains a signed UTC offset in seconds, a daylight-saving flag, and an index into the abbreviation strings.
A transition is not a list of every clock reading, it marks the instant at which the rules change. For example, a transition can switch London from offset zero and GMT to offset 3,600 and BST. Before the first transition, localtime normally uses the first type in the file, which matters when testing historical timestamps.
Version 2 and later files contain the older version 1 header and block first, then a second header and block with eight-byte transition times, finishing with a newline-enclosed POSIX-style TZ string. The duplicate block is for compatibility with old readers; a modern reader should use the later data. Version 1 cannot represent transitions beyond the 2038 boundary, so it is a legacy format rather than a good target for new writers.
Inspect the printable strings at the end as a quick confirmation, not as a parser:
$ strings -a /usr/share/zoneinfo/Europe/London | tail -n 3
TZif2
BDST
GMT0BST,M3.5.0/1,M10.5.0
The final line is the future-rule footer for this installed file, used for instants after the last stored transition. A footer is not present in a version 1 file, and an empty footer is allowed when no POSIX representation is available.
Use zdump to test the rules through the normal system interface rather than trying to calculate offsets from the bytes yourself:
$ zdump -v -c 2026,2027 Europe/London
Europe/London Sun Mar 29 00:59:59 2026 UT = Sun Mar 29 00:59:59 2026 GMT isdst=0 gmtoff=0
Europe/London Sun Mar 29 01:00:00 2026 UT = Sun Mar 29 02:00:00 2026 BST isdst=1 gmtoff=3600
Europe/London Sun Oct 25 00:59:59 2026 UT = Sun Oct 25 01:59:59 2026 BST isdst=1 gmtoff=3600
Europe/London Sun Oct 25 01:00:00 2026 UT = Sun Oct 25 01:00:00 2026 GMT isdst=0 gmtoff=0
The -v option prints detailed transition information. The -c 2026,2027 limit keeps the output focused on the years from 2026 up to, but not including, 2027. Compare the reported offset and abbreviation with the question you are investigating. The command reads the named zone from the system zoneinfo directory; it does not modify it.
For a different file, pass its zone name rather than a path:
$ zdump -v -c 2026,2027 America/New_York
Tip: if an application disagrees with zdump, check the application's timezone setting and data source before replacing system files. Many programmes use a bundled database or an explicit TZ setting, so the TZif file may be correct while the application is reading another copy.
A file that lacks the TZif magic, has impossible counts, or ends before the lengths implied by its headers should be treated as invalid.
Warning: do not repair a suspect file by changing bytes in place. Compare the file with the package's expected contents and reinstall or update the owning package using your normal change process. That action needs elevated privileges and may affect applications that read the file, so schedule it as an operational change.
Remember that a valid file can still contain historical oddities. Offsets need not be whole hours, abbreviations are data rather than proof of a legal time-zone name, and leap-second information may be absent. The installed tzdata version controls the data you observe. Record that version when filing a bug or comparing two machines:
$ dpkg-query -W -f='${Package} ${Version}
' tzdata
tzdata 2026c-0ubuntu0.24.04.1
/usr/share/zoneinfo.TZif, and the version byte is understood.zdump confirms the offset and transitions relevant to the incident.tzdata version.