Дата и время в Python: практическое руководство по datetime, strftime/strptime и таймзонам

Дата и время в Python: практическое руководство по datetime, strftime/strptime и таймзонам

Дата и время в Python: практическое руководство по datetime, strftime/strptime и таймзонам

Работа с датой и временем — обязательный навык в реальных проектах: логи, отчёты, напоминания, аналитика. В Python за это отвечает модуль datetime, а с Python 3.9+ — также zoneinfo для таймзон без внешних библиотек. Ниже — практическая выжимка с примерами и советами, чтобы вы уверенно справлялись с датой/временем в любом проекте.

Быстрый старт: создание даты и времени

from datetime import date, time, datetime, timedelta
from zoneinfo import ZoneInfo

# Текущие дата и время
today = date.today()
now_naive = datetime.now()  # naive — без таймзоны
now_utc = datetime.now(ZoneInfo("UTC"))  # aware — с таймзоной UTC

# Конкретные значения
birthday = date(1995, 7, 14)
meeting = datetime(2025, 3, 21, 14, 30)

# С таймзоной (Москва)
msk = ZoneInfo("Europe/Moscow")
now_msk = datetime.now(msk)
print(today, now_naive, now_utc, now_msk, sep="n")

В Python бывают два типа datetime: naive (без tzinfo) и aware (с tzinfo). Для надёжной работы в распределённых системах храните время в UTC и переводите в локальную зону только на границе с пользователем.

Форматирование дат: strftime

strftime превращает дату/время в строку по шаблону. Самые полезные спецификаторы: %Y (год), %m (месяц), %d (день), %H:%M:%S (часы, минуты, секунды), %f (микросекунды), %z (смещение таймзоны), %Z (имя зоны).

from datetime import datetime
from zoneinfo import ZoneInfo

dt = datetime(2025, 3, 21, 14, 35, 12, 123456, tzinfo=ZoneInfo("Europe/Moscow"))
print(dt.strftime("%Y-%m-%d %H:%M:%S"))      # 2025-03-21 14:35:12
print(dt.strftime("%d.%m.%Y"))               # 21.03.2025
print(dt.strftime("%H:%M"))                  # 14:35
print(dt.strftime("%Y-%m-%dT%H:%M:%S%z"))    # 2025-03-21T14:35:12+0300

Совет: для обмена между сервисами используйте ISO 8601. Быстрый способ — dt.isoformat() или явный шаблон %Y-%m-%dT%H:%M:%S%z.

Парсинг строк в дату: strptime

strptime делает обратное: разбирает строку по формату в объект datetime. Следите, чтобы формат точно соответствовал строке.

from datetime import datetime

# 21.03.2025 14:35 → datetime (naive)
dt1 = datetime.strptime("21.03.2025 14:35", "%d.%m.%Y %H:%M")
# 2025-03-21T14:35:12+03:00 → datetime с tzinfo
dt2 = datetime.strptime("2025-03-21T14:35:12+03:00", "%Y-%m-%dT%H:%M:%S%z")
print(dt1, dt1.tzinfo)  # None
print(dt2, dt2.tzinfo)  # tzoffset +0300

# Частая ошибка: 12-часовой формат
# %H — 00..23, %I + %p — 01..12 c AM/PM
dt3 = datetime.strptime("07:05 PM", "%I:%M %p")
print(dt3.strftime("%H:%M"))  # 19:05

Совет: для ISO 8601 можно использовать datetime.fromisoformat — быстрее и без шаблонов, например datetime.fromisoformat(«2025-03-21T14:35:12+03:00″).

Таймзоны с zoneinfo: перевод и тонкости

Начиная с Python 3.9, используйте zoneinfo вместо внешних зависимостей. Всегда переводите локальное время в UTC перед хранением или сравнениями.

from datetime import datetime
from zoneinfo import ZoneInfo

msk = ZoneInfo("Europe/Moscow")
utc = ZoneInfo("UTC")

dt_msk = datetime(2025, 3, 21, 10, 0, tzinfo=msk)
dt_utc = dt_msk.astimezone(utc)
print(dt_msk, dt_utc)

# Присвоение tzinfo (без перевода) допустимо только если вы УЖЕ уверены,
# что naive datetime выражает локальное «настенное» время этой зоны.
naive_local = datetime(2025, 3, 21, 10, 0)
aware_local = naive_local.replace(tzinfo=msk)
# Теперь можно перевести в UTC:
print(aware_local.astimezone(utc))

Частые ловушки:

  • Не смешивайте naive и aware в одном сравнении или арифметике.
  • Не используйте tzinfo=timezone(offset) как «имя зоны» — это только фиксированное смещение без учёта переходов на летнее время.
  • Для пользовательских интерфейсов храните в UTC, а показывайте в локальной зоне пользователя.
  • Арифметика времени: timedelta и «границы» суток

    from datetime import datetime, timedelta
    from zoneinfo import ZoneInfo
    
    now = datetime.now(ZoneInfo("UTC"))
    in_week = now + timedelta(days=7, hours=3)
    print(in_week)
    
    # Начало и конец дня (в конкретной зоне)
    msk = ZoneInfo("Europe/Moscow")
    dt = datetime(2025, 3, 21, 14, 35, tzinfo=msk)
    start_of_day = dt.replace(hour=0, minute=0, second=0, microsecond=0)
    end_of_day = start_of_day + timedelta(days=1) - timedelta(microseconds=1)
    print(start_of_day, end_of_day)
    

    Мини-практика: нормализация временных меток логов в UTC

    Допустим, у нас логи с разными форматами времени. Задача — получить единый UTC для последующей аналитики.

    from datetime import datetime
    from zoneinfo import ZoneInfo
    
    PATTERNS = [
        "%Y-%m-%d %H:%M:%S,%f",  # 2025-03-21 14:35:12,123
        "%d/%m/%Y %H:%M:%S",     # 21/03/2025 14:35:12
        "%Y-%m-%dT%H:%M:%S%z",   # 2025-03-21T14:35:12+03:00
    ]
    DEFAULT_TZ = ZoneInfo("Europe/Moscow")
    UTC = ZoneInfo("UTC")
    
    def parse_to_utc(s: str) -> datetime:
        # Сначала пробуем fromisoformat (быстро для ISO)
        try:
            dt = datetime.fromisoformat(s)
            if dt.tzinfo is None:
                dt = dt.replace(tzinfo=DEFAULT_TZ)
            return dt.astimezone(UTC)
        except Exception:
            pass
    
        # Затем проверяем набор известных форматов
        for p in PATTERNS:
            try:
                dt = datetime.strptime(s, p)
                if dt.tzinfo is None:
                    # Предполагаем, что строка — локальное время DEFAULT_TZ
                    dt = dt.replace(tzinfo=DEFAULT_TZ)
                return dt.astimezone(UTC)
            except Exception:
                continue
        raise ValueError(f"Не удалось разобрать дату: {s}")
    
    lines = [
        "2025-03-21 14:35:12,123 INFO User logged in",
        "21/03/2025 11:35:12 WARN Disk space low",
        "2025-03-21T14:35:12+03:00 ERROR Timeout",
    ]
    
    for line in lines:
        # Берём первые слова до пробела как дату (для примера)
        ts = line.split(" ")[0]
        try:
            dt_utc = parse_to_utc(ts)
            print(dt_utc.strftime("%Y-%m-%dT%H:%M:%S%z"), "→", line)
        except ValueError as e:
            print("SKIP:", e)
    

    Идея: пытаться распарсить несколькими способами, аккуратно присваивать таймзону, затем переводить в UTC. В реальных задачах распознавайте временную метку регулярками и описывайте форматы явно.

    Лучшие практики

  • Храните время в UTC, показывайте пользователю в его зоне.
  • Старайтесь работать только с aware datetime; избегайте смешивания с naive.
  • Используйте ISO 8601 для API и интеграций (isoformat() и fromisoformat()).
  • Для таймзон используйте zoneinfo, а не «ручные» смещения.
  • При «приклеивании» таймзоны через replace(tzinfo=...) будьте уверены, что время уже локальное для этой зоны.
  • Куда двигаться дальше

    Хотите закрепить основы Python и уверенно применять их в проектах? Рекомендую Пройти курс «Python с Нуля до Гуру» и прокачать навыки на практике — там много упражнений, разборов и проектов для быстрого роста.

    Освоив основы работы с датой и временем, вы избежите типичных багов и облегчите себе жизнь при интеграциях, аналитике и обработке событий во времени. Экспериментируйте с форматами, автоматизируйте конвертацию в UTC и держите под рукой zoneinfo — этого достаточно для большинства рабочих задач.

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий