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.