Часовые пояса
Обзор
При включенном поддержке часовых поясов Django хранит информацию о датах и временах в формате UTC в базе данных, использует объекты datetime с учётом часового пояса во внутренней работе и переводит их в часовой пояс конечного пользователя в шаблонах и формах.
Это удобно, если ваши пользователи проживают в нескольких часовых поясах, и вы хотите отображать информацию о дате и времени в соответствии с местным временем каждого пользователя.
Даже если ваш сайт доступен только в одном часовом поясе, всё равно рекомендуется хранить данные в формате UTC в вашей базе данных. Основная причина — летнее время (DST). Многие страны имеют систему DST, где время переводится вперёд весной и назад осенью. Если вы работаете с местным временем, вы, скорее всего, столкнётесь с ошибками дважды в год, когда происходят переходы. Это, вероятно, не имеет значения для вашего блога, но это проблема, если вы переплачиваете или недоплачиваете своим клиентам на один час дважды в год, каждый год. Решением этой проблемы является использование UTC в коде и использование местного времени только при взаимодействии с конечными пользователями.
Поддержка часовых поясов отключена по умолчанию. Чтобы включить её, установите USE_TZ =
True в файле настроек.
Примечание
В Django 5.0 поддержка часовых поясов будет включена по умолчанию.
Поддержка часовых поясов использует zoneinfo, которая является частью стандартной библиотеки Python с Python 3.9. Пакет backports.zoneinfo автоматически устанавливается вместе с Django, если вы используете Python 3.8.
Примечание
Файл по умолчанию settings.py созданный django-admin
startproject включает USE_TZ = True для удобства.
Если вы столкнулись с определённой проблемой, начните с часто задаваемых вопросов по часовым поясам.
Концепции
Объекты datetime без и с учётом часового пояса
Объекты datetime.datetime Python имеют атрибут tzinfo, который может использоваться для хранения информации о часовом поясе, представленной как экземпляр подкласса datetime.tzinfo. Если этот атрибут установлен и описывает смещение, объект datetime является с учётом часового пояса. В противном случае он является без учёта часового пояса.
Вы можете использовать is_aware() и is_naive(), чтобы определить, являются ли datetime объекты с учётом часового пояса или без.
Когда поддержка часовых поясов отключена, Django использует объекты datetime без учёта часового пояса в местном времени. Это достаточно для многих случаев использования. В этом режиме, чтобы получить текущее время, вы бы написали:
import datetime now = datetime.datetime.now()
Когда поддержка часовых поясов включена (USE_TZ=True), Django использует объекты datetime с учётом часового пояса. Если ваш код создаёт объекты datetime, они также должны учитывать часовой пояс. В этом режиме пример выше становится:
from django.utils import timezone now = timezone.now()
Предупреждение
Работа с объектами datetime с учётом часового пояса не всегда интуитивна. Например, аргумент tzinfo стандартного конструктора datetime не работает надёжно для часовых поясов с DST. Использование UTC, как правило, безопасно; если вы используете другие часовые пояса, вам следует внимательно изучить документацию zoneinfo.
Примечание
Объекты datetime.time Python также имеют атрибут tzinfo, а PostgreSQL имеет соответствующий тип time with time zone. Однако, как указано в документации PostgreSQL, этот тип «обладает свойствами, которые приводят к сомнительной полезности».
Django поддерживает только объекты time без учёта часового пояса и выдаст исключение, если вы попытаетесь сохранить объект time с учётом часового пояса, так как часовой пояс для времени без связанной даты не имеет смысла.
Интерпретация объектов datetime без учёта часового пояса
Когда USE_TZ True, Django всё ещё принимает объекты datetime без учёта часового пояса, чтобы сохранить обратную совместимость. Когда уровень базы данных получает такой объект, он пытается сделать его с учётом часового пояса, интерпретируя его в по умолчанию часовом поясе и выводит предупреждение.
К сожалению, во время переходов на летнее/зимнее время некоторые даты и времена не существуют или являются неоднозначными. Вот почему вы всегда должны создавать объекты datetime с учётом часового пояса, когда включена поддержка часовых поясов. (См. Using ZoneInfo section of the zoneinfo
docs для примеров использования атрибута fold, чтобы указать смещение, которое должно применяться к datetime во время перехода на летнее/зимнее время.)
На практике это редко является проблемой. Django предоставляет вам объекты datetime с учётом часового пояса в моделях и формах, и чаще всего новые объекты datetime создаются из существующих с помощью арифметики timedelta. Единственный объект datetime, который часто создаётся в прикладном коде, — это текущее время, и timezone.now() автоматически делает всё правильно.
Часовой пояс по умолчанию и текущий часовой пояс
Часовой пояс по умолчанию — это часовой пояс, определённый настройкой TIME_ZONE.
Текущий часовой пояс — это часовой пояс, используемый для отображения.
Вы должны установить текущий часовой пояс на фактический часовой пояс конечного пользователя с помощью activate(). В противном случае используется часовой пояс по умолчанию.
Примечание
Как объяснено в документации TIME_ZONE, Django устанавливает переменные среды, чтобы процесс его работы выполнялся в часовом поясе по умолчанию. Это происходит независимо от значения USE_TZ и текущего часового пояса.
Когда USE_TZ True, это полезно для сохранения обратной совместимости с приложениями, которые по-прежнему полагаются на местное время. Однако, как объяснено выше, это не совсем надёжно, и вы всегда должны работать с объектами datetime с учётом часового пояса в формате UTC в своём коде. Например, используйте fromtimestamp() и установите параметр tz на utc.
Выбор текущего часового пояса
Текущий часовой пояс эквивалентен текущему языковому параметру для переводов. Однако нет эквивалента HTTP-заголовка Accept-Language, который Django мог бы использовать для автоматического определения часового пояса пользователя. Вместо этого Django предоставляет функции выбора часового пояса. Используйте их, чтобы создать логику выбора часового пояса, которая имеет смысл для вас.
Большинство веб-сайтов, которые заботятся о часовых поясах, спрашивают пользователей, в каком часовом поясе они проживают, и хранят эту информацию в профиле пользователя. Для анонимных пользователей они используют часовой пояс основной аудитории или UTC. zoneinfo.available_timezones() предоставляет набор доступных часовых поясов, которые вы можете использовать для создания карты из вероятных местоположений в часовые пояса.
Вот пример, который сохраняет текущий часовой пояс в сессии. (Он полностью пропускает обработку ошибок ради простоты.)
Добавьте следующий миддлвейр в MIDDLEWARE:
import zoneinfo
from django.utils import timezone
class TimezoneMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
tzname = request.session.get("django_timezone")
if tzname:
timezone.activate(zoneinfo.ZoneInfo(tzname))
else:
timezone.deactivate()
return self.get_response(request)
Создайте представление, которое может установить текущий часовой пояс:
from django.shortcuts import redirect, render
# Prepare a map of common locations to timezone choices you wish to offer.
common_timezones = {
"London": "Europe/London",
"Paris": "Europe/Paris",
"New York": "America/New_York",
}
def set_timezone(request):
if request.method == "POST":
request.session["django_timezone"] = request.POST["timezone"]
return redirect("/")
else:
return render(request, "template.html", {"timezones": common_timezones})
Включите форму в template.html которая POST в это представление:
{% load tz %}
{% get_current_timezone as TIME_ZONE %}
<form action="{% url 'set_timezone' %}" method="POST">
{% csrf_token %}
<label for="timezone">Time zone:</label>
<select name="timezone">
{% for city, tz in timezones %}
<option value="{{ tz }}"{% if tz == TIME_ZONE %} selected{% endif %}>{{ city }}</option>
{% endfor %}
</select>
<input type="submit" value="Set">
</form>
Часовые пояса в формах
Когда вы включили поддержку часовых поясов, Django интерпретирует даты и времена, введённые в формах, в текущем часовом поясе и возвращает объекты datetime с учётом часового пояса в cleaned_data.
Преобразованные даты и времена, которые не существуют или являются неоднозначными, потому что они попадают на переход на летнее/зимнее время, будут отображаться как недопустимые значения.
Часовые пояса в шаблонах
При включённой поддержке часовых поясов Django преобразует объекты datetime с учётом часового пояса в текущий часовой пояс при их отображении в шаблонах. Это очень похоже на форматирование локализации.
Предупреждение
Django не преобразует объекты datetime без учёта часового пояса, потому что они могут быть неоднозначными, и потому что ваш код никогда не должен генерировать объекты datetime без учёта часового пояса, когда включена поддержка часовых поясов. Однако вы можете принудительно осуществить преобразование с помощью фильтров шаблонов, описанных ниже.
Преобразование во время местного часового пояса не всегда уместно — вы можете генерировать выходные данные для компьютеров, а не для людей. Следующие фильтры и теги, предоставляемые библиотекой тегов шаблонов tz, позволяют вам управлять преобразованиями часовых поясов.
Теги шаблонов
localtime
Этот тег имеет точно такие же эффекты, как и настройка USE_TZ с точки зрения движка шаблонов. Он позволяет более тонко управлять преобразованием.
Для активации или деактивации преобразования для блока шаблона используйте:
{% load tz %}
{% localtime on %}
{{ value }}
{% endlocaltime %}
{% localtime off %}
{{ value }}
{% endlocaltime %}
Примечание
Значение USE_TZ не учитывается внутри блока {% localtime %}.
timezone
Устанавливает или сбрасывает текущий часовой пояс в содержащемся блоке. Когда текущий часовой пояс сброшен, применяется значение по умолчанию.
{% load tz %}
{% timezone "Europe/Paris" %}
Paris time: {{ value }}
{% endtimezone %}
{% timezone None %}
Server time: {{ value }}
{% endtimezone %}
get_current_timezone
Вы можете получить имя текущего часового пояса, используя тег get_current_timezone:
{% get_current_timezone as TIME_ZONE %}
В качестве альтернативы вы можете активировать обработчик контекста tz() и использовать переменную контекста TIME_ZONE.
Фильтры шаблонов
Эти фильтры принимают как объекты datetime с учётом часового пояса, так и без него. Для целей преобразования они предполагают, что объекты datetime без учёта часового пояса находятся в часовом поясе по умолчанию. Они всегда возвращают объекты datetime с учётом часового пояса.
localtime
Вынуждает преобразование единственного значения в текущий часовой пояс.
Например:
{% load tz %}
{{ value|localtime }}
utc
Вынуждает преобразование единственного значения в UTC.
Например:
{% load tz %}
{{ value|utc }}
timezone
Вынуждает преобразование единственного значения в произвольный часовой пояс.
Аргумент должен быть экземпляром подкласса tzinfo или именем часового пояса.
Например:
{% load tz %}
{{ value|timezone:"Europe/Paris" }}
Руководство по миграции часовых поясов
Вот как мигрировать проект, который был запущен до того, как Django поддерживал часовые пояса.
База данных
PostgreSQL
Бэкенд PostgreSQL хранит объекты datetime как timestamp with time zone. На практике это означает, что он преобразует объекты datetime из часового пояса подключения в UTC при сохранении и из UTC в часовой пояс подключения при извлечении.
Следовательно, если вы используете PostgreSQL, вы можете свободно переключаться между USE_TZ
= False и USE_TZ = True. Часовой пояс подключения к базе данных будет установлен на TIME_ZONE или UTC соответственно, чтобы Django получал правильные объекты datetime во всех случаях. Вам не нужно выполнять какие-либо преобразования данных.
Другие базы данных
Другие бэкенды хранят объекты datetime без информации о часовом поясе. Если вы переключаетесь с USE_TZ = False на USE_TZ = True, вы должны преобразовать ваши данные из местного времени в UTC — что не является детерминированным, если ваше местное время имеет летнее время.
Код
Первый шаг — добавить USE_TZ = True в ваш файл настроек. В этот момент вещи в основном должны работать. Если вы создаёте объекты datetime без учёта часового пояса в своём коде, Django делает их с учётом при необходимости.
Однако эти преобразования могут не выполняться при переходах на летнее время, что означает, что вы ещё не получаете все преимущества поддержки часовых поясов. Кроме того, вы, вероятно, столкнетесь с несколькими проблемами, потому что невозможно сравнить объект datetime без учёта часового пояса с объектом datetime с учётом часового пояса. Поскольку Django теперь предоставляет вам объекты datetime с учётом часового пояса, вы получите исключения всякий раз, когда будете сравнивать объект datetime, полученный из модели или формы, с объектом datetime без учёта часового пояса, который вы создали в своём коде.
Таким образом, второй шаг — переработать ваш код, где вы инициализируете объекты datetime, чтобы сделать их с учётом часового пояса. Это можно сделать постепенно. django.utils.timezone определяет несколько полезных вспомогательных функций для кода совместимости: now(), is_aware(), is_naive(), make_aware() и make_naive().
Наконец, чтобы помочь вам найти код, который нуждается в обновлении, Django выводит предупреждение, когда вы пытаетесь сохранить объект datetime без учёта часового пояса в базе данных:
RuntimeWarning: DateTimeField ModelName.field_name received a naive datetime (2012-01-01 00:00:00) while time zone support is active.
Во время разработки вы можете превратить такие предупреждения в исключения и получить трассировку стека, добавив следующее в ваш файл настроек:
import warnings
warnings.filterwarnings(
"error",
r"DateTimeField .* received a naive datetime",
RuntimeWarning,
r"django\.db\.models\.fields",
)
Файлы fixtures
При сериализации объекта datetime с учётом часового пояса смещение UTC включается, как это:
"2011-09-01T13:20:30+03:00"
В то время как для объекта datetime без учёта часового пояса это не так:
"2011-09-01T13:20:30"
Для моделей с полями DateTimeField это различие делает невозможным создание файла fixtures, который работает как с поддержкой часовых поясов, так и без неё.
Файлы fixtures, сгенерированные с USE_TZ = False, или до Django 1.4, используют формат «без учёта часового пояса». Если ваш проект содержит такие файлы fixtures, после включения поддержки часовых поясов вы увидите RuntimeWarning при их загрузке. Чтобы избавиться от предупреждений, вы должны преобразовать ваши файлы fixtures в формат «с учётом часового пояса».
Вы можете перегенерировать файлы fixtures с помощью loaddata, а затем dumpdata. Либо, если они достаточно маленькие, вы можете отредактировать их, чтобы добавить смещение UTC, соответствующее вашему TIME_ZONE, к каждому сериализованному объекту datetime.
Часто задаваемые вопросы о часовых поясах
Настройка
-
Мне не нужны несколько часовых поясов. Нужно ли включать поддержку часовых поясов?
Да. При включённой поддержке часовых поясов Django использует более точную модель местного времени. Это защищает вас от скрытых и невоспроизводимых ошибок при переходах на летнее время (DST).
Когда вы включаете поддержку часовых поясов, вы столкнетесь с некоторыми ошибками, потому что вы используете объекты datetime без учёта часового пояса, где Django ожидает объекты datetime с учётом часового пояса. Такие ошибки появляются при выполнении тестов. Вы быстро научитесь избегать недопустимых операций.
С другой стороны, ошибки, вызванные отсутствием поддержки часовых поясов, гораздо сложнее предотвратить, диагностировать и исправить. Любая задача, связанная с запланированными задачами или арифметикой datetime, является кандидатом на скрытые ошибки, которые будут беспокоить вас только один или два раза в год.
По этим причинам поддержка часовых поясов включена по умолчанию в новых проектах, и вы должны её сохранить, если у вас нет веских причин этого не делать.
-
Я включил поддержку часовых поясов. Я в безопасности?
Возможно. Вы лучше защищены от ошибок, связанных с DST, но вы всё ещё можете навредить себе, бездумно превращая объекты datetime без учёта часового пояса в объекты datetime с учётом часового пояса и наоборот.
Если ваше приложение подключается к другим системам — например, если оно запрашивает веб-сервис — убедитесь, что объекты datetime правильно указаны. Для безопасной передачи объектов datetime их представление должно включать смещение UTC или их значения должны быть в UTC (или то и другое!).
Наконец, наша система календаря содержит интересные частные случаи. Например, вы не всегда можете напрямую вычесть один год из заданной даты:
>>> import datetime >>> def one_year_before(value): # Wrong example. ... return value.replace(year=value.year - 1) ... >>> one_year_before(datetime.datetime(2012, 3, 1, 10, 0)) datetime.datetime(2011, 3, 1, 10, 0) >>> one_year_before(datetime.datetime(2012, 2, 29, 10, 0)) Traceback (most recent call last): ... ValueError: day is out of range for month
Чтобы правильно реализовать такую функцию, вы должны решить, является ли 2012-02-29 минус один год 2011-02-28 или 2011-03-01, что зависит от ваших бизнес-требований.
-
Как взаимодействовать с базой данных, которая хранит объекты datetime в местном времени?
Установите параметр
TIME_ZONEв соответствующий часовой пояс для этой базы данных в настройкеDATABASES.Это полезно для подключения к базе данных, которая не поддерживает часовые пояса и не управляется Django, когда
USE_TZвключенаTrue.
Устранение неполадок
-
Моё приложение падает с
TypeError: can't compare offset-naiveand offset-aware datetimes– в чём проблема?Давайте воспроизведём эту ошибку, сравнив неявную и явную дату и время:
>>> from django.utils import timezone >>> aware = timezone.now() >>> naive = timezone.make_naive(aware) >>> naive == aware Traceback (most recent call last): ... TypeError: can't compare offset-naive and offset-aware datetimes
Если вы столкнулись с этой ошибкой, скорее всего, ваш код сравнивает эти два объекта:
- объект datetime, предоставленный Django – например, значение, считанное из формы или поля модели. Поскольку вы включили поддержку часовых поясов, он явный.
- объект datetime, сгенерированный вашим кодом, который неявный (иначе вы бы этого не читали).
В целом, правильное решение – изменить ваш код, чтобы использовать явную дату и время вместо неявной.
Если вы пишете подключаемый модуль, который должен работать независимо от значения
USE_TZ, вы можете найтиdjango.utils.timezone.now()полезным. Эта функция возвращает текущую дату и время в виде неявной даты и времени, когдаUSE_TZ = Falseи как явную дату и время, когдаUSE_TZ = True. Вы можете добавлять или вычитатьdatetime.timedeltaпо мере необходимости. -
Я вижу много
RuntimeWarning: DateTimeField received a naive datetime(YYYY-MM-DD HH:MM:SS)while time zone support is active– это плохо?Когда включена поддержка часовых поясов, слой базы данных ожидает получить только явные даты и время от вашего кода. Это предупреждение появляется, когда он получает неявную дату и время. Это указывает на то, что вы не закончили адаптацию своего кода к поддержке часовых поясов. Обратитесь к руководству по миграции за советами по этому процессу.
Тем временем, для обратной совместимости, дата и время рассматриваются как находящиеся в стандартном часовом поясе, что, как правило, и ожидается.
-
now.date()вчера! (или завтра)Если вы всегда использовали неявные даты и время, вы, вероятно, считаете, что можете преобразовать дату и время в дату, вызвав метод
date(). Вы также считаете, чтоdateочень похож наdatetime, за исключением того, что он менее точен.Ничего из этого неверно в среде с поддержкой часовых поясов:
>>> import datetime >>> import zoneinfo >>> paris_tz = zoneinfo.ZoneInfo("Europe/Paris") >>> new_york_tz = zoneinfo.ZoneInfo("America/New_York") >>> paris = datetime.datetime(2012, 3, 3, 1, 30, tzinfo=paris_tz) # This is the correct way to convert between time zones. >>> new_york = paris.astimezone(new_york_tz) >>> paris == new_york, paris.date() == new_york.date() (True, False) >>> paris - new_york, paris.date() - new_york.date() (datetime.timedelta(0), datetime.timedelta(1)) >>> paris datetime.datetime(2012, 3, 3, 1, 30, tzinfo=zoneinfo.ZoneInfo(key='Europe/Paris')) >>> new_york datetime.datetime(2012, 3, 2, 19, 30, tzinfo=zoneinfo.ZoneInfo(key='America/New_York'))Как показывает этот пример, одна и та же дата и время имеют разные даты в зависимости от часового пояса, в котором она представлена. Но настоящая проблема более фундаментальна.
Дата и время представляет собой точку во времени. Она абсолютна: она не зависит ни от чего. Напротив, дата – это понятие календаря. Это период времени, границы которого зависят от часового пояса, в котором рассматривается дата. Как вы можете видеть, эти два понятия фундаментально различаются, и преобразование даты и времени в дату не является детерминированной операцией.
Что это значит на практике?
В целом, следует избегать преобразования
datetimeвdate. Например, вы можете использовать фильтр шаблоновdate, чтобы отобразить только часть даты и времени. Этот фильтр преобразует дату и время в текущий часовой пояс перед форматированием, гарантируя правильность результатов.Если вам действительно нужно выполнить это преобразование самостоятельно, вы должны убедиться, что сначала дата и время преобразованы в соответствующий часовой пояс. Обычно это будет текущий часовой пояс:
>>> from django.utils import timezone >>> timezone.activate(zoneinfo.ZoneInfo("Asia/Singapore")) # For this example, we set the time zone to Singapore, but here's how # you would obtain the current time zone in the general case. >>> current_tz = timezone.get_current_timezone() >>> local = paris.astimezone(current_tz) >>> local datetime.datetime(2012, 3, 3, 8, 30, tzinfo=zoneinfo.ZoneInfo(key='Asia/Singapore')) >>> local.date() datetime.date(2012, 3, 3) -
Я получаю ошибку “
Are time zone definitions for your database installed?”Если вы используете MySQL, см. раздел Определения часовых поясов в примечаниях к MySQL для получения инструкций по загрузке определений часовых поясов.
Использование
-
У меня есть строка
"2012-02-21 10:28:45"и я знаю, что она в часовом поясе"Europe/Helsinki". Как её преобразовать в явную дату и время?Здесь вам нужно создать необходимый
ZoneInfoэкземпляр и присоединить его к неявной дате и времени:>>> import zoneinfo >>> from django.utils.dateparse import parse_datetime >>> naive = parse_datetime("2012-02-21 10:28:45") >>> naive.replace(tzinfo=zoneinfo.ZoneInfo("Europe/Helsinki")) datetime.datetime(2012, 2, 21, 10, 28, 45, tzinfo=zoneinfo.ZoneInfo(key='Europe/Helsinki')) -
Как получить местное время в текущем часовом поясе?
Ну, первый вопрос – вам это действительно нужно?
Вы должны использовать местное время только при взаимодействии с пользователями, а слой шаблонов предоставляет фильтры и теги для преобразования дат и времени в часовой пояс по вашему выбору.
Кроме того, Python умеет сравнивать явные даты и время, учитывая смещения UTC при необходимости. Гораздо проще (и, возможно, быстрее) написать весь код модели и представления в UTC. Поэтому в большинстве случаев даты и время в UTC, возвращаемые
django.utils.timezone.now(), будут достаточны.Однако, ради полноты, если вам действительно нужно местное время в текущем часовом поясе, вот как его получить:
>>> from django.utils import timezone >>> timezone.localtime(timezone.now()) datetime.datetime(2012, 3, 3, 20, 10, 53, 873365, tzinfo=zoneinfo.ZoneInfo(key='Europe/Paris'))
В этом примере текущий часовой пояс –
"Europe/Paris". -
Как увидеть все доступные часовые пояса?
zoneinfo.available_timezones()предоставляет набор всех допустимых ключей для часовых поясов IANA, доступных вашей системе. См. документацию для использования.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.2/topics/i18n/timezones/