Часовые пояса¶
Быстрый обзор¶
Когда поддержка часовых поясов включена, Django хранит информацию о времени в UTC в базе данных, использует внутренние объекты времени с учетом часовых поясов и переводит их в часовой пояс конечного пользователя в шаблонах и формах.
Это удобно, если ваши пользователи живут в нескольких часовых поясах и вы хотите отображать информацию о времени в соответствии с настенными часами каждого пользователя.
Even if your website is available in only one time zone, it’s still good practice to store data in UTC in your database. The main reason is daylight saving time (DST). Many countries have a system of DST, where clocks are moved forward in spring and backward in autumn. If you’re working in local time, you’re likely to encounter errors twice a year, when the transitions happen. This probably doesn’t matter for your blog, but it’s a problem if you over bill or under bill your customers by one hour, twice a year, every year. The solution to this problem is to use UTC in the code and use local time only when interacting with end users.
Time zone support is disabled by default. To enable it, set USE_TZ =
True
in your settings file.
Примечание
В Django 5.0 поддержка часовых поясов будет включена по умолчанию.
Поддержка часовых поясов использует zoneinfo
, который является частью стандартной библиотеки Python, начиная с Python 3.9. Пакет backports.zoneinfo
автоматически устанавливается вместе с Django, если вы используете Python 3.8.
Примечание
Файл по умолчанию 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()
Предупреждение
Dealing with aware datetime objects isn’t always intuitive. For instance,
the tzinfo
argument of the standard datetime constructor doesn’t work
reliably for time zones with DST. Using UTC is generally safe; if you’re
using other time zones, you should review the zoneinfo
documentation carefully.
Примечание
Объекты 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
и текущего часового пояса.
When USE_TZ
is True
, this is useful to preserve
backwards-compatibility with applications that still rely on local time.
However, as explained above, this isn’t
entirely reliable, and you should always work with aware datetimes in UTC
in your own code. For instance, use fromtimestamp()
and set the tz
parameter to utc
.
Выбор текущего часового пояса¶
Текущий часовой пояс эквивалентен текущему locale для переводов. Однако не существует эквивалента HTTP-заголовка Accept-Language
, который Django мог бы использовать для автоматического определения часового пояса пользователя. Вместо этого Django предоставляет time zone selection functions. Используйте их для построения логики выбора часового пояса, которая имеет смысл для вас.
Most websites that care about time zones ask users in which time zone they live
and store this information in the user’s profile. For anonymous users, they use
the time zone of their primary audience or UTC.
zoneinfo.available_timezones()
provides a set of available timezones that
you can use to build a map from likely locations to time zones.
Вот пример, который хранит текущий часовой пояс в сессии. (Для простоты в нем полностью пропущена обработка ошибок).
Добавьте следующее промежуточное программное обеспечение в 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
.
Converted datetimes that don’t exist or are ambiguous because they fall in a DST transition will be reported as invalid values.
Вывод в шаблонах с учетом часового пояса¶
Когда вы включаете поддержку часовых поясов, 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¶
Настройка¶
Мне не нужно несколько часовых поясов. Должен ли я включить поддержку часовых поясов?
Yes. When time zone support is enabled, Django uses a more accurate model of local time. This shields you from subtle and unreproducible bugs around daylight saving time (DST) transitions.
Когда вы включите поддержку часовых поясов, вы столкнетесь с некоторыми ошибками, потому что вы используете наивные времена дат там, где Django ожидает знающие времена дат. Такие ошибки проявляются при выполнении тестов. Вы быстро узнаете, как избежать некорректных операций.
С другой стороны, ошибки, вызванные отсутствием поддержки часовых поясов, гораздо сложнее предотвратить, диагностировать и исправить. Все, что связано с запланированными задачами или арифметикой времени, является кандидатом на тонкие ошибки, которые будут кусать вас только один или два раза в год.
По этим причинам поддержка часовых поясов включена по умолчанию в новых проектах, и вы должны сохранить ее, если у вас нет очень веских причин не делать этого.
Я включил поддержку часовых поясов. Я в безопасности?
Возможно. Вы лучше защищены от ошибок, связанных с DST, но вы все еще можете прострелить себе ногу, неосторожно превращая наивные времена дат в осознанные времена дат, и наоборот.
If your application connects to other systems – for instance, if it queries a web service – make sure datetimes are properly specified. To transmit datetimes safely, their representation should include the UTC offset, or their values should be in UTC (or both!).
Наконец, наша календарная система содержит интересные крайние случаи. Например, вы не всегда можете вычесть один год непосредственно из заданной даты:
>>> 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, доступных для вашей системы. Соображения по использованию см. в документации.