Если вы управляете глобальной службой или просто хотите предоставить контент в зависимости от времени доступа пользователей, управление часовыми поясами (Timezone) является одной из самой сложной задачей в веб-разработке. Пользователь может помнить, что он написал сообщение в '09:00', но сервер записывает это как '00:00' (UTC), а пользователи из других стран могут видеть это как '17:00' (PST). Что делать?

Django предлагает ясную философию для решения этой путаницы.

"Всегда сохраняйте в базе данных в формате UTC и только при отображении пользователям преобразовывайте в местное время."

Модуль django.utils.timezone предоставляет все инструменты, необходимые для реализации этой философии. Этот модуль работает идеально, если в settings.py Django установлено USE_TZ = True (значение по умолчанию).

В этом посте мы подробно рассмотрим основные функции django.utils.timezone.


1. Вместо datetime.datetime.now() используйте timezone.now()



Когда вы хотите зафиксировать текущее время в проекте Django, не следует используйте стандартную библиотеку Python datetime.datetime.now().

  • datetime.datetime.now(): если USE_TZ=False, возвращает 'naive' (без информации о часовом поясе) объект datetime. Это время определяется по локальному времени сервера. Если сервер находится в KST (Корея), то это время будет по KST, если в американском регионе AWS (в основном UTC), то это будет основано на времени сервера, что приводит к несоответствиям.
  • timezone.now(): когда USE_TZ=True, возвращает 'aware' (с информацией о часовом поясе) объект datetime, который всегда основан на UTC.

При сохранении времени в базе данных обязательно используйте timezone.now().

Пример:

from django.db import models
from django.utils import timezone
# from datetime import datetime # Не используйте это!

class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    # Использование default=timezone.now сохраняет время в базе данных всегда в UTC.
    created_at = models.DateTimeField(default=timezone.now)

# Создание новой записи
# Временная метка UTC на момент создания будет сохранена в created_at.
new_post = Post.objects.create(title="Заголовок", content="Содержимое")

2. 'Naive' против 'Aware' (основные концепции)

Чтобы понять этот модуль, вам нужно знать два типа объектов времени.

  • Aware (с информацией о часовом поясе): объект datetime, включающий информацию о часовом поясе (tzinfo). (например: 2025-11-14 10:00:00+09:00)
  • Naive (без информации о часовом поясе): объект datetime, не содержащий информацию о часовом поясе. (например: 2025-11-14 10:00:00) Naive время просто говорит "10:00", но не указывает, 10:00 какого-то часового пояса.

django.utils.timezone предоставляет функции для проверки и преобразования этих двух типов.

  • is_aware(value): возвращает True, если это Aware объект.
  • is_naive(value): возвращает True, если это Naive объект.

Это может быть очень полезно при отладке.


3. Преобразование в местное время: localtime() и activate()



Если вы правильно сохранили в базе данных в формате UTC, теперь пора показать это пользователям.

  • localtime(value, timezone=None): Преобразует объект Aware datetime (обычно UTC) в 'время текущего активированного часового пояса'.

Как задать 'текущий активированный часовой пояс'?

  • activate(timezone): Устанавливает основной часовой пояс для текущего потока (запроса). (Обычно используются объекты pytz или zoneinfo в Python 3.9+)
  • deactivate(): Возвращает активированный часовой пояс к значению по умолчанию (обычно UTC).
  • get_current_timezone(): Возвращает текущий активированный объект часового пояса.

Важно: Редко нужно вручную вызывать activate() в представлениях (View). Если settings.py включает MIDDLEWARE с django.middleware.timezone.TimezoneMiddleware, Django автоматически вызывается activate() на основе cookies или настроек профиля пользователя и т.д.

localtime() может быть использован в представлениях для ручного преобразования или в шаблонах с фильтром ({{ post.created_at|localtime }}).

Пример:

from django.utils import timezone
import pytz # или в Python 3.9+ используйте from zoneinfo import ZoneInfo

# 1. Загрузка записи из базы данных (created_at в формате UTC)
#    (Допустим, написано 14 ноября 2025 года в 01:50:00 UTC)
post = Post.objects.get(pk=1) 
# post.created_at -> datetime.datetime(2025, 11, 14, 1, 50, 0, tzinfo=<UTC>)

# 2. Предполагая, что пользователь находится в 'Asia/Seoul' (+09:00), активируем этот часовой пояс
seoul_tz = pytz.timezone('Asia/Seoul')
timezone.activate(seoul_tz)

# 3. Преобразование в местное время
local_created_at = timezone.localtime(post.created_at)

print(f"UTC время: {post.created_at}")
print(f"Сеульское время: {local_created_at}")

# 4. После использования деактивируем (обычно это делает middleware автоматически)
timezone.deactivate()

Результат:

UTC время: 2025-11-14 01:50:00+00:00
Сеульское время: 2025-11-14 10:50:00+09:00

4. Преобразование Naive времени в Aware время: make_aware()

При интеграции с внешним API, парсинге или получении данных, которые пользователи вводят в форме в формате YYYY-MM-DD HH:MM, мы сталкиваемся с 'Naive' объектом datetime, который не содержит информации о часовом поясе.

Если попытаться просто сохранить это Naive время в БД, появится предупреждение (Django 4.0+) или оно может быть сохранено неверно.

make_aware(value, timezone=None) присваивает Naive объекту datetime информацию о часовом поясе, чтобы превратить его в Aware объект.

Важно: make_aware не преобразует время, а объявляет: "это Naive время – это время по этому часовому поясу."

Пример:

from datetime import datetime
from django.utils import timezone
import pytz

# Время, полученное из внешнего API (Naive)
# "10:00 14 ноября 2025 года"
naive_dt = datetime(2025, 11, 14, 10, 0, 0)

# Предполагая, что мы знаем, что это 'Сеул' 기준 시간
seoul_tz = pytz.timezone('Asia/Seoul')

# Преобразуем Naive время в Aware время для 'Asia/Seoul'
aware_dt = timezone.make_aware(naive_dt, seoul_tz)

print(f"Naive: {naive_dt}")
print(f"Aware (Сеул): {aware_dt}")

# Теперь, если мы сохраним этот объект aware_dt в БД,
# Django автоматически преобразует его в UTC (2025-11-14 01:00:00+00:00).
# Post.objects.create(title="Интеграция API", event_time=aware_dt)

Результат:

Naive: 2025-11-14 10:00:00
Aware (Сеул): 2025-11-14 10:00:00+09:00

Итог: Принципы управления часовыми поясами в Django

Ключевые принципы правильного использования django.utils.timezone просты.

  1. Сохраняйте настройки USE_TZ = True.
  2. Всегда используйте timezone.now() для текущего времени, сохраняемого в базу данных.
  3. Используйте TimezoneMiddleware для автоматического активации часового пояса в зависимости от пользовательского запроса.
  4. При отображении времени UTC из базы данных пользователю используйте фильтр {{ value|localtime }} в шаблонах или функцию localtime() в представлениях.
  5. Наive время, полученное извне, обязательно преобразуйте в Aware с использованием make_aware() перед обработкой (сохранением).