OCImendby CloudTrace

How to check the time-zone data of Go, Java, Node.js and Python (tzdata 2026e, part 2)

OCImend · 2026-10-14 · news

Short answer

Each runtime reads its own copy: the C library and Python's zoneinfo use /usr/share/zoneinfo, Go uses $ZONEINFO, the system copy, $GOROOT/lib/time/zoneinfo.zip, then data compiled in with time/tzdata, Java uses the JDK's tzdb.dat, Node.js its built-in ICU data (process.versions.tz), and pytz its bundled copy (pytz.OLSON_VERSION). Check each by asking for a real offset (Winnipeg on 2026-11-02 must be UTC−05:00 with 2026e), or run ocimend tzcheck, which finds and tests every copy on a host or in a container.

One command for all of it

ocimend tzcheck finds every copy of the time-zone database on a host or inside a container and asks each one for real offsets: Vancouver, Edmonton, Inuvik and Winnipeg on 2 November 2026, Casablanca on 15 October. Version labels can lie (a backported package, a JDK patched with an updater, a Go program with its own copy); the offsets cannot. It needs no account and sends nothing anywhere.

curl -fsSL https://ocimend.io/downloads/agent/install.sh | sh     # Linux and macOS, checksum verified
ocimend tzcheck                      # this machine: system data, JDKs, node, python, Go programs on PATH
ocimend tzcheck /srv /app            # also look for Go programs in these directories
ocimend tzcheck --json               # for scripts and inventories
ocimend tzcheck --strict             # exit 1 when a copy in use is stale: a CI or fleet gate

On a machine with tzdata 2025b, a Go program built with time/tzdata, OpenJDK 21, Node.js 22 and Python 3.11 with the tzdata and pytz packages (every value below is real output):

$ ocimend tzcheck /srv/billing
ocimend tzcheck · vm · linux/amd64 · newest tz release known: 2026e

STALE  system          /usr/share/zoneinfo  [2025b]
       America/Vancouver  2026-11-02  −08:00  expected −07:00  WRONG (old rules)
       America/Edmonton   2026-11-02  −07:00  expected −06:00  WRONG (old rules)
       Africa/Casablanca  2026-10-15  +01:00  expected +00:00  WRONG (old rules)
       America/Inuvik     2026-11-02  −07:00  expected −06:00  WRONG (old rules)
       America/Winnipeg   2026-11-02  −06:00  expected −05:00  WRONG (old rules)
       misses: British Columbia, Alberta, Morocco, Northwest Territories (Inuvik), Manitoba
       used by: the C library (date, cron, most daemons), Go and Python's zoneinfo when present, most databases

STALE  go-toolchain    /usr/local/go1.24.7/lib/time/zoneinfo.zip  [no version label]
       America/Vancouver  2026-11-02  −08:00  expected −07:00  WRONG (old rules)
       America/Edmonton   2026-11-02  −07:00  expected −06:00  WRONG (old rules)
       Africa/Casablanca  2026-10-15  +01:00  expected +00:00  WRONG (old rules)
       America/Inuvik     2026-11-02  −07:00  expected −06:00  WRONG (old rules)
       America/Winnipeg   2026-11-02  −06:00  expected −05:00  WRONG (old rules)
       misses: British Columbia, Alberta, Morocco, Northwest Territories (Inuvik), Manitoba
       used by: Go programs on this machine when there is no system copy; and what time/tzdata embeds in programs built here

STALE  java            /usr/lib/jvm/java-21-openjdk-amd64/lib/tzdb.dat  [2025b]
       misses: British Columbia, Alberta, Morocco, Northwest Territories (Inuvik), Manitoba
       used by: java.time and java.util.TimeZone in this JDK

STALE  node            /opt/node22/bin/node  [2025c]
       America/Vancouver  2026-11-02  −08:00  expected −07:00  WRONG (old rules)
       America/Edmonton   2026-11-02  −07:00  expected −06:00  WRONG (old rules)
       Africa/Casablanca  2026-10-15  +01:00  expected +00:00  WRONG (old rules)
       America/Inuvik     2026-11-02  −07:00  expected −06:00  WRONG (old rules)
       America/Winnipeg   2026-11-02  −06:00  expected −05:00  WRONG (old rules)
       misses: British Columbia, Alberta, Morocco, Northwest Territories (Inuvik), Manitoba
       ICU 78.2
       used by: Intl, Date#toLocaleString and libraries built on them, in this Node.js

STALE  python-zoneinfo /opt/app-venv/bin/python3 (Python 3.11.15)  [no version label]
       America/Vancouver  2026-11-02  −08:00  expected −07:00  WRONG (old rules)
       America/Edmonton   2026-11-02  −07:00  expected −06:00  WRONG (old rules)
       Africa/Casablanca  2026-10-15  +01:00  expected +00:00  WRONG (old rules)
       America/Inuvik     2026-11-02  −07:00  expected −06:00  WRONG (old rules)
       America/Winnipeg   2026-11-02  −06:00  expected −05:00  WRONG (old rules)
       misses: British Columbia, Alberta, Morocco, Northwest Territories (Inuvik), Manitoba
       tzdata package 2025.2 installed (used only when the system copy is missing)
       used by: zoneinfo.ZoneInfo: the system copy on /usr/share/zoneinfo:/usr/lib/zoneinfo:/usr/share/lib/zoneinfo:/etc/zoneinfo, else the tzdata package

STALE  pytz            /opt/app-venv/bin/python3  [2025b]
       America/Vancouver  2026-11-02  −08:00  expected −07:00  WRONG (old rules)
       America/Edmonton   2026-11-02  −07:00  expected −06:00  WRONG (old rules)
       Africa/Casablanca  2026-10-15  +01:00  expected +00:00  WRONG (old rules)
       America/Inuvik     2026-11-02  −07:00  expected −06:00  WRONG (old rules)
       America/Winnipeg   2026-11-02  −06:00  expected −05:00  WRONG (old rules)
       misses: British Columbia, Alberta, Morocco, Northwest Territories (Inuvik), Manitoba
       pytz 2025.2
       used by: pytz.timezone(): pytz's own bundled copy, never the system's

Go programs (1)
  /srv/billing/billing-api  (go1.24.7)  uses: system (/usr/share/zoneinfo)
    STALE  go-embedded     /srv/billing/billing-api  [no version label]
           America/Vancouver  2026-11-02  −08:00  expected −07:00  WRONG (old rules)
           America/Edmonton   2026-11-02  −07:00  expected −06:00  WRONG (old rules)
           Africa/Casablanca  2026-10-15  +01:00  expected +00:00  WRONG (old rules)
           America/Inuvik     2026-11-02  −07:00  expected −06:00  WRONG (old rules)
           America/Winnipeg   2026-11-02  −06:00  expected −05:00  WRONG (old rules)
           misses: British Columbia, Alberta, Morocco, Northwest Territories (Inuvik), Manitoba

6 copies in use miss 2026 changes: local times in those regions will be wrong. Fixes: https://ocimend.io/blog/check-time-zone-data-go-java-node-python

The same machine with the system data replaced by 2026e (compiled from the IANA source) and nothing else changed: the system copy passes, the Go program now reads it (its own embedded copy is still old, but no longer used), and the JDK still fails, so --strict exits 1.

$ ZONEINFO=/opt/tz-2026e ocimend tzcheck --no-exec --strict /srv/billing
ocimend tzcheck · vm · linux/amd64 · newest tz release known: 2026e

OK     system          /opt/tz-2026e  [2026e]
       America/Vancouver  2026-11-02  −07:00  expected −07:00  ok
       America/Edmonton   2026-11-02  −06:00  expected −06:00  ok
       Africa/Casablanca  2026-10-15  +00:00  expected +00:00  ok
       America/Inuvik     2026-11-02  −06:00  expected −06:00  ok
       America/Winnipeg   2026-11-02  −05:00  expected −05:00  ok
       used by: the C library (date, cron, most daemons), Go and Python's zoneinfo when present, most databases

STALE  java            /usr/lib/jvm/java-21-openjdk-amd64/lib/tzdb.dat  [2025b]
       misses: British Columbia, Alberta, Morocco, Northwest Territories (Inuvik), Manitoba
       used by: java.time and java.util.TimeZone in this JDK

Go programs (1)
  /srv/billing/billing-api  (go1.24.7)  uses: $ZONEINFO (/opt/tz-2026e)
    STALE  go-embedded     /srv/billing/billing-api  [no version label]
           America/Vancouver  2026-11-02  −08:00  expected −07:00  WRONG (old rules)
           America/Edmonton   2026-11-02  −07:00  expected −06:00  WRONG (old rules)
           Africa/Casablanca  2026-10-15  +01:00  expected +00:00  WRONG (old rules)
           America/Inuvik     2026-11-02  −07:00  expected −06:00  WRONG (old rules)
           America/Winnipeg   2026-11-02  −06:00  expected −05:00  WRONG (old rules)
           misses: British Columbia, Alberta, Morocco, Northwest Territories (Inuvik), Manitoba

1 copy in use misses 2026 changes: local times in those regions will be wrong. Fixes: https://ocimend.io/blog/check-time-zone-data-go-java-node-python
exit 1

Who reads which copy

RuntimeWhere its time-zone data comes fromHow to see the versionHow to fix
C library, shell, cron, most daemons/usr/share/zoneinfo (or $TZDIR)head -1 /usr/share/zoneinfo/tzdata.ziupgrade the tzdata package; restart long-running services
Go$ZONEINFO → system zoneinfo → $GOROOT/lib/time/zoneinfo.zip → its own copy (time/tzdata)not visible in go version -m: ask the program, or ocimend tzchecksystem tzdata in the image, or rebuild with a Go release whose data is 2026e
Javathe JDK's lib/tzdb.dat, never the system'sZoneRulesProvider.getVersions("UTC")a JDK update with 2026e, or the vendor's time-zone updater applied to the JDK
Node.jsICU data built into the node binarynode -p process.versions.tza Node.js release with 2026e in its ICU data
Python zoneinfosystem zoneinfo, else the tzdata packagetest an offset (it has no version)upgrade the system tzdata; tzdata ≥ 2026.5 for images without one
Python pytzits own bundled copy, never the system'spytz.OLSON_VERSIONpip install -U pytz (2026.5 = 2026e)

1. The system copy (hosts and images)

$ head -1 /usr/share/zoneinfo/tzdata.zi
# version 2025b
$ dpkg-query -W tzdata
tzdata	2025b-0ubuntu0.24.04.1
$ zdump -v -c 2026,2027 America/Winnipeg
America/Winnipeg  -9223372036854775808 = NULL
America/Winnipeg  -9223372036854689408 = NULL
America/Winnipeg  Sun Mar  8 07:59:59 2026 UT = Sun Mar  8 01:59:59 2026 CST isdst=0 gmtoff=-21600
America/Winnipeg  Sun Mar  8 08:00:00 2026 UT = Sun Mar  8 03:00:00 2026 CDT isdst=1 gmtoff=-18000
America/Winnipeg  Sun Nov  1 06:59:59 2026 UT = Sun Nov  1 01:59:59 2026 CDT isdst=1 gmtoff=-18000
America/Winnipeg  Sun Nov  1 07:00:00 2026 UT = Sun Nov  1 01:00:00 2026 CST isdst=0 gmtoff=-21600
America/Winnipeg  9223372036854689407 = NULL
America/Winnipeg  9223372036854775807 = NULL
$ TZ=America/Winnipeg date -d "2026-11-02 12:00 UTC"
Mon Nov  2 06:00:00 CST 2026
--- with 2026e
$ zdump -v -c 2026,2027 America/Winnipeg
America/Winnipeg  -9223372036854775808 = NULL
America/Winnipeg  -9223372036854689408 = NULL
America/Winnipeg  Sun Mar  8 07:59:59 2026 UT = Sun Mar  8 01:59:59 2026 CST isdst=0 gmtoff=-21600
America/Winnipeg  Sun Mar  8 08:00:00 2026 UT = Sun Mar  8 03:00:00 2026 CDT isdst=1 gmtoff=-18000
America/Winnipeg  Sun Nov  1 06:59:59 2026 UT = Sun Nov  1 01:59:59 2026 CDT isdst=1 gmtoff=-18000
America/Winnipeg  Sun Nov  1 07:00:00 2026 UT = Sun Nov  1 02:00:00 2026 EST isdst=0 gmtoff=-18000
America/Winnipeg  9223372036854689407 = NULL
America/Winnipeg  9223372036854775807 = NULL
$ TZ=America/Winnipeg date -d "2026-11-02 12:00 UTC"
Mon Nov  2 07:00:00 EST 2026

Read the last two transitions: with 2025b, Winnipeg falls back to CST (UTC−06) on 1 November. With 2026e, the same instant is a change of name only (CDT → EST) and the offset stays −05:00. Note the new abbreviations: with 2026e data Vancouver shows MST, Edmonton and Inuvik CST, Winnipeg EST.

Package managers say the same thing: dpkg-query -W tzdata, apk info -v tzdata, rpm -q tzdata. In a container, run them inside the image: the host's package is irrelevant to it.

A trap when compiling the data yourself: the 2026b release notes warn that zic versions 2023d to 2026a, in their default "slim" mode, produce a Vancouver file some readers misread; compile with zic -b fat when your zic is older than 2026b.

2. Go

go version -m shows the toolchain and modules, but not whether the program carries time-zone data or how old it is:

$ go version -m /srv/billing/billing-api
/srv/billing/billing-api: go1.24.7
	path	example.com/billing
	mod	example.com/billing	(devel)	
	build	-buildmode=exe
$ ./billing-api
2026-11-02 06:00 CST -06:00
$ ZONEINFO=/opt/tz-2026e ./billing-api
2026-11-02 07:00 EST -05:00

This program imports time/tzdata, so it carries the data of the toolchain that built it (Go 1.24.7 here, older than 2026e). It still prefers the system copy when one exists, which is why pointing $ZONEINFO at 2026e fixes it without a rebuild. In a scratch or distroless-static image there is no system copy: the embedded data is what runs, and only a rebuild with a newer Go release (or adding /usr/share/zoneinfo to the image) fixes it. Without time/tzdata and without system data, time.LoadLocation fails and code that ignores the error silently uses UTC.

  1. 01$ZONEINFOif set
  2. 02System/usr/share/zoneinfo
  3. 03$GOROOT zipmachines with Go installed
  4. 04time/tzdatacompiled into the program

3. Java

Every JDK carries its own lib/tzdb.dat, whatever the host or image has. A ten-line check (Java 11+ runs a single source file directly):

// TzCheck.java
import java.time.*;
import java.time.zone.ZoneRulesProvider;

public class TzCheck {
    public static void main(String[] a) {
        System.out.println("tzdb version: " + ZoneRulesProvider.getVersions("UTC").lastKey());
        Instant i = Instant.parse("2026-11-02T12:00:00Z");
        for (String z : new String[] {"America/Vancouver", "America/Edmonton", "America/Inuvik", "America/Winnipeg"})
            System.out.println(z + " " + i.atZone(ZoneId.of(z)).getOffset());
    }
}

$ java TzCheck.java
tzdb version: 2025b
America/Vancouver -08:00
America/Edmonton -07:00
America/Inuvik -07:00
America/Winnipeg -06:00

Expected with 2026e: −07:00, −06:00, −06:00, −05:00. ocimend tzcheck reads the version straight from each JDK's tzdb.dat (JAVA_HOME, /usr/lib/jvm, /opt/java and the java on PATH), so it also finds JDKs nothing is running yet. Fix: the JDK update that carries 2026e, or the vendor's time-zone updater tool run against that JDK, then restart the JVMs.

4. Node.js

Node.js formats dates with the ICU library compiled into the node binary, so the system's data does not matter:

$ node -p process.versions.tz
$ node -e 'for (const z of ["America/Vancouver","America/Edmonton","America/Inuvik","America/Winnipeg"])
  console.log(z, new Intl.DateTimeFormat("en-CA", {timeZone: z, timeZoneName: "longOffset", hour: "2-digit", minute: "2-digit"})
    .format(new Date("2026-11-02T12:00:00Z")))'
2025c
America/Vancouver 04:00 a.m. GMT-08:00
America/Edmonton 05:00 a.m. GMT-07:00
America/Inuvik 05:00 a.m. GMT-07:00
America/Winnipeg 06:00 a.m. GMT-06:00

Libraries that format through Intl (date-fns-tz, Luxon, Day.js plugins) inherit this. Libraries that bundle their own data (moment-timezone) have a version of their own to update. Fix: a Node.js release whose process.versions.tz is 2026e or newer.

5. Python

$ python3 -c '
from zoneinfo import ZoneInfo; from datetime import datetime, timezone; import importlib.metadata as m, pytz
at = datetime(2026, 11, 2, 12, tzinfo=timezone.utc)
print("zoneinfo :", at.astimezone(ZoneInfo("America/Winnipeg")).strftime("%Y-%m-%d %H:%M %Z %z"))
print("tzdata pkg:", m.version("tzdata"), "| pytz:", pytz.__version__, "OLSON_VERSION", pytz.OLSON_VERSION)
print("pytz     :", at.astimezone(pytz.timezone("America/Winnipeg")).strftime("%Y-%m-%d %H:%M %Z %z"))'
zoneinfo : 2026-11-02 06:00 CST -0600
tzdata pkg: 2025.2 | pytz: 2025.2 OLSON_VERSION 2025b
pytz     : 2026-11-02 06:00 CST -0600

Two independent copies: zoneinfo uses the system data first (the tzdata package only when there is none, as in slim or distroless images), while pytz always uses its own. Upgrading the OS fixes the first and not the second. The PyPI versions map to releases: 2026.5 is 2026e.

In Kubernetes and containers

The binary is static: copy it into a running container and run it there, where the application's data is.

kubectl cp ./ocimend my-pod:/tmp/ocimend
kubectl exec my-pod -- /tmp/ocimend tzcheck --no-exec /app
# without a live container: scan the image on ocimend.io (every report now shows its time-zone data)

Use --no-exec inside production pods to only read files (it skips starting node or python); the system copy, Go programs and JDKs are still checked.

Check your browser now

Your browser has its own copy too: the online check tests it with the same offsets.

Check an image online →

Questions

Is checking the OS package enough?
No. Java, Node.js, pytz and Go programs built with time/tzdata carry their own copies; each needs its own update.
How do I know if a Go binary embeds time zone data?
go version -m does not show it. ocimend tzcheck finds the embedded copy inside the binary and tests it; the program uses it only when there is no system copy.
Which Python package version is tzdata 2026e?
2026.5 (the minor number counts the letters: a=1 … e=5), for both the tzdata and pytz packages.
Does ocimend tzcheck send anything?
No. It reads files and asks local interpreters; it needs no account and no network.
What does --strict do?
It exits with status 1 when a copy that is actually in use misses the 2026 changes, so a CI job or a fleet check can fail on it.

Sources

More guides