← Назад к вопросам

Почему datetime прибегает к дополнительным timezone?

1.0 Junior🔥 141 комментариев
#Асинхронность и многопоточность

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

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Почему datetime прибегает к дополнительным timezone

Этот вопрос касается истории и архитектуры Python и стандарта ISO 8601. Timezone в datetime необходимы для корректной работы с временем в распределённых системах, но часто приводят к путанице.

Историческая причина: UTC эпоха

Первоначально Python в стандартной библиотеке предоставлял только naive datetime (без информации о timezone). Это было ошибкой архитектуры:

# Старый подход — naive datetime
dt = datetime.utcnow()  # UTC, но класс это не знает
print(dt.tzinfo)  # None — потеряна информация о timezone

Это приводило к багам: вы получали время в UTC, но Python думал, что это локальное время.

Настоящая причина: Распределённые системы

В production используются серверы в разных часовых поясах. Нужен способ хранить время с информацией о timezone:

from datetime import datetime, timezone, timedelta

# Наивный datetime — ОПАСНО!
dt_naive = datetime(2026, 3, 23, 15, 30, 0)  # Какая timezone?
print(dt_naive.tzinfo)  # None

# Aware datetime — ПРАВИЛЬНО
dt_utc = datetime(2026, 3, 23, 15, 30, 0, tzinfo=timezone.utc)
dt_moscow = datetime(2026, 3, 23, 15, 30, 0, tzinfo=timezone(timedelta(hours=3)))

# Одно и то же время в разных зонах
print(dt_utc == dt_moscow)  # True
print(dt_utc)    # 2026-03-23 15:30:00+00:00
print(dt_moscow) # 2026-03-23 15:30:00+03:00

Проблема наивных datetime

# БД хранит UTC, приложение в Moscow
db_time = datetime(2026, 3, 23, 12, 0, 0)  # Из БД — этого UTC
local_time = datetime(2026, 3, 23, 12, 0, 0)  # Локальное время Moscow

# Выглядят одинаково, но это разные времена!
print(db_time == local_time)  # True — ОШИБКА!

# Вычисления ломаются
print((db_time - local_time).total_seconds())  # 0 — неправильно!

Решение: Timezone-aware datetime

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

# ✅ ПРАВИЛЬНО — всегда UTC в коде
def get_now_utc() -> datetime:
    return datetime.now(timezone.utc)

# ✅ Сохраняем с timezone информацией
dt_utc = datetime.now(timezone.utc)
print(dt_utc)  # 2026-03-23 12:00:00+00:00

# ✅ Конвертируем при отправке пользователю
moscow_tz = ZoneInfo("Europe/Moscow")
dt_moscow = dt_utc.astimezone(moscow_tz)
print(dt_moscow)  # 2026-03-23 15:00:00+03:00

Почему это нужно?

  1. Синхронизация серверов — разные серверы в разных зонах
  2. Хранение в БД — PostgreSQL TIMESTAMPTZ требует timezone
  3. Корректные вычисления — разница между временами вычисляется правильно
  4. Стандарт ISO 8601 — указывает, что время должно содержать timezone

Особенность Python: datetime.utcnow() ОПАСЕН

# ❌ ПЛОХО — возвращает naive datetime
dt = datetime.utcnow()
print(dt.tzinfo)  # None — это UTC, но Python не знает!

# ✅ ХОРОШО — явно указываем UTC
dt = datetime.now(timezone.utc)
print(dt.tzinfo)  # timezone.utc

# Проблема utcnow():
dt_utc = datetime.utcnow()  # Выглядит как UTC
dt_local = datetime.now()    # Выглядит как локальное

if dt_utc == dt_local:  # Случайно совпадают!
    print("Бага — они разные по факту, но выглядят одинаково")

Работа с базой данных

# PostgreSQL: TIMESTAMPTZ хранит с timezone
from sqlalchemy import Column, DateTime
from datetime import datetime, timezone

class Event(Base):
    __tablename__ = "events"
    
    # ✅ ПРАВИЛЬНО
    created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
    
    # ❌ ПЛОХО
    # created_at = Column(DateTime, default=datetime.utcnow)
-- SQL миграция
CREATE TABLE events (
    id SERIAL PRIMARY KEY,
    -- TIMESTAMPTZ — с timezone информацией
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

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

  1. В коде используйте UTC везде:
from datetime import datetime, timezone

now = datetime.now(timezone.utc)  # Всегда UTC
  1. В БД используйте TIMESTAMPTZ:
CREATE TABLE users (
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
  1. При отправке пользователю конвертируйте:
from zoneinfo import ZoneInfo

moscow_tz = ZoneInfo("Europe/Moscow")
user_time = utc_time.astimezone(moscow_tz)
  1. НИКОГДА не используйте utcnow():
# ❌ Забудьте
datetime.utcnow()

# ✅ Используйте
datetime.now(timezone.utc)
  1. Типизируйте как aware:
from datetime import datetime, timezone
from typing import Annotated

# Вспомогательный тип для осведомлённого datetime
UTCDateTime = Annotated[datetime, "UTC timezone-aware datetime"]

def process_event(created_at: UTCDateTime) -> None:
    assert created_at.tzinfo == timezone.utc

Почему именно timezone?

Тимezone были добавлены в Python потому, что:

  • Стандарт ISO 8601 требует информацию о timezone
  • PostgreSQL, MySQL используют типы с timezone
  • Распределённые системы не могут обойтись без явной информации о timezone
  • Без timezone дата-время становится неоднозначной

Это не усложнение, это необходимость для корректной работы с временем в production системах.

Почему datetime прибегает к дополнительным timezone? | PrepBro