Модуль zoneinfo. Поддержка актуальных часовых поясов в Python
Существует так называемая IANA Time Zone Database (tz database), которая содержит информацию обо всех часовых поясах, используемых в мире. Каждый пояс представлен в ней файлом, содержащем сведения о названии зоны, смещении от UTC, переходе на летнее время, исторических изменениях. Сбором данных и поддержкой актуальности этой базы занимается IANA (одна из организаций по стандартизации интернета).
Эта база данных у вас уже может быть установлена, так как используется многими операционными системами для своих нужд и периодически обновляется. Сам по себе модуль zoneinfo
стандартной библиотеки языка Python не содержит информации о часовых поясах. Он предназначен для получения данных из tz database и их использования в коде Python.
Чтобы узнать локацию файлов часовых поясов, можно воспользоваться константой TZPATH
модуля:
Если в системе не установлена tz database (скорее всего в Windows) или необходимо обеспечить кроссплатформенную совместимость программы, тогда следует установить собственный (для Python) пакет с часовыми поясами: tzdata.
Модуль zoneinfo
содержит класс ZoneInfo
, от которого создаются соответствующие python-экземпляры переданных в конструктор зон.
from zoneinfo import ZoneInfo moscow = ZoneInfo('Europe/Moscow') cuba = ZoneInfo('Cuba') print(moscow) # Europe/Moscow print(cuba) # Cuba luna = ZoneInfo('Luna') # ZoneInfoNotFoundError
Ключ, который передается в конструктор ZoneInfo
, является именем файла в директории zoneinfo (там, где располагается база данных). Если файл зоны находится в подкаталоге, то ключ отражает относительный путь до этого файла. Если файл указанного пояса не найден, то выбрасывается исключение ZoneInfoNotFoundError
.
Узнать имена всех зон можно не только просматривая каталог, также с помощью имеющейся в модуле функции available_timezones
.
import zoneinfo print(zoneinfo.available_timezones())
Функция возвращает множество из строк, представляющих собой имена зон.
Экземпляры ZoneInfo
не предназначены для непосредственного использования. Обычно их передают в качестве аргумента параметру tzinfo
конструктора datetime
.
from datetime import datetime from zoneinfo import ZoneInfo a = datetime(2023, 5, 27, hour=3, tzinfo=ZoneInfo('Asia/Tokyo')) print(a) print(a.utcoffset(), a.tzname(), a.tzinfo) print(a.tzinfo.__repr__()) print() b = a.astimezone(ZoneInfo('Europe/Moscow')) print(b) print(b.utcoffset(), b.tzname(), b.tzinfo)
2023-05-27 03:00:00+09:00 9:00:00 JST Asia/Tokyo zoneinfo.ZoneInfo(key='Asia/Tokyo') 2023-05-26 21:00:00+03:00 3:00:00 MSK Europe/Moscow
Технически объекты типа ZoneInfo
можно передавать и в конструктор time
, однако особого смысла в этом нет.
from datetime import * from zoneinfo import ZoneInfo moscow = ZoneInfo('Europe/Moscow') a = time(20, 30, tzinfo=moscow) # Время наивное, хотя tzinfo не равен None b = time(20, 30, tzinfo=timezone(timedelta(hours=3))) print(a) # 20:30:00 print(b) # 20:30:00+03:00 print(a.utcoffset(), '|', a.tzinfo) # None | Europe/Moscow print(b.utcoffset(), '|', b.tzinfo) # 3:00:00 | UTC+03:00 print(type(a.tzinfo)) # <class 'zoneinfo.ZoneInfo'> print(type(b.tzinfo)) # <class 'datetime.timezone'> c = time(20, 30) print(a > c) # Сравнивнение работает: оба времени наивны # print(a > b) # Будет ошибка: нельзя так сравнивать осведомленное с наивным # Экземпляр ZoneInfo передается из экземпляра time и # для экземпляра datetime создается таймдельта, которую # возвращает utcoffset() d = datetime.combine(date.today(), a) # Это осведомленное время print(d) # 2023-05-27 20:30:00+03:00 print(d.utcoffset()) # 3:00:00
Причина, по которой таймдельта не вычисляется для объектов типа time
видимо связана с тем, что tz database, с которой работает zoneinfo
, помимо прочего хранит информацию о переходе на летнее время и обратно. Поэтому, если нет даты, то нельзя точно указать смещение от UTC для конкретного часового пояса. Оно может отличаться для летнего и зимнего времени.
Модуль zoneinfo
освобождает программиста от необходимости разработки кода, обслуживающего изменение смещения часового пояса в течение года.
from datetime import datetime, timedelta from zoneinfo import ZoneInfo a = datetime(2023, 5, 27, hour=3, tzinfo=ZoneInfo('Europe/Berlin')) print(a) print('Летнее DST:', a.dst()) b = a + timedelta(days=190) print(b) print('Зимнее DST:', b.dst()) c = b + timedelta(days=200) print(c) print('Опять летнее:', c.dst())
2023-05-27 03:00:00+02:00 Летнее DST: 1:00:00 2023-12-03 03:00:00+01:00 Зимнее DST: 0:00:00 2024-06-20 03:00:00+02:00 Опять летнее: 1:00:00
Метод dst()
экземпляра datetime
возвращает объект типа timedelta
, смысловое назначение которого показать, включает ли смещении от UTC поправку на летнее время. Например, если две даты имеют смещение в 2 часа, но одна из них летняя, а другая зимняя, то они могут представлять собой разные часовые пояса.
Следует отметить, что не во всех странах есть переход на летнее время.
from datetime import datetime, timedelta from zoneinfo import ZoneInfo a = datetime(2023, 5, 27, 12, 45, 59, tzinfo=ZoneInfo('Europe/Moscow')) print(a) print('Летнее DST:', a.dst()) b = a + timedelta(days=190) print(b) print('Зимнее DST:', b.dst())
2023-05-27 12:45:59+03:00 Летнее DST: 0:00:00 2023-12-03 12:45:59+03:00 Зимнее DST: 0:00:00
Также обратим внимание, что когда происходит переход из летнего времени в стандартное для часового пояса или обратно, то таймдельта игнорирует его. Как-будто не было никакого прыжка на час вперед/назад.
from datetime import datetime, timedelta from zoneinfo import ZoneInfo a = datetime(2023, 10, 28, hour=22, tzinfo=ZoneInfo('Europe/Berlin')) print(a) b = a + timedelta(hours=5) print(b)
2023-10-28 22:00:00+02:00 2023-10-29 03:00:00+01:00
В примере в реальном физическом мире прошло 6 часов, так как часы были переведены на час назад. Поэтому через реальных 5 часов должно было бы быть 2 часа ночи, а не 3.