Введение: Часовые пояса

Работа с датой и временем в программировании — это одна из тех «темных» областей, на которой каждый разработчик набивает свои шишки. На первый взгляд все просто: 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-сообществе.

Уверен, у вас все получится. Вперед, к практике!

Комментарии (0)