Введение: Часовые пояса
Работа с датой и временем в программировании — это одна из тех «темных» областей, на которой каждый разработчик набивает свои шишки. На первый взгляд все просто: from datetime import datetime, datetime.now(). Что может пойти не так?
А потом в проекте появляются часовые пояса, и начинается тихий ужас.
Вы внезапно обнаруживаете, что стандартная библиотека Python оперирует двумя видами объектов: «наивными» (naive), которые ничего не знают о своем часовом поясе, и «осведомленными» (aware), у которых эта информация есть. И datetime.now() по умолчанию создает именно «наивный» объект, который в лучшем случае бесполезен, а в худшем — источник трудноуловимых багов, когда ваш код запускается на сервере в другом конце света.
Чтобы сделать все «правильно», вам приходится писать что-то вроде этого, привлекая сторонние библиотеки и выполняя странные ритуалы:
from datetime import datetime
from pytz import timezone # Или zoneinfo из Python 3.9+
# 1. Получаем "наивное" время в UTC
dt_utcnow = datetime.utcnow()
# 2. Создаем объект часового пояса
paris_tz = timezone('Europe/Paris')
# 3. "Локализуем" наше время, делая его "осведомленным"
dt_paris = paris_tz.localize(dt_utcnow)
# Какой-то ритуал, не правда ли?
print(dt_paris)
Это громоздко, неочевидно и легко ломается. А ведь мы еще не коснулись летнего/зимнего времени, арифметики с месяцами разной длины или парсинга дат из строк в десятках разных форматов.
К счастью, как и для многих других проблем в Python, для этой тоже есть элегантное решение. Знакомьтесь, Pendulum.
Если бы requests и datetime решили создать библиотеку, это был бы Pendulum. Он берет на себя всю грязную работу с часовыми поясами, парсингом и арифметикой, предоставляя невероятно простой, читаемый и интуитивно понятный API.
Часть 1: Первое знакомство — магия "из коробки"
Итак, мы оставили позади мир "наивных" дат и сложных конструкций. Давайте посмотрим, как Pendulum превращает боль в удовольствие, причем делает это сразу, без предварительной подготовки.
Установка: одна команда до начала волшебства
Как и положено современному пакету, установка не требует никаких усилий. Откройте ваш терминал с активированным виртуальным окружением и выполните:
pip install pendulum
Все, библиотека готова творить магию.
"Hello, Pendulum!": простота, которая подкупает
Давайте выполним самую базовую операцию — получим текущее время. Забудьте про выбор между now() и utcnow(). В Pendulum есть один очевидный способ:
import pendulum
# Получаем текущий момент времени
now = pendulum.now()
print(now)
# Вывод: 2025-11-15T11:55:00.123456+03:00
Главное отличие: «осведомленность» по умолчанию
Обратите внимание на вывод: ... +03:00. Это и есть та самая магия "из коробки". Pendulum по умолчанию создает осведомленный (aware) объект, автоматически определяя часовой пояс вашей системы (в данном случае, UTC+3). Он не оставляет вам шанса случайно создать "наивную" дату и выстрелить себе в ногу.
Это фундаментальное отличие от datetime, которое сразу избавляет от целого класса потенциальных ошибок.
Умный парсинг строк: забудьте про strptime
Вспомните, сколько раз вам приходилось гуглить коды форматирования (%Y, %m, %d, %H...), чтобы распарсить дату из строки с помощью datetime.strptime()? А что, если строка может прийти в нескольких разных форматах?
Pendulum решает эту проблему гениально. Его метод parse() достаточно умен, чтобы понять большинство общепринятых форматов дат и времени без каких-либо подсказок.
import pendulum
# ISO 8601 - стандарт для API
dt1 = pendulum.parse("2025-01-20T15:30:00")
# Европейский формат
dt2 = pendulum.parse("20.01.2025 15:30")
# Американский формат
dt3 = pendulum.parse("01/20/2025")
# Формат с названием месяца
dt4 = pendulum.parse("January 20, 2025")
print(dt1.to_day_datetime_string())
# Вывод: Mon, Jan 20, 2025 3:30 PM
Все эти примеры работают без единой строки форматирования. Pendulum просто "понимает", что вы ему даете. Он берет на себя рутинную и подверженную ошибкам работу, позволяя вам сосредоточиться на логике.
Часть 2: Решаем главную боль — часовые пояса без страданий
Вспомните ритуал с localize() из введения. Забудьте о нем. С Pendulum вы работаете с часовыми поясами так, как и должны — как с естественным атрибутом времени, а не как со сложной внешней сущностью.
Создание даты в нужном часовом поясе
Хотите узнать, который час сейчас в Париже? Просто спросите. Хотите создать объект для конкретной даты в Нью-Йорке? Просто укажите это.
import pendulum
# Способ 1: Текущий момент в Париже
dt_paris_now = pendulum.now('Europe/Paris')
print(f"Сейчас в Париже: {dt_paris_now}")
# Вывод: Сейчас в Париже: 2025-11-15T09:55:00.123456+01:00
# Способ 2: Конкретная дата и время в Нью-Йорке
dt_ny = pendulum.datetime(2025, 1, 1, 10, 0, 0, tz='America/New_York')
print(f"Новый год в Нью-Йорке: {dt_ny}")
# Вывод: Новый год в Нью-Йорке: 2025-01-01T10:00:00-05:00
Посмотрите, насколько это чисто. Часовой пояс — это просто еще один аргумент tz. Никаких отдельных объектов timezone, никаких вызовов localize(). Вы просто говорите, что вам нужно, и получаете это.
Конвертация между часовыми поясами: метод, который говорит сам за себя
Хорошо, у нас есть время в Нью-Йорке. А сколько в этот же самый момент будет в Токио? В стандартной библиотеке для этого есть метод astimezone(), который часто путают с localize(). В Pendulum для этого есть метод, который невозможно понять неправильно: .in_timezone().
Он читается как простое английское предложение.
# Возьмем наше время в Нью-Йорке
dt_ny = pendulum.datetime(2025, 1, 1, 10, 0, 0, tz='America/New_York')
# Узнаем, сколько в этот момент времени в Токио
dt_tokyo = dt_ny.in_timezone('Asia/Tokyo')
print(f"Когда в Нью-Йорке было 10 утра 1 января, в Токио было: {dt_tokyo}")
# Вывод: Когда в Нью-Йорке было 10 утра 1 января, в Токио было: 2025-01-02T00:00:00+09:00
И снова — никакой магии. Вы просто выражаете свое намерение: "Покажи мне это время в часовом поясе 'Asia/Tokyo'". Pendulum сам разбирается с правилами перехода на летнее/зимнее время и корректно вычисляет смещение. Вся сложность скрыта под капотом, оставляя вам чистый и понятный код.
Сравните эти два примера с кодом из введения. Никаких localize(), никаких astimezone(). Никакой путаницы между созданием и конвертацией. Pendulum предоставляет единый, интуитивно понятный интерфейс для всех операций с часовыми поясами.
Часть 3: Арифметика и манипуляции, которые просто работают
Если вам когда-либо приходилось вычислять дату "через 2 месяца и 5 дней от сегодня", вы знаете, что стандартный timedelta не работает с месяцами или годами. Приходилось писать сложную логику вручную или подключать dateutil. Pendulum встраивает всю эту мощь прямо в свои объекты, делая арифметику предсказуемой и простой.
Интуитивное сложение и вычитание
Вместо того чтобы импортировать timedelta и складывать объекты, вы просто используете методы, которые читаются как обычный английский язык: .add() и .subtract().
import pendulum
now = pendulum.now()
# Какая дата и время будут через 1 неделю и 2 дня?
future = now.add(weeks=1, days=2)
print(f"Через 1 неделю и 2 дня: {future.to_day_datetime_string()}")
# Какая дата была 3 месяца назад?
past = now.subtract(months=3)
print(f"3 месяца назад: {past.to_day_datetime_string()}")
Это не просто синтаксический сахар. Этот подход (fluent interface) позволяет создавать элегантные и очень читаемые цепочки вызовов.
Магия "умной" арифметики
А теперь — настоящая магия. Что произойдет, если к 31 января прибавить один месяц? Стандартная библиотека сломается, потому что 31 февраля не существует. Pendulum же ведет себя именно так, как вы от него ожидаете.
# 31 января 2024 года (високосный год)
dt = pendulum.datetime(2024, 1, 31)
# Прибавляем 1 месяц
dt_plus_month = dt.add(months=1)
print(dt_plus_month)
# Вывод: 2024-02-29T00:00:00+00:00
Pendulum "понимает", что вы хотите получить последний день следующего месяца. Он автоматически обрабатывает високосные годы и разную длину месяцев. Эта одна "умная" фича избавляет от целого класса пограничных ошибок (off-by-one errors), которые так сложно отлаживать.
Модификаторы-спасатели: привязка ко времени
Как часто вам нужно было получить начало текущего дня (полночь)? Или конец месяца? Или дату следующей среды? Обычно для этого приходится писать несколько строк кода с использованием .replace().
Pendulum предлагает для этого набор невероятно мощных и удобных методов-модификаторов.
now = pendulum.now()
# Начало текущего дня
print(f"Начало дня: {now.start_of('day')}")
# Конец текущего месяца
print(f"Конец месяца: {now.end_of('month')}")
# Дата следующей среды (относительно 'now')
next_wednesday = now.next(pendulum.WEDNESDAY)
print(f"Следующая среда: {next_wednesday.to_date_string()}")
# Прошлый понедельник
previous_monday = now.previous(pendulum.MONDAY)
print(f"Прошлый понедельник: {previous_monday.to_date_string()}")
Каждый из этих методов заменяет собой блок ручных вычислений. Вам больше не нужно писать функции для расчета последнего дня месяца или дня недели. Вы просто декларируете, что хотите получить, и Pendulum делает это за вас.
Часть 4: Говорим на человеческом языке — форматирование и сравнение
Техническая точность — это хорошо, но когда вы показываете дату пользователю в интерфейсе, формат 2025-11-15T12:30:00+03:00 редко бывает лучшим выбором. Pendulum предоставляет элегантные инструменты, чтобы форматировать даты и представлять временные интервалы в человекочитаемом виде.
«2 часа назад»: магия .diff_for_humans()
Это главная «вау»-фича Pendulum, которую вы видели на тысячах сайтов и в приложениях. Как сказать, что событие произошло не «15 ноября в 12:30», а «15 минут назад»? Обычно для этого пишут громоздкую функцию с кучей if/else. С Pendulum это один метод.
import pendulum
now = pendulum.now()
# Создадим моменты в прошлом и будущем
past = now.subtract(hours=2, minutes=30)
future = now.add(days=3)
# Узнаем, как давно это было
print(f"Событие произошло: {past.diff_for_humans()}")
# Вывод: Событие произошло: 2 часа назад
# Узнаем, как скоро это будет
print(f"Событие произойдет: {future.diff_for_humans()}")
# Вывод: Событие произойдет: через 3 дня
Этот метод невероятно умен. Он правильно подбирает единицы измерения (секунды, минуты, часы, дни, годы) и корректно формирует фразу на нужном языке (по умолчанию используется локаль системы). Это незаменимый инструмент для любого пользовательского интерфейса, будь то веб-сайт, Telegram-бот или десктопное приложение.
Удобное форматирование: прощай, strftime!
Вам надоело постоянно гуглить коды для strftime (%Y, %m, %d...), чтобы получить дату в нужном формате? Pendulum предлагает набор готовых, понятных по названию методов для самых частых случаев.
dt = pendulum.datetime(2025, 12, 25, 20, 30, 0)
# Стандартный формат для API
print(f"ISO 8601: {dt.to_iso8601_string()}")
# Вывод: ISO 8601: 2025-12-25T20:30:00+00:00
# Просто дата в красивом виде
print(f"Красивая дата: {dt.to_formatted_date_string()}")
# Вывод: Красивая дата: Dec 25, 2025
# Дата, день недели и время
print(f"Полный формат: {dt.to_day_datetime_string()}")
# Вывод: Полный формат: Thu, Dec 25, 2025 8:30 PM
Конечно, если вам нужен очень специфический формат, старый добрый strftime тоже доступен (dt.strftime('%Y-%m-%d')), но в 90% случаев встроенных методов более чем достаточно.
Работа с периодами: итерация по времени
Pendulum также упрощает работу с интервалами времени. Когда вы вычитаете один объект pendulum из другого, вы получаете не timedelta, а объект Period.
А главная прелесть Period в том, что по нему можно итерироваться!
start_of_week = pendulum.now().start_of('week')
end_of_week = pendulum.now().end_of('week')
# Создаем период, охватывающий текущую неделю
week_period = end_of_week - start_of_week
print(f"Все дни текущей недели:")
# Просто итерируемся по дням внутри периода
for day in week_period.in_days():
# day - это объект pendulum на каждый день
print(day.to_date_string())
Эта возможность невероятно удобна для генерации отчетов, построения календарей или любой другой задачи, где нужно перебрать последовательность дат.
Мы рассмотрели, как Pendulum делает код не только более надежным и коротким, но и более выразительным. Он позволяет общаться со временем на высоком уровне, не отвлекаясь на детали реализации.
Часть 5: Когда стоит задуматься? Минусы и альтернативы Pendulum
После такого потока восторгов может показаться, что Pendulum — это серебряная пуля для всех проблем, связанных с датой и временем. И во многих случаях это действительно так. Однако, как и у любого мощного инструмента, у него есть своя цена и свои сценарии, где его использование может быть неоптимальным или избыточным.
Чтобы принимать взвешенное инженерное решение, важно понимать не только сильные, но и слабые стороны библиотеки.
1. Производительность: удобство имеет свою цену
Pendulum — это высокоуровневая обертка над стандартным datetime. Вся магия, которая скрывает от нас сложность, — умный парсинг, интуитивная арифметика, работа с часовыми поясами — требует дополнительных вычислений.
Для большинства приложений (веб-серверы, боты, аналитические скрипты) эта разница в производительности будет абсолютно незаметной. Выигрыш в скорости разработки и надежности кода многократно перекроет микросекунды, потраченные на вызовы методов Pendulum.
Однако если вы работаете в сфере высокопроизводительных вычислений (High-Performance Computing), обрабатываете миллионы записей из логов в секунду или пишете код, где каждый такт процессора на счету, нативный datetime будет ощутимо быстрее. В таких сценариях отказ от удобства в пользу «чистой» скорости может быть оправданным.
2. Внешняя зависимость: еще один пакет в requirements.txt
Добавляя Pendulum в проект, вы добавляете внешнюю зависимость. Это означает:
Увеличение размера проекта: Для небольшого скрипта или lambda-функции это может иметь значение.
Потенциальные конфликты: Как и любая другая библиотека, Pendulum может иметь свои зависимости, которые теоретически могут конфликтовать с другими пакетами в вашем проекте.
Поддержание в актуальном состоянии: Вам нужно следить за обновлениями и возможными уязвимостями.
Если все, что вам нужно — это получить текущее время в UTC, тянуть ради этого целую библиотеку может быть излишним.
3. Стандартная библиотека становится лучше: появление zoneinfo
Основная критика стандартной библиотеки, озвученная в начале статьи, была особенно актуальна во времена доминирования pytz. Однако начиная с Python 3.9, в стандартную библиотеку был включен модуль zoneinfo, который решил многие проблемы.
Давайте посмотрим, как теперь выглядит работа с часовыми поясами без сторонних библиотек:
from datetime import datetime
from zoneinfo import ZoneInfo # Встроенный модуль!
# Получаем текущее время в Париже
dt_paris = datetime.now(tz=ZoneInfo("Europe/Paris"))
print(f"Сейчас в Париже: {dt_paris}")
# Конвертируем в Токио
dt_tokyo = dt_paris.astimezone(ZoneInfo("Asia/Tokyo"))
print(f"В этот же момент в Токио: {dt_tokyo}")
Этот код гораздо чище и понятнее, чем старый подход с pytz.localize(). Да, он все еще не предлагает умной арифметики или метода .diff_for_humans(), но главная боль — работа с часовыми поясами — стала гораздо менее острой. Разрыв в удобстве сократился, и для многих задач возможностей datetime + zoneinfo может быть вполне достаточно.
4. Совместимость и экосистема: Pendulum — это не datetime
Хотя объекты Pendulum наследуются от datetime, они не всегда полностью взаимозаменяемы. Некоторые библиотеки, особенно те, что написаны на C (например, определенные ORM или сериализаторы), могут ожидать на вход строго экземпляр datetime.datetime.
В большинстве случаев проблем не возникает, но иногда можно столкнуться с неожиданным поведением или ошибками. Pendulum предусматривает это и предлагает метод .to_datetime_instance(), чтобы получить "чистый" объект datetime, но это требует дополнительного шага и внимания со стороны разработчика.
Заключение: так использовать или нет?
Pendulum — это фантастический инструмент. Он остается золотым стандартом, когда вам нужны богатые возможности для манипуляции временем, максимальная читаемость кода и защита от распространенных ошибок.
Использовать Pendulum — ваш лучший выбор, если:
Вы разрабатываете веб-приложение, API или любую систему, где активно работаете с часовыми поясами, пользовательским вводом и форматированием дат.
Читаемость и надежность кода для вас важнее незначительных потерь в производительности.
Вам нужна "человеческая" арифметика (сложение месяцев) и форматирование (типа "2 дня назад").
Стоит остаться на стандартной библиотеке, если:
Производительность является абсолютным приоритетом.
Вы пишете небольшой скрипт или библиотеку с требованием минимальных зависимостей.
Ваши потребности ограничиваются базовыми операциями с UTC или конвертацией между парой часовых поясов (с чем отлично справляется
zoneinfoв Python 3.9+).
Домашнее задание: Укрощаем время
Отлично, теория позади! Теперь давайте на практике убедимся, что работать с датой и временем может быть просто и приятно. Выполните эти задания, чтобы закрепить материал и навсегда подружиться с Pendulum.
1. Умный парсер: прочитайте дату, написанную человеком
Цель: Убедиться, насколько мощный и гибкий метод pendulum.parse().
Задача: Напишите скрипт, который берет следующую строку: "Lunch is scheduled for 2:30pm on the 3rd of next month" и, используя только pendulum.parse(), превращает ее в полноценный объект datetime. Выведите результат в формате YYYY-MM-DD HH:mm:ss.
Подсказки:
Вам не нужно писать никаких регулярных выражений или сложных правил. Просто передайте строку в
pendulum.parse().Обратите внимание, как Pendulum сам поймет, что "next month" означает следующий месяц относительно текущей даты.
Для вывода используйте метод
strftime('%Y-%m-%d %H:%M:%S')или просто вызовитеstr()на объекте.
2. Планировщик мирового запуска: магия часовых поясов
Цель: Закрепить навыки работы с часовыми поясами.
Задача: Представьте, что вы планируете запуск продукта. Он должен состояться 20 февраля 2026 года в 9:00 утра по времени Токио (Asia/Tokyo). Напишите скрипт, который выводит, какая это будет дата и время для ваших коллег в Лондоне (Europe/London) и Сан-Франциско (America/Los_Angeles).
Подсказки:
Сначала создайте исходный объект
datetimeс помощьюpendulum.datetime(...), не забудьте указатьtz='Asia/Tokyo'.Затем используйте метод
.in_timezone()дважды, чтобы сконвертировать исходное время в нужные часовые пояса.
3. Расчет дедлайна: арифметика для профессионалов
Цель: Попрактиковаться в использовании методов .add() и модификаторов (.end_of()).
Задача: Сотруднику поставили задачу. Дедлайн — конец рабочего дня (18:00) через 3 недели и 2 рабочих дня от текущего момента. Напишите скрипт, который рассчитывает и выводит точную дату и время дедлайна.
Подсказки:
Pendulum не умеет считать "рабочие дни" из коробки, так что упростим задачу до "3 недели и 2 дня".
Начните с
pendulum.now().Используйте метод
.add(weeks=3, days=2)для добавления периода.Чтобы установить время на 18:00, используйте метод
.at(18, 0, 0).
4. "Только что опубликовано": пишем функцию для UI
Цель: Скомбинировать несколько навыков и использовать самую "человечную" фичу — .diff_for_humans().
Задача: Напишите функцию display_post_time(iso_timestamp), которая принимает на вход строку с датой в формате ISO 8601 (например, "2025-11-10T12:00:00Z").
Если с момента публикации прошло меньше одной недели, функция должна возвращать относительное время (например, "2 дня назад" или "5 часов назад").
Если прошло больше недели, функция должна возвращать дату в красивом формате (например, "Nov 10, 2025").
Подсказки:
Внутри функции сначала распарсите строку с помощью
pendulum.parse().Чтобы проверить, прошла ли неделя, можно сравнить
pendulum.now()сpost_time.add(weeks=1).Используйте
post_time.diff_for_humans()для первого случая иpost_time.to_formatted_date_string()для второго.
Анонс новых статей, полезные материалы, а так же если в процессе решения возникнут сложности, обсудить их или задать вопрос по статье можно в моём Telegram-сообществе.
Уверен, у вас все получится. Вперед, к практике!