2026, Jan 12 18:01

Как получить каноническое имя часового пояса в Python (ZoneInfo, tzdata)

Почему ZoneInfo не канонизирует алиасы и как извлечь каноническое имя из tzdata.zi. Пошаговый пример на Python, разбор L-ссылок и кэширование сопоставлений.

Канонизация названий часовых поясов нередко требуется, когда во входных данных встречаются исторические или связанные через backward-алиасы идентификаторы. Типичный пример — Asia/Calcutta, который является псевдонимом для Asia/Kolkata. Можно ожидать, что ZoneInfo автоматически нормализует такое имя и вернёт канонический ключ, но простой способ этого не делает.

Воспроизведение проблемы

Пример ниже показывает, почему полагаться на ZoneInfo.key недостаточно, если нужен канонический идентификатор:

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

Идентификатор остаётся в том виде, в котором вы его передали, и в ZoneInfo нет прямого API для получения канонического имени.

Что происходит на самом деле

Переданное вами имя часового пояса принимается как есть, даже если это backward-алиас. ZoneInfo просто возвращает использованный ключ и не следует по ссылке. Встроенного механизма, который позволил бы напрямую получить каноническое имя из ZoneInfo, нет.

Практический путь к каноническому имени

Нужное сопоставление уже поставляется вместе с tzdata. В файле tzdata/zoneinfo/tzdata.zi есть определения ссылок: строки формата L CANONICAL_NAME BACKWARD_NAME описывают связь между алиасом и его каноническим соответствием. Разобрав эти строки, можно собрать таблицу соответствий и предоставить свойство с каноническим именем, не затрагивая логику работы часовых поясов.

Ниже — минимальное расширение, которое один раз читает tzdata.zi, кэширует сопоставление и добавляет свойство с каноническим именем. Логика программы повторяет структуру данных о ссылках и не влияет на разрешение часовых поясов.

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

После этого алиасы можно разрешать в канонические имена так:

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

Зачем это нужно

Единообразные ключи помогают избежать дубликатов, упрощают сравнения и делают хранение предсказуемым. Когда логи, конфигурации или API получают разные идентификаторы одной и той же зоны, канонизация выравнивает их без изменения смещений и переходов часовых поясов. Это также позволяет предоставлять стабильное имя для последующих потребителей.

Выводы

Если вам нужно каноническое имя часового пояса, один лишь ZoneInfo по ссылкам не пойдёт. Связи ссылок указаны в tzdata/zoneinfo/tzdata.zi: каждая нужная строка начинается с L и содержит каноническое имя, за которым следует backward-алиас. Разберите эти строки один раз, закэшируйте результат и используйте его, чтобы добавить каноническое свойство рядом со стандартным поведением ZoneInfo.