Часовые пояса
Обзор
При включенном поддержке часовых поясов Django хранит информацию о датах и временах в формате UTC в базе данных, использует объекты datetime с учетом часового пояса внутри и преобразует их для часового пояса конечного пользователя в формах. Шаблоны будут использовать по умолчанию часовой пояс, но это можно обновить до часового пояса конечного пользователя с помощью фильтров и тегов.
Это удобно, если ваши пользователи находятся в нескольких часовых поясах, и вы хотите отображать информацию о датах и временах в соответствии с часовым поясом каждого пользователя.
Даже если ваш сайт доступен только в одном часовом поясе, все равно рекомендуется хранить данные в UTC в вашей базе данных. Основная причина — летнее время (DST). Многие страны используют систему DST, где время смещается вперед весной и назад осенью. Если вы работаете с местным временем, вы, вероятно, столкнетесь с ошибками дважды в год, когда происходят переходы. Это, вероятно, не имеет значения для вашего блога, но это проблема, если вы переплачиваете или недоплачиваете своим клиентам на один час дважды в год, каждый год. Решением этой проблемы является использование UTC в коде и использование местного времени только при взаимодействии с конечными пользователями.
Поддержка часовых поясов включена по умолчанию. Чтобы отключить ее, установите USE_TZ =
False в вашем файле настроек.
Поддержка часовых поясов использует zoneinfo, которая является частью стандартной библиотеки Python начиная с Python 3.9.
Если у вас возникли проблемы, начните с ЧАВО по часовым поясам.
Концепции
Простые и осознанные объекты datetime
Объекты datetime.datetime Python имеют атрибут tzinfo, который может использоваться для хранения информации о часовом поясе, представленной в виде экземпляра подкласса datetime.tzinfo. Когда этот атрибут установлен и описывает смещение, объект datetime является осознанным. В противном случае он простой.
Вы можете использовать is_aware() и is_naive(), чтобы определить, являются ли даты и времена осознанными или простыми.
Когда поддержка часовых поясов отключена, 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 не работает надежно для часовых поясов с переходом на летнее время. Использование UTC, как правило, безопасно; если вы используете другие часовые пояса, вы должны внимательно изучить документацию zoneinfo.
Примечание
Объекты datetime.time Python также имеют атрибут tzinfo, а PostgreSQL имеет соответствующий тип time with time zone. Однако, как говорится в документации PostgreSQL, этот тип «обладает свойствами, которые приводят к сомнительной полезности».
Django поддерживает только простые объекты времени и выведет исключение, если вы попытаетесь сохранить осознанный объект времени, так как часовой пояс для времени без связанной даты не имеет смысла.
Интерпретация простых объектов 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, это полезно для сохранения обратной совместимости с приложениями, которые по-прежнему полагаются на местное время. Однако, как объяснялось выше, это не совсем надежно, и вы всегда должны работать с осознанными датами и временами в UTC в собственном коде. Например, используйте fromtimestamp() и установите параметр tz в utc.
Выбор текущего часового пояса
Текущий часовой пояс эквивалентен текущему локали для переводов. Однако нет эквивалента заголовку Accept-Language HTTP, который Django мог бы использовать для автоматического определения часового пояса пользователя. Вместо этого Django предоставляет функции выбора часовых поясов. Используйте их для построения логики выбора часового пояса, которая имеет смысл для вас.
Большинство веб-сайтов, которые учитывают часовые пояса, спрашивают пользователей, в каком часовом поясе они находятся, и хранят эту информацию в профиле пользователя. Для анонимных пользователей они используют часовой пояс своей основной аудитории или UTC. zoneinfo.available_timezones() предоставляет набор доступных часовых поясов, которые вы можете использовать для построения карты от вероятных мест до часовых поясов.
Вот пример, который хранит текущий часовой пояс в сессии. (Он полностью пропускает обработку ошибок ради простоты.)
Добавьте следующий middleware в 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, позволяют управлять преобразованиями часовых поясов.
Фильтры шаблонов
Эти фильтры принимают как объекты 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. Часовой пояс подключения к базе данных будет установлен на DATABASE-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",
)
Файлы фикстур
При сериализации объекта datetime с учётом часового пояса смещение UTC включается, так:
"2011-09-01T13:20:30+03:00"
В то время как для объекта datetime без учёта часового пояса это не так:
"2011-09-01T13:20:30"
Для моделей с полями DateTimeField это различие делает невозможным создание фикстуры, которая работает как с поддержкой часовых поясов, так и без неё.
Файлы фикстур, сгенерированные с помощью USE_TZ = False, или до Django 1.4, используют формат «без учёта часового пояса». Если ваш проект содержит такие файлы фикстур, после включения поддержки часовых поясов вы увидите RuntimeWarning при их загрузке. Чтобы избавиться от предупреждений, вы должны преобразовать ваши файлы фикстур в формат «с учётом часового пояса».
Вы можете перегенерировать файлы фикстур с помощью loaddata и затем dumpdata. Или, если они достаточно небольшие, вы можете изменить их, добавив смещение UTC, соответствующее вашей настройке TIME_ZONE, к каждой сериализованной дате и времени.
ЧАВО
Настройка
-
Мне не нужны несколько часовых поясов. Стоит ли активировать поддержку часовых поясов?
Да. Когда включена поддержка часовых поясов, Django использует более точную модель местного времени. Это защищает вас от скрытых и невоспроизводимых ошибок при переходе на летнее время (DST).
Когда вы активируете поддержку часовых поясов, вы столкнетесь с некоторыми ошибками, потому что используете наивные значения datetime, а Django ожидает значения datetime с учетом часового пояса. Такие ошибки появляются при запуске тестов. Вы быстро научитесь избегать некорректных операций.
С другой стороны, ошибки, вызванные отсутствием поддержки часовых поясов, гораздо сложнее предотвратить, диагностировать и исправить. Любая задача, связанная с запланированными задачами или арифметикой 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— в чём проблема?Давайте воспроизведём эту ошибку, сравнивая наивное и осознанное значение datetime:
>>> 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, сгенерированное вашим кодом, которое наивно (иначе вы бы этого не читали).
В общем случае правильным решением является изменение вашего кода на использование осознанного значения datetime.
Если вы пишете подключаемое приложение, которое должно работать независимо от значения
USE_TZ, вы можете найти полезной функциюdjango.utils.timezone.now(). Эта функция возвращает текущую дату и время в виде наивного значения datetime, когдаUSE_TZ = False, и в виде осознанного значения datetime, когдаUSE_TZ = True. Вы можете добавлять или вычитатьdatetime.timedeltaпо мере необходимости. -
Я вижу много сообщений об ошибке
RuntimeWarning: DateTimeField received a naive datetime(YYYY-MM-DD HH:MM:SS)while time zone support is active— это плохо?Когда включена поддержка часовых поясов, уровень базы данных ожидает получать только осознанные значения datetime от вашего кода. Это предупреждение возникает, когда он получает наивное значение datetime. Это указывает на то, что вы не закончили переносить свой код для поддержки часовых поясов. Обратитесь к руководству по миграции за советами по этому процессу.
Тем временем, для обратной совместимости, значение datetime рассматривается как находящееся в по умолчанию часовом поясе, что, как правило, ожидается.
-
now.date()вчера! (или завтра)Если вы всегда использовали наивные значения datetime, вы, вероятно, считаете, что можете преобразовать значение datetime в дату, вызвав его метод
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 имеет разную дату в зависимости от часового пояса, в котором оно представлено. Но реальная проблема более фундаментальна.
Значение datetime представляет собой точку во времени. Оно абсолютное: не зависит ни от чего. Напротив, дата — это понятие календаря. Это период времени, границы которого зависят от часового пояса, в котором рассматривается дата. Как видите, эти два понятия фундаментально различаются, и преобразование значения datetime в дату не является детерминированной операцией.
Что это означает на практике?
В общем случае следует избегать преобразования
datetimeвdate. Например, вы можете использовать фильтр шаблоновdateдля отображения только части даты значения datetime. Этот фильтр преобразует значение datetime в текущий часовой пояс перед форматированием, гарантируя корректное отображение результатов.Если вам действительно нужно выполнить преобразование самостоятельно, вы должны сначала убедиться, что значение datetime преобразовано в соответствующий часовой пояс. Обычно это будет текущий часовой пояс:
>>> 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". Как преобразовать её в осознанное значение datetime?Здесь вам нужно создать необходимый экземпляр
ZoneInfoи прикрепить его к наивному значению datetime:>>> 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')) -
Как получить местное время в текущем часовом поясе?
Ну, первый вопрос — вам это действительно нужно?
Вы должны использовать местное время только при взаимодействии с людьми, а уровень шаблонов предоставляет фильтры и теги для преобразования значений datetime в часовой пояс по вашему выбору.
Кроме того, Python знает, как сравнивать осознанные значения datetime, учитывая смещения UTC при необходимости. Гораздо проще (и, возможно, быстрее) написать весь ваш код модели и представления в UTC. Таким образом, в большинстве случаев значения datetime в 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/5.2/topics/i18n/timezones/