Если вы управляете глобальной службой или просто хотите предоставить контент в зависимости от времени доступа пользователей, управление часовыми поясами (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 просты.
- Сохраняйте настройки
USE_TZ = True. - Всегда используйте
timezone.now()для текущего времени, сохраняемого в базу данных. - Используйте
TimezoneMiddlewareдля автоматического активации часового пояса в зависимости от пользовательского запроса. - При отображении времени UTC из базы данных пользователю используйте фильтр
{{ value|localtime }}в шаблонах или функциюlocaltime()в представлениях. - Наive время, полученное извне, обязательно преобразуйте в Aware с использованием
make_aware()перед обработкой (сохранением).
Комментариев нет.