Часовые пояса
Обзор
При включённой поддержке часовых поясов Django хранит информацию о датах и временах в UTC в базе данных, использует объекты datetime с учётом часового пояса внутри и преобразует их для часового пояса конечного пользователя в шаблонах и формах.
Это удобно, если ваши пользователи проживают в нескольких часовых поясах, и вы хотите отображать информацию о датах и временах в соответствии с местным временем каждого пользователя.
Даже если ваш веб-сайт доступен только в одном часовом поясе, всё равно рекомендуется хранить данные в UTC в базе данных. Основная причина — летнее время (DST). Многие страны используют систему DST, где время меняется вперёд весной и назад осенью. Если вы работаете с местным временем, вы, скорее всего, столкнётесь с ошибками дважды в год, когда происходят переходы. (Документация pytz подробно рассматривает эти проблемы.) Это, вероятно, не имеет значения для вашего блога, но это проблема, если вы переплачиваете или недоплачиваете клиентам на час дважды в год каждый год. Решением этой проблемы является использование UTC в коде и использование местного времени только при взаимодействии с конечными пользователями.
Поддержка часовых поясов по умолчанию отключена. Для её включения установите USE_TZ =
True в вашем файле настроек. Поддержка часовых поясов использует pytz, который устанавливается при установке Django.
Более старые версии не требуют pytz или не устанавливают его автоматически.
Примечание
Файл по умолчанию settings.py созданный django-admin
startproject включает USE_TZ = True для удобства.
Примечание
Также есть независимый, но связанный параметр USE_L10N, который управляет тем, будет ли Django активировать локализацию форматов. Подробнее см. Локализация форматов.
Если вы столкнулись с какой-то проблемой, начните с Раздела часто задаваемых вопросов по часовым поясам.
Концепции
Объекты 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, как правило, безопасно; если вы используете другие часовые пояса, вы должны внимательно изучить документацию pytz.
Примечание
Объекты datetime.time Python также имеют атрибут tzinfo, а PostgreSQL имеет соответствующий тип time with time zone. Однако, как говорится в документации PostgreSQL, этот тип «имеет свойства, которые ведут к сомнительной полезности».
Django поддерживает только объекты времени без учёта часового пояса и вызовет исключение, если вы попытаетесь сохранить объект времени с учётом часового пояса, так как часовой пояс для времени без привязанной даты не имеет смысла.
Интерпретация объектов datetime без учёта часового пояса
Когда USE_TZ равняется True, Django всё ещё принимает объекты datetime без учёта часового пояса, чтобы сохранить обратную совместимость. Когда слой базы данных получает такой объект, он пытается сделать его с учётом часового пояса, интерпретируя его в по умолчанию, и выводит предупреждение.
К сожалению, во время переходов на летнее время некоторые даты и времена не существуют или являются неоднозначными. В таких ситуациях pytz выводит исключение. Вот почему вы всегда должны создавать объекты datetime с учётом часового пояса, когда включена поддержка часовых поясов.
На практике это редко проблема. Django предоставляет вам объекты datetime с учётом часового пояса в моделях и формах, и чаще всего новые объекты datetime создаются из существующих с помощью арифметики timedelta. Единственный объект datetime, который часто создаётся в коде приложения, — это текущее время, и timezone.now() автоматически делает всё правильно.
По умолчанию часовой пояс и текущий часовой пояс
По умолчанию часовой пояс — это часовой пояс, определённый параметром TIME_ZONE.
Текущий часовой пояс — это часовой пояс, используемый для рендеринга.
Вы должны установить текущий часовой пояс на фактический часовой пояс конечного пользователя с помощью activate(). В противном случае используется часовой пояс по умолчанию.
Примечание
Как объяснено в документации TIME_ZONE, Django устанавливает переменные окружения, чтобы его процесс работал в часовом поясе по умолчанию. Это происходит независимо от значения USE_TZ и текущего часового пояса.
Когда USE_TZ равно True, это полезно для сохранения обратной совместимости с приложениями, которые по-прежнему полагаются на местное время. Однако, как объяснено выше, это не совсем надёжно, и вы всегда должны работать с объектами datetime с учётом часового пояса в UTC в собственном коде. Например, используйте fromtimestamp() и установите параметр tz в utc.
Выбор текущего часового пояса
Текущий часовой пояс эквивалентен текущему локали для переводов. Однако нет эквивалента заголовку Accept-Language HTTP, который Django мог бы использовать для автоматического определения часового пояса пользователя. Вместо этого Django предоставляет функции выбора часового пояса. Используйте их, чтобы построить логику выбора часового пояса, которая имеет смысл для вас.
Большинство веб-сайтов, которые заботятся о часовых поясах, просто спрашивают пользователей, в каком часовом поясе они проживают, и сохраняют эту информацию в профиле пользователя. Для анонимных пользователей они используют часовой пояс своей основной аудитории или UTC. pytz предоставляет помощники, такие как список часовых поясов по странам, которые вы можете использовать для предварительного выбора наиболее вероятных вариантов.
Вот пример, который сохраняет текущий часовой пояс в сессии. (Он полностью пропускает обработку ошибок ради простоты.)
Добавьте следующий middleware в MIDDLEWARE:
import pytz
from django.utils import timezone
from django.utils.deprecation import MiddlewareMixin
class TimezoneMiddleware(MiddlewareMixin):
def process_request(self, request):
tzname = request.session.get('django_timezone')
if tzname:
timezone.activate(pytz.timezone(tzname))
else:
timezone.deactivate()
Создайте представление, которое может установить текущий часовой пояс:
from django.shortcuts import redirect, render
def set_timezone(request):
if request.method == 'POST':
request.session['django_timezone'] = request.POST['timezone']
return redirect('/')
else:
return render(request, 'template.html', {'timezones': pytz.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 tz in timezones %}
<option value="{{ tz }}"{% if tz == TIME_ZONE %} selected{% endif %}>{{ tz }}</option>
{% endfor %}
</select>
<input type="submit" value="Set" />
</form>
Часовой пояс в формах
При включённой поддержке часовых поясов Django интерпретирует даты и время, введённые в формах, в текущем часовом поясе и возвращает объекты datetime с учётом часового пояса в cleaned_data.
Если текущий часовой пояс выводит исключение для дат и времён, которые не существуют или являются неоднозначными, потому что они попадают в переход на летнее время (часовые пояса, предоставляемые pytz, делают это), такие даты и времена будут сообщены как недопустимые значения.
Часовой пояс в шаблонах
При включённой поддержке часовых поясов 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 — что не является детерминированным, если ваше местное время имеет DST.
Код
Первый шаг — добавить USE_TZ = True в свой файл настроек. На этом этапе все должно работать в основном. Если вы создаёте объекты datetime без временной зоны в своём коде, Django делает их осознанными при необходимости.
Однако эти преобразования могут потерпеть неудачу во время переходов DST, что означает, что вы ещё не получаете всех преимуществ поддержки временных зон. Кроме того, вы, вероятно, столкнётесь с несколькими проблемами, потому что невозможно сравнить 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, к каждой сериализованной datetime.
Часто задаваемые вопросы
Настройка
-
Мне не нужны несколько часовых поясов. Стоит ли мне включать поддержку временных зон?
Да. Когда поддержка временных зон включена, Django использует более точную модель местного времени. Это защищает вас от тонких и невоспроизводимых ошибок при переходах на летнее время (DST).
Когда вы включаете поддержку временных зон, вы столкнётесь с некоторыми ошибками, потому что вы используете datetime без временной зоны, где Django ожидает datetime с временной зоной. Такие ошибки появляются при запуске тестов, и их легко исправить. Вы быстро научитесь избегать недопустимых операций.
С другой стороны, ошибки, вызванные отсутствием поддержки временных зон, гораздо сложнее предотвратить, диагностировать и устранить. Любой процесс, который включает запланированные задачи или арифметику datetime, является кандидатом на тонкие ошибки, которые укусят вас только один-два раза в год.
По этим причинам поддержка временных зон включена по умолчанию в новых проектах, и вы должны её сохранить, если у вас нет очень веских причин для отказа.
-
Я включил поддержку временных зон. Я в безопасности?
Возможно. Вы лучше защищены от ошибок, связанных с DST, но вы всё ещё можете навредить себе, бездумно превращая datetime без временной зоны в datetime с временной зоной и наоборот.
Если ваше приложение подключается к другим системам — например, если оно запрашивает веб-сервис — убедитесь, что datetime правильно указаны. Чтобы безопасно передавать datetime, их представление должно включать смещение UTC или их значения должны быть в UTC (или оба!).
Наконец, наша система календаря содержит интересные ловушки для компьютеров:
>>> import datetime >>> def one_year_before(value): # DON'T DO THAT! ... 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– в чем проблема?Давайте воспроизведем эту ошибку, сравнив неявную и явную дату и время:
>>> import datetime >>> from django.utils import timezone >>> naive = datetime.datetime.utcnow() >>> aware = timezone.now() >>> 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 pytz >>> paris_tz = pytz.timezone("Europe/Paris") >>> new_york_tz = pytz.timezone("America/New_York") >>> paris = paris_tz.localize(datetime.datetime(2012, 3, 3, 1, 30)) # This is the correct way to convert between time zones with pytz. >>> new_york = new_york_tz.normalize(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=<DstTzInfo 'Europe/Paris' CET+1:00:00 STD>) >>> new_york datetime.datetime(2012, 3, 2, 19, 30, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)Как показывает этот пример, у одного и того же объекта datetime разная дата в зависимости от часового пояса, в котором он представлен. Но настоящая проблема более фундаментальна.
Объект datetime представляет собой точку во времени. Он абсолютен: не зависит ни от чего. Напротив, дата — это понятия календаря. Это период времени, границы которого зависят от часового пояса, в котором рассматривается дата. Как вы можете видеть, эти два понятия фундаментально различны, и преобразование объекта datetime в дату — это не детерминированная операция.
Что это означает на практике?
В целом, следует избегать преобразования
datetimeвdate. Например, можно использовать фильтр шаблонаdate, чтобы отобразить только часть даты объекта datetime. Этот фильтр преобразует объект datetime в текущий часовой пояс перед форматированием, гарантируя, что результаты отображаются правильно.Если вам действительно нужно выполнить преобразование самостоятельно, вы должны сначала убедиться, что объект datetime преобразован в соответствующий часовой пояс. Обычно это будет текущий часовой пояс:
>>> from django.utils import timezone >>> timezone.activate(pytz.timezone("Asia/Singapore")) # For this example, we just 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() # Again, this is the correct way to convert between time zones with pytz. >>> local = current_tz.normalize(paris.astimezone(current_tz)) >>> local datetime.datetime(2012, 3, 3, 8, 30, tzinfo=<DstTzInfo 'Asia/Singapore' SGT+8:00:00 STD>) >>> 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?Для этого как раз и предназначен pytz.
>>> from django.utils.dateparse import parse_datetime >>> naive = parse_datetime("2012-02-21 10:28:45") >>> import pytz >>> pytz.timezone("Europe/Helsinki").localize(naive, is_dst=None) datetime.datetime(2012, 2, 21, 10, 28, 45, tzinfo=<DstTzInfo 'Europe/Helsinki' EET+2:00:00 STD>)Обратите внимание, что
localize— это расширение pytz дляtzinfoAPI. Также, возможно, вы захотите перехватитьpytz.InvalidTimeError. Документация pytz содержит больше примеров. Вы должны изучить её перед попыткой манипулировать явными объектами datetime. -
Как получить местное время в текущем часовом поясе?
Ну, первый вопрос — вам это действительно нужно?
Вы должны использовать местное время только при взаимодействии с пользователями, и уровень шаблонов предоставляет фильтры и теги для преобразования объектов 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=<DstTzInfo 'Europe/Paris' CET+1:00:00 STD>)
В этом примере текущий часовой пояс —
"Europe/Paris". -
Как увидеть все доступные часовые пояса?
pytz предоставляет помощники, включая список текущих часовых поясов и список всех доступных часовых поясов — некоторые из которых представляют лишь исторический интерес.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/2.0/topics/i18n/timezones/