pytz Python: работа с часовыми поясами
Библиотека pytz для Python содержит базу часовых поясов IANA и позволяет переводить время между городами и странами с учетом летнего времени. В статье показано, как установить pytz, правильно создать дату в нужном поясе, перевести ее в другой и чем pytz отличается от встроенного модуля zoneinfo.

Содержание статьи
Установка и первый пример
pytz не входит в стандартную библиотеку, его ставят через pip. Пакет написан на чистом Python и не имеет зависимостей, база поясов поставляется внутри него и обновляется с новыми версиями пакета.
pip install pytzСамый короткий сценарий: получить текущее время в конкретном поясе. Для этого объект пояса передают в datetime.now().
from datetime import datetime
import pytz
moscow = pytz.timezone('Europe/Moscow')
now = datetime.now(moscow)
print(now.strftime('%d.%m.%Y %H:%M %Z')) # например 08.03.2025 14:30 MSKПояса задаются именами вида Континент/Город: Europe/Moscow, Asia/Yekaterinburg, America/New_York. Сокращения вроде MSK или EST в качестве имени не подходят, так как они неоднозначны.
Поиск нужного часового пояса
В pytz есть несколько готовых списков. Они пригодятся, когда нужно показать пользователю выбор пояса или проверить, существует ли имя.
pytz.all_timezones- все имена, включая устаревшие (около 600 штук);pytz.common_timezones- актуальные пояса без исторических псевдонимов;pytz.country_timezones('ru')- пояса страны по двухбуквенному коду ISO;pytz.utc- готовый объект UTC.
import pytz
print(pytz.country_timezones('ru')[:3])
# ['Europe/Kaliningrad', 'Europe/Moscow', 'Europe/Kirov']
print('Asia/Omsk' in pytz.common_timezones) # True
try:
pytz.timezone('Moscow')
except pytz.UnknownTimeZoneError:
print('Нет такого пояса')Главная ловушка: tzinfo вместо localize
Самая частая ошибка с pytz - передать пояс в конструктор datetime через параметр tzinfo. Так делать нельзя: pytz хранит всю историю смещений пояса, и без подсказки datetime берет самое первое из них, местное среднее время (LMT) конца XIX века.
from datetime import datetime
import pytz
msk = pytz.timezone('Europe/Moscow')
wrong = datetime(2022, 3, 27, 1, 30, tzinfo=msk)
right = msk.localize(datetime(2022, 3, 27, 1, 30))
print(wrong) # 2022-03-27 01:30:00+02:30
print(right) # 2022-03-27 01:30:00+03:00Смещение +02:30 не имеет отношения к современной Москве. Правильный способ - создать наивную дату (без пояса) и вызвать метод localize(). Он выберет смещение, которое действовало именно в эту дату. Исключение одно: с pytz.utc параметр tzinfo работает корректно, у UTC нет истории изменений.
Перевод времени между поясами
Если дата уже осведомленная (aware), в другой пояс ее переводят стандартным методом astimezone(). Хранить и сравнивать время лучше в UTC, а в местное переводить только для показа пользователю.
from datetime import datetime
import pytz
meeting = datetime(2025, 7, 1, 12, 0, tzinfo=pytz.utc)
for name in ('Europe/Moscow', 'America/New_York', 'Asia/Tokyo'):
local = meeting.astimezone(pytz.timezone(name))
print(f'{name:18} {local:%H:%M %Z}')
# Europe/Moscow 15:00 MSK
# America/New_York 08:00 EDT
# Asia/Tokyo 21:00 JSTТак удобно выводить время вебинара или дедлайна для участников из разных городов. Для форматирования используется тот же синтаксис, что в strftime, код %Z подставляет сокращение пояса, а %z - смещение вида +0300.
Летнее время и метод normalize
В России сезонный перевод часов отменен, но в США и Европе он есть, и именно там pytz требует аккуратности. При арифметике через timedelta смещение у даты не пересчитывается само, даже если результат перешел через границу летнего времени.
from datetime import datetime, timedelta
import pytz
ny = pytz.timezone('America/New_York')
d = ny.localize(datetime(2024, 3, 10, 1, 30))
later = d + timedelta(hours=1)
print(later) # 2024-03-10 02:30:00-05:00, такого времени нет
print(ny.normalize(later)) # 2024-03-10 03:30:00-04:00Метод normalize() исправляет смещение после сложения или вычитания. Еще две проблемные ситуации - несуществующий час весной и повторяющийся час осенью. Параметр is_dst=None в localize() заставляет pytz не угадывать, а выбрасывать исключение.
try:
ny.localize(datetime(2024, 3, 10, 2, 30), is_dst=None)
except pytz.NonExistentTimeError:
print('Этого времени не было: часы перевели вперед')
try:
ny.localize(datetime(2024, 11, 3, 1, 30), is_dst=None)
except pytz.AmbiguousTimeError:
print('Это время было дважды')Фиксированное смещение и исторические даты
Пояс с постоянным смещением без привязки к городу создается функцией pytz.FixedOffset(), аргумент задается в минутах. Имени вида UTC+5 в базе нет, вызов pytz.timezone('UTC+5') закончится ошибкой UnknownTimeZoneError.
from datetime import datetime
import pytz
plus5 = pytz.FixedOffset(300)
print(datetime(2025, 1, 1, 9, 0, tzinfo=plus5)) # 2025-01-01 09:00:00+05:00
ny = pytz.timezone('America/New_York')
print(ny.localize(datetime(1900, 1, 1))) # 1900-01-01 00:00:00-04:56Второй пример показывает, что база хранит историю: в 1900 году Нью-Йорк жил по местному времени со смещением -4:56. Это полезно при обработке архивных данных, где нужно точное время событий прошлых лет.
pytz или zoneinfo
С Python 3.9 в стандартной библиотеке есть модуль zoneinfo, который решает ту же задачу без ловушек pytz. Сам pytz находится в режиме поддержки, новых возможностей в него не добавляют.
| Задача | pytz | zoneinfo |
|---|---|---|
| Создать дату в поясе | tz.localize(dt) | datetime(..., tzinfo=ZoneInfo(name)) |
| Арифметика через летнее время | нужен normalize() | работает сразу |
| Перевод в другой пояс | astimezone() | astimezone() |
| Источник базы поясов | внутри пакета | система или пакет tzdata |
| Версии Python | любые | 3.9 и новее |
В новом проекте выбирайте zoneinfo. pytz остается нужен при поддержке старого кода и в библиотеках, которые требуют именно его объекты, например в старых версиях Django и pandas. Подробнее о стандартных классах дат читайте в статье про модуль datetime, а разницу между датами разбирает материал о timedelta.
Частые вопросы
Короткие ответы на вопросы о pytz, которые задают чаще всего.
Почему pytz показывает смещение +02:30 для Москвы?
Пояс передан в конструктор datetime через tzinfo=. Pytz в этом случае подставляет историческое местное время. Создайте наивную дату и вызовите tz.localize(dt).
Как получить текущее время в UTC?
Вызовите datetime.now(pytz.utc) или без pytz datetime.now(timezone.utc) из модуля datetime. Метод datetime.utcnow() устарел с Python 3.12 и возвращает наивную дату.
Как убрать часовой пояс из даты?
Вызовите dt.replace(tzinfo=None). Время останется прежним, исчезнет только информация о поясе. Если нужно наивное время в UTC, сначала переведите дату через astimezone(pytz.utc).
Нужно ли обновлять pytz?
Да, правила часовых поясов меняются правительствами разных стран несколько раз в год. Свежая база приходит с новой версией пакета: pip install -U pytz.