2025, Dec 19 17:00

Get the canonical timezone name in Python: resolve ZoneInfo aliases using tzdata.zi link data

Learn how to canonicalize Python ZoneInfo timezone names by parsing tzdata.zi link mappings. Resolve aliases like Asia/Calcutta to Asia/Kolkata. Stay aligned.

Canonicalizing timezone names is a recurring need when your input can include historical or backward-linked identifiers. A typical case is Asia/Calcutta, which is an alias for Asia/Kolkata. You might expect ZoneInfo to normalize that automatically and surface the canonical key, but the straightforward approach does not provide it.

Reproducing the issue

The following example shows why relying on ZoneInfo.key is not enough when you want the canonical identifier:

from zoneinfo import ZoneInfo

alias_tz = ZoneInfo("Asia/Calcutta")
print(alias_tz.key)

The identifier remains as provided, and there is no direct API in ZoneInfo to resolve the canonical name.

What is actually happening

The timezone name you pass in is accepted as-is, even when it is a backward-linked alias. ZoneInfo exposes the key you used, and it does not follow that link. There is no built-in hook to retrieve the canonical name from ZoneInfo directly.

Practical path to the canonical name

The mapping you need is already shipped in tzdata. The file tzdata/zoneinfo/tzdata.zi includes link definitions, where lines formatted as L CANONICAL_NAME BACKWARD_NAME declare the association between an alias and its canonical counterpart. By parsing those lines, you can build a lookup table and expose a canonical property without altering any timezone behavior.

Below is a minimal extension that reads tzdata.zi once, caches the mapping, and provides a property with the canonical name. The program logic mirrors the structure of the link data and does not change timezone resolution.

import importlib.resources
from zoneinfo import ZoneInfo


class CanonicalZoneInfo(ZoneInfo):
    __linkmap = {}

    @property
    def primary_name(self) -> str:
        """
        Return the canonical name if an alias exists, otherwise the original key.
        """
        return self._alias_map().get(self.key, self.key)

    @classmethod
    def _alias_map(cls) -> dict:
        """
        Build and cache a mapping of backward (alias) names to canonical names
        using tzdata/zoneinfo/tzdata.zi lines formatted as:
        L CANONICAL_NAME BACKWARD_NAME
        """
        if not cls.__linkmap:
            zi_path = importlib.resources.files("tzdata") / "zoneinfo" / "tzdata.zi"
            with zi_path.open("r") as fh:
                for ln in fh:
                    if ln.startswith("L "):
                        canonical, alias = ln.split()[1:3]
                        cls.__linkmap[alias] = canonical
        return cls.__linkmap

You can then resolve aliases to canonical names like this:

tz = CanonicalZoneInfo("Asia/Calcutta")
print(tz.primary_name)

Why this matters

Consistent keys help avoid duplicate entries, simplify comparisons, and make storage predictable. When logs, configs, or APIs receive mixed identifiers for the same zone, canonicalization keeps everything aligned without changing any timezone offsets or transitions. It also lets you surface a stable name for downstream consumers.

Takeaways

If you need the canonical timezone name, ZoneInfo by itself won’t follow links. The link associations are present in tzdata/zoneinfo/tzdata.zi, with each relevant line starting with L and listing the canonical name followed by the backward alias. Parse those lines once, cache the result, and use it to expose a canonical property alongside the standard ZoneInfo behavior.