Часовые пояса¶
Быстрый обзор¶
Когда поддержка часовых поясов включена, Django хранит информацию о времени в UTC в базе данных, использует внутренние объекты времени с учетом часовых поясов и переводит их в часовой пояс конечного пользователя в шаблонах и формах.
Это удобно, если ваши пользователи живут в нескольких часовых поясах и вы хотите отображать информацию о времени в соответствии с настенными часами каждого пользователя.
Даже если ваш сайт доступен только в одном часовом поясе, хранить данные в базе данных в формате UTC - хорошая практика. Основная причина - переход на летнее время (DST). Во многих странах существует система DST, при которой часы переводятся вперед весной и назад осенью. Если вы работаете по местному времени, вы, скорее всего, столкнетесь с ошибками два раза в год, когда происходит переход. Возможно, для вашего блога это не имеет значения, но это проблема, если вы каждый год дважды в год завышаете или занижаете счет своим клиентам на один час. Решение этой проблемы - использовать UTC в коде и использовать местное время только при взаимодействии с конечными пользователями.
По умолчанию поддержка часовых поясов отключена. Чтобы включить ее, установите значение USE_TZ = True
в файле настроек.
Примечание
В Django 5.0 поддержка часовых поясов будет включена по умолчанию.
Поддержка часовых поясов использует zoneinfo
, который является частью стандартной библиотеки Python, начиная с Python 3.9. Пакет backports.zoneinfo
автоматически устанавливается вместе с Django, если вы используете Python 3.8.
Добавлена поддержка реализаций часовых поясов не``pytz``.
zoneinfo
стал реализацией часового пояса по умолчанию. Вы можете продолжать использовать pytz в течение цикла выпуска 4.x с помощью настройки USE_DEPRECATED_PYTZ
.
Примечание
Файл по умолчанию settings.py
, созданный django-admin startproject
, включает USE_TZ = True
для удобства.
Если вы боретесь с конкретной проблемой, начните с time zone FAQ.
Концепции¶
Наивные и осознанные объекты времени суток¶
Объекты Python datetime.datetime
имеют атрибут tzinfo
, который может быть использован для хранения информации о часовом поясе, представленной в виде экземпляра подкласса datetime.tzinfo
. Когда этот атрибут установлен и описывает смещение, объект datetime является aware. В противном случае он является наивным.
Вы можете использовать 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 не работает надежно для часовых поясов с DST. Использование UTC обычно безопасно; если вы используете другие часовые пояса, вам следует внимательно изучить документацию zoneinfo
.
Примечание
Объекты Python datetime.time
также имеют атрибут tzinfo
, а PostgreSQL имеет соответствующий тип time with time zone
. Однако, как говорится в документации PostgreSQL, этот тип «проявляет свойства, которые приводят к сомнительной полезности».
Django поддерживает только наивные объекты времени, и если вы попытаетесь сохранить объект времени с информацией, то возникнет исключение, поскольку временная зона для времени без связанной даты не имеет смысла.
Интерпретация наивных объектов времени суток¶
Когда USE_TZ
становится True
, Django все еще принимает наивные объекты datetime, чтобы сохранить обратную совместимость. Когда уровень базы данных получает такой объект, он пытается дать о нем знать, интерпретируя его в default time zone, и выдает предупреждение.
К сожалению, во время перехода на летнее время некоторые объекты времени не существуют или являются неоднозначными. Поэтому всегда следует создавать объекты datetime, когда включена поддержка часовых поясов. (Примеры использования атрибута Using ZoneInfo section of the zoneinfo docs
для указания смещения, которое должно применяться к дате во время перехода на летнее время, см. в fold
).
На практике это редко является проблемой. Django предоставляет вам известные объекты datetime в моделях и формах, и чаще всего новые объекты datetime создаются из существующих с помощью арифметики timedelta
. Единственное время, которое часто создается в коде приложения - это текущее время, и timezone.now()
автоматически делает то, что нужно.
Часовой пояс по умолчанию и текущий часовой пояс¶
Часовой пояс по умолчанию - это часовой пояс, определяемый настройкой TIME_ZONE
.
текущий часовой пояс - это часовой пояс, который используется для рендеринга.
Вы должны установить текущий часовой пояс на фактический часовой пояс конечного пользователя с помощью activate()
. В противном случае будет использоваться часовой пояс по умолчанию.
Примечание
Как объясняется в документации к TIME_ZONE
, Django устанавливает переменные окружения так, чтобы его процесс работал в часовом поясе по умолчанию. Это происходит независимо от значения USE_TZ
и текущего часового пояса.
Когда USE_TZ
равно True
, это полезно для сохранения обратной совместимости с приложениями, которые все еще полагаются на местное время. Однако, as explained above, это не совсем надежно, и вы всегда должны работать с известным временем в UTC в своем собственном коде. Например, используйте fromtimestamp()
и установите параметр tz
в значение utc
.
Выбор текущего часового пояса¶
Текущий часовой пояс эквивалентен текущему locale для переводов. Однако не существует эквивалента HTTP-заголовка Accept-Language
, который Django мог бы использовать для автоматического определения часового пояса пользователя. Вместо этого Django предоставляет time zone selection functions. Используйте их для построения логики выбора часового пояса, которая имеет смысл для вас.
Большинство сайтов, заботящихся о часовых поясах, спрашивают пользователей, в каком часовом поясе они живут, и хранят эту информацию в профиле пользователя. Для анонимных пользователей используется часовой пояс их основной аудитории или 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 интерпретирует даты, введенные в формах, в current time zone и возвращает осознанные объекты datetime в cleaned_data
.
Преобразованные даты, которые не существуют или являются неоднозначными, поскольку попадают в переходный период DST, будут представлены как недопустимые значения.
Вывод в шаблонах с учетом часового пояса¶
Когда вы включаете поддержку часовых поясов, Django преобразует объекты datetime в current time zone, когда они отображаются в шаблонах. Это ведет себя очень похоже на format localization.
Предупреждение
Django не конвертирует наивные объекты datetime, потому что они могут быть неоднозначными, и потому что ваш код никогда не должен создавать наивные объекты datetime, если включена поддержка часовых поясов. Тем не менее, вы можете принудительно конвертировать их с помощью фильтров шаблонов, описанных ниже.
Конвертация в местное время не всегда уместна - возможно, вы генерируете вывод для компьютеров, а не для людей. Следующие фильтры и теги, предоставляемые библиотекой тегов шаблона tz
, позволяют управлять преобразованиями часовых поясов.
Теги шаблона¶
localtime
¶
Включает или выключает преобразование известных объектов datetime в текущий часовой пояс в содержащемся блоке.
Этот тег имеет точно такие же эффекты, как и параметр 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 %}
Шаблонные фильтры¶
Эти фильтры принимают как известные, так и наивные даты. Для целей преобразования они предполагают, что наивные времена дат находятся в часовом поясе по умолчанию. Они всегда возвращают известные времена.
localtime
¶
Принудительное преобразование одного значения в текущий часовой пояс.
Например:
{% load tz %}
{{ value|localtime }}
Руководство по миграции¶
Вот как перенести проект, который был начат до того, как Django поддерживал часовые пояса.
База данных¶
PostgreSQL¶
Бэкенд PostgreSQL хранит время даты как timestamp with time zone
. На практике это означает, что при хранении он преобразует время дат из часового пояса соединения в UTC, а при извлечении - из UTC в часовой пояс соединения.
Как следствие, если вы используете PostgreSQL, вы можете свободно переключаться между USE_TZ = False
и USE_TZ = True
. Часовой пояс соединения с базой данных будет установлен на TIME_ZONE
или UTC
соответственно, так что Django получит правильное время дат во всех случаях. Вам не нужно выполнять никаких преобразований данных.
Другие базы данных¶
Другие бэкенды хранят данные без информации о часовом поясе. Если вы переключаетесь с USE_TZ = False
на USE_TZ = True
, вы должны преобразовать ваши данные из местного времени в UTC - что не является детерминированным, если ваше местное время имеет DST.
Код¶
Первый шаг - добавить USE_TZ = True
в файл настроек. На этом этапе все должно в основном работать. Если вы создаете наивные объекты datetime в своем коде, Django делает их известными, когда это необходимо.
Однако эти преобразования могут не сработать при переходе на летнее время, что означает, что вы еще не получили всех преимуществ поддержки часовых поясов. Кроме того, вы можете столкнуться с несколькими проблемами, потому что невозможно сравнить наивное время даты с известным временем даты. Поскольку Django теперь предоставляет вам знающие времена, вы будете получать исключения, когда будете сравнивать время, полученное из модели или формы, с наивным временем, которое вы создали в своем коде.
Поэтому вторым шагом будет рефакторинг вашего кода везде, где вы инстанцируете объекты datetime, чтобы сделать их осознанными. Это можно сделать постепенно. django.utils.timezone
определяет несколько удобных помощников для кода совместимости: now()
, is_aware()
, is_naive()
, make_aware()
и make_naive()
.
Наконец, чтобы помочь вам найти код, который нуждается в обновлении, Django выдает предупреждение, когда вы пытаетесь сохранить наивное время даты в базе данных:
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',
)
Приспособления¶
При сериализации осознаваемого времени даты включается смещение UTC, например, так:
"2011-09-01T13:20:30+03:00"
В то время как для наивного времени даты это не так:
"2011-09-01T13:20:30"
Для моделей с DateTimeField
с эта разница делает невозможным написание приспособления, которое работает как с поддержкой часовых поясов, так и без нее.
Фикстуры, созданные с помощью USE_TZ = False
, или до Django 1.4, используют «наивный» формат. Если ваш проект содержит такие фикстуры, то после включения поддержки часовых поясов вы увидите RuntimeWarning
при их загрузке. Чтобы избавиться от предупреждений, вы должны преобразовать ваши фикстуры в формат «aware».
Вы можете регенерировать фикстуры с помощью loaddata
, затем dumpdata
. Или, если они достаточно малы, вы можете отредактировать их, чтобы добавить смещение UTC, соответствующее вашему TIME_ZONE
, к каждому сериализованному времени даты.
FAQ¶
Настройка¶
Мне не нужно несколько часовых поясов. Должен ли я включить поддержку часовых поясов?
Да. Когда включена поддержка часовых поясов, Django использует более точную модель местного времени. Это защищает вас от тонких и невоспроизводимых ошибок, связанных с переходом на летнее время (DST).
Когда вы включите поддержку часовых поясов, вы столкнетесь с некоторыми ошибками, потому что вы используете наивные времена дат там, где Django ожидает знающие времена дат. Такие ошибки проявляются при выполнении тестов. Вы быстро узнаете, как избежать некорректных операций.
С другой стороны, ошибки, вызванные отсутствием поддержки часовых поясов, гораздо сложнее предотвратить, диагностировать и исправить. Все, что связано с запланированными задачами или арифметикой времени, является кандидатом на тонкие ошибки, которые будут кусать вас только один или два раза в год.
По этим причинам поддержка часовых поясов включена по умолчанию в новых проектах, и вы должны сохранить ее, если у вас нет очень веских причин не делать этого.
Я включил поддержку часовых поясов. Я в безопасности?
Возможно. Вы лучше защищены от ошибок, связанных с DST, но вы все еще можете прострелить себе ногу, неосторожно превращая наивные времена дат в осознанные времена дат, и наоборот.
Если ваше приложение подключается к другим системам - например, запрашивает веб-службу - убедитесь, что время даты указано правильно. Для безопасной передачи временных данных их представление должно включать смещение 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, что зависит от ваших бизнес-требований.
Как взаимодействовать с базой данных, в которой хранятся даты в местном времени?
Установите в параметре
TIME_ZONE
соответствующий часовой пояс для этой базы данных в параметреDATABASES
.Это полезно для подключения к базе данных, которая не поддерживает часовые пояса и не управляется Django, когда
USE_TZ
становитсяTrue
.
Устранение неполадок¶
Мое приложение аварийно завершается с
TypeError: can't compare offset-naive
and 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
Если вы столкнулись с этой ошибкой, скорее всего, ваш код сравнивает эти две вещи:
- время даты, предоставленное Django - например, значение, считанное из формы или поля модели. Поскольку вы включили поддержку часовых поясов, это известно.
- дататайм, сгенерированный вашим кодом, что наивно (иначе вы бы не читали это).
Как правило, правильным решением будет изменить ваш код, чтобы вместо него использовать осознаваемое время даты.
Если вы пишете подключаемое приложение, которое должно работать независимо от значения
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
**- это плохо? **Когда включена поддержка часовых поясов, уровень базы данных ожидает, что ваш код будет получать от вас только известные значения времени. Это предупреждение появляется, когда он получает наивное время даты. Это указывает на то, что вы не закончили перенос вашего кода для поддержки часовых поясов. Пожалуйста, обратитесь к migration guide за советами по этому процессу.
В то же время, для обратной совместимости, время даты считается в часовом поясе по умолчанию, что обычно соответствует вашим ожиданиям.
<<< 0 >> ** это вчера! (или завтра)**
Если вы всегда пользовались наивными временами дат, вы, вероятно, считаете, что можно преобразовать время даты в дату, вызвав его метод
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
и присоединить его к наивному 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'))
Как я могу получить местное время в текущем часовом поясе?
Первый вопрос - действительно ли вам это нужно?
Вы должны использовать местное время только при взаимодействии с людьми, и слой шаблонов предоставляет filters and tags для преобразования времени даты в часовой пояс по вашему выбору.
Кроме того, 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, доступных для вашей системы. Соображения по использованию см. в документации.