Понимание времени в Python: полное руководство по datetime

Серия 03 – операции с датой/временем, часовые пояса, преобразование форматов

Время – это не просто строка. Дни сменяются, месяцы переходят, летнее время вступает в силу, а в разных регионах правила различаются. Поэтому при работе с датой и временем важно различать «отображаемую строку» и «числовое значение», а также учитывать часовые пояса.

Магический кабинет часового мастера

В этой статье мы сосредоточимся на модуле datetime и разберём:

  • Создание текущего времени и даты
  • Вычисление периодов (timedelta)
  • Форматирование и разбор строк (strftime, strptime)
  • Работа с часовыми поясами (timezone, zoneinfo)
  • Частые ошибки и надёжные шаблоны

1. Что предоставляет datetime?



В datetime есть несколько типов, которые выглядят похожими, но выполняют разные задачи.

  • date – только год‑месяц‑день
  • time – только часы‑минуты‑секунды
  • datetime – дата и время (самый распространённый тип)
  • timedelta – разница во времени (период)
  • timezone – фиксированный смещение часового пояса (например, UTC+9)

Начиная с Python 3.9 в стандартную библиотеку вошёл модуль zoneinfo, упрощающий работу с региональными часовыми поясами (например, Asia/Tokyo).


2. Как получить «сейчас»: naive vs aware

2.1 Что такое naive и aware?

Объекты datetime делятся на две категории.

  • naive datetime – без информации о часовом поясе
  • aware datetime – с информацией о часовом поясе (tzinfo)

Смешивание этих типов при вычислениях может привести к ошибкам или неожиданным результатам.

2.2 Рекомендуемое значение по умолчанию: начать с UTC aware

Единый стандарт UTC упрощает хранение и расчёты.

from datetime import datetime, timezone

utc_now = datetime.now(timezone.utc)  # aware (UTC)
print(utc_now)

Если нужна локальная дата, преобразуем на этапе отображения.

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

utc_now = datetime.now(timezone.utc)
tokyo_now = utc_now.astimezone(ZoneInfo("Asia/Tokyo"))

print(utc_now)
print(tokyo_now)

3. Вычисления даты/времени: timedelta в центре внимания



3.1 Сложение/вычитание

from datetime import datetime, timedelta, timezone

now = datetime.now(timezone.utc)
print(now + timedelta(days=3))
print(now - timedelta(hours=2))

3.2 Разница между двумя моментами

from datetime import datetime, timezone

a = datetime(2026, 1, 1, tzinfo=timezone.utc)
b = datetime(2026, 1, 10, tzinfo=timezone.utc)

delta = b - a
print(delta.days)            # 9
print(delta.total_seconds()) # 777600.0

3.3 Как воспринимать «период» через timedelta

timedelta работает в днях, секундах и микросекундах. Выражения вроде «через месяц» требуют более сложных подходов (например, dateutil), поскольку длина месяца меняется.


4. Форматирование и разбор: strftime / strptime

4.1 datetime → строка (strftime)

from datetime import datetime, timezone

dt = datetime(2026, 1, 30, 14, 5, 0, tzinfo=timezone.utc)
print(dt.strftime("%Y-%m-%d %H:%M:%S %z"))

Часто используемые шаблоны:

  • %Y-%m-%d – 2026-01-30
  • %H:%M:%S – 14:05:00
  • %z – +0000 (смещение UTC)

4.2 строка → datetime (strptime)

from datetime import datetime

s = "2026-01-30 14:05:00"
dt = datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
print(dt)

Полученный dtnaive. Чтобы задать часовой пояс, добавляем tzinfo.

from datetime import datetime, timezone

s = "2026-01-30 14:05:00"
dt = datetime.strptime(s, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc)
print(dt)

replace(tzinfo=...) не меняет время, а только присваивает метку часового пояса. Для реального преобразования используйте astimezone().


5. Часовые пояса: различие между timezone и zoneinfo

5.1 Фиксированное смещение – timezone

from datetime import datetime, timezone, timedelta

kst_fixed = timezone(timedelta(hours=9))
dt = datetime(2026, 1, 30, 12, 0, tzinfo=kst_fixed)
print(dt)

5.2 Региональный часовой пояс – zoneinfo

Для зон с летним временем используйте zoneinfo.

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

utc_now = datetime.now(timezone.utc)
ny_now = utc_now.astimezone(ZoneInfo("America/New_York"))
print(ny_now)

5.3 replace(tzinfo=...) vs astimezone(...)

  • replace(tzinfo=...) – время остаётся тем же, меняется только метка
  • astimezone(...) – переводит момент во времени в другой часовой пояс

Понимание разницы помогает избежать багов.


6. Частые ошибки

6.1 Смешивание naive/aware

  • Для внутренних расчётов лучше использовать aware (UTC)
  • При вводе данных сразу определяйте и нормализуйте часовой пояс

6.2 «Локальное время» может отличаться в разных средах

datetime.now() использует локальные настройки среды. В контейнерах это может быть UTC. Лучше явно указывать datetime.now(timezone.utc).

6.3 Несоответствие формата при разборе строк

strptime чувствителен к формату: даже один символ разницы приводит к ошибке. Если входные строки могут иметь разные форматы, предварительно нормализуйте их или пробуйте несколько шаблонов.


7. Три популярных шаблона

7.1 Сохранение в формате ISO 8601

from datetime import datetime, timezone

dt = datetime.now(timezone.utc)
print(dt.isoformat())  # пример: 2026-01-30T05:12:34.567890+00:00

7.2 Формирование имени файла с сегодняшней датой

from datetime import datetime

stamp = datetime.now().strftime("%Y%m%d")
filename = f"report_{stamp}.json"
print(filename)

7.3 Вычисление оставшегося времени до заданного момента

from datetime import datetime, timezone

target = datetime(2026, 2, 1, 0, 0, tzinfo=timezone.utc)
now = datetime.now(timezone.utc)

remaining = target - now
print(remaining)
print(remaining.total_seconds())

8. Итоги

Модуль datetime позволяет не только форматировать даты, но и выполнять надёжные расчёты времени. Включение часовых поясов (timezone, zoneinfo) делает код устойчивым к различиям среды.

В следующей статье мы разберём модуль random, включая генерацию случайных чисел, выборки, перемешивание и безопасные случайные значения (secrets).


Связанные статьи:


Предыдущие части серии