Часовые пояса
Обзор
При включенном управлении часовыми поясами Django хранит информацию о датах и временах в UTC в базе данных, использует объекты datetime, учитывающие часовой пояс, во внутренней работе и преобразует их в часовой пояс конечного пользователя в шаблонах и формах.
Это удобно, если ваши пользователи проживают в нескольких часовых поясах, и вы хотите отображать информацию о датах и временах в соответствии с местным временем каждого пользователя.
Даже если ваш сайт доступен только в одном часовом поясе, все равно рекомендуется хранить данные в UTC в базе данных. Основная причина — летнее время (DST). Многие страны используют систему DST, когда часы переводят вперед весной и назад осенью. Если вы работаете с местным временем, вы, вероятно, столкнетесь с ошибками дважды в год, когда происходят переходы. (Документация pytz подробно описывает эти проблемы.) Это, вероятно, не важно для вашего блога, но это проблема, если вы переплачиваете или недоплачиваете своим клиентам на один час дважды в год, каждый год. Решение этой проблемы — использовать UTC в коде и использовать местное время только при взаимодействии с конечными пользователями.
Поддержка часовых поясов отключена по умолчанию. Чтобы включить ее, установите USE_TZ =
True в файле настроек. Поддержка часовых поясов использует pytz, который устанавливается при установке Django.
Примечание
Создаваемый по умолчанию файл settings.py, создаваемый django-admin
startproject, включает USE_TZ = True для удобства.
Примечание
Также существует независимый, но связанный, параметр USE_L10N, который управляет тем, должен ли Django активировать локализованный формат. Подробнее см. Локализация формата.
Если вы столкнулись с определенной проблемой, начните с ЧАВО по часовым поясам.
Концепции
Простые и полные объекты 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, как правило, безопасно; если вы используете другие часовые пояса, вы должны внимательно изучить документацию pytz.
Примечание
Объекты datetime.time Python также имеют атрибут tzinfo, а PostgreSQL имеет соответствующий тип time with time zone. Однако, как говорится в документации PostgreSQL, этот тип «обладает свойствами, которые приводят к сомнительной полезности».
Django поддерживает только простые объекты времени и будет генерировать исключение, если вы попытаетесь сохранить полный объект времени, поскольку часовой пояс для времени без связанной даты не имеет смысла.
Интерпретация простых объектов datetime
Когда USE_TZ True, Django по-прежнему принимает простые объекты datetime для сохранения обратной совместимости. Когда слой базы данных получает один из них, он пытается сделать его полным, интерпретируя его в установленном по умолчанию часовом поясе, и выводит предупреждение.
К сожалению, во время перехода на летнее время некоторые даты и время не существуют или являются неоднозначными. В таких ситуациях pytz выводит исключение. Вот почему вы всегда должны создавать полные объекты datetime, когда включена поддержка часовых поясов.
На практике это редко проблема. Django предоставляет вам полные объекты datetime в моделях и формах, и чаще всего новые объекты datetime создаются из существующих с помощью арифметики timedelta. Единственное время, которое часто создается в коде приложения, — это текущее время, и timezone.now() автоматически выполняет правильные действия.
Установленный по умолчанию часовой пояс и текущий часовой пояс
Установленный по умолчанию часовой пояс — это часовой пояс, определенный параметром TIME_ZONE.
Текущий часовой пояс — это часовой пояс, используемый для отображения.
Вы должны установить текущий часовой пояс на фактический часовой пояс конечного пользователя с помощью activate(). В противном случае используется часовой пояс по умолчанию.
Примечание
Как объясняется в документации TIME_ZONE, Django устанавливает переменные окружения, чтобы процесс работал в установленном по умолчанию часовом поясе. Это происходит независимо от значения USE_TZ и текущего часового пояса.
Когда USE_TZ True, это полезно для сохранения обратной совместимости с приложениями, которые все еще полагаются на местное время. Однако, как объяснялось выше, это не полностью надёжно, и вы всегда должны работать с полными датами и временем в 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 без временной зоны, так как они могут быть неоднозначными, и потому, что ваш код никогда не должен генерировать такие объекты, когда поддержка временных зон включена. Однако, вы можете принудительно выполнить преобразование с помощью фильтров шаблонов, описанных ниже.
Преобразование в местное время не всегда уместно – вы можете генерировать вывод для компьютеров, а не для людей. Следующие фильтры и теги, предоставляемые библиотекой тегов шаблонов 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',
)
Файлы данных
При сериализации объекта 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 использует более точную модель местного времени. Это защищает вас от скрытых и невоспроизводимых ошибок, связанных с переходом на летнее время.
Когда вы включите поддержку временных зон, вы столкнётесь с некоторыми ошибками, потому что используете объекты datetime без временной зоны, где Django ожидает объекты datetime с временной зоной. Такие ошибки появляются при запуске тестов, и их легко исправить. Вы быстро научитесь избегать недопустимых операций.
С другой стороны, ошибки, вызванные отсутствием поддержки временных зон, гораздо сложнее предотвратить, диагностировать и исправить. Всё, что связано с расписаниями задач или арифметикой datetime, является кандидатом на скрытые ошибки, которые могут возникнуть только один-два раза в год.
По этим причинам поддержка временных зон включена по умолчанию в новых проектах, и её следует сохранить, если у вас нет очень веской причины, чтобы этого не делать.
-
Я включил поддержку временных зон. Я в безопасности?
Возможно. Вы защищены лучше от ошибок, связанных с переходом на летнее время, но вы всё ещё можете навредить себе, беспечно превращая объекты 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
Если вы столкнулись с этой ошибкой, скорее всего, ваш код сравнивает эти два элемента:
- дата и время, предоставленные 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– это плохо?При включённой поддержке часовых поясов, уровень базы данных ожидает получать от вашего кода только осознанные даты и время. Это предупреждение появляется, когда он получает наивную дату и время. Это указывает на то, что вы не завершили перенос своего кода для поддержки часовых поясов. Обратитесь к руководству по миграции за советами по этому процессу.
Тем временем, для обратной совместимости, дата и время считаются в часовом поясе по умолчанию, что, как правило, ожидается.
-
now.date()вчера! (или завтра)Если вы всегда использовали наивные даты и время, вы, вероятно, считаете, что можете преобразовать дату и время в дату, вызвав её метод
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вdate. Например, вы можете использовать шаблонный фильтрdate, чтобы отобразить только часть даты даты и времени. Этот фильтр преобразует дату и время в текущий часовой пояс перед форматированием, гарантируя правильное отображение результатов.Если вам действительно нужно выполнить преобразование самостоятельно, вы должны убедиться, что дата и время сначала преобразованы в соответствующий часовой пояс. Обычно это будет текущий часовой пояс:
>>> 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". Как преобразовать её в осознанную дату и время?Для этого предназначен именно 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 содержит ещё примеры. Вам следует ознакомиться с ней перед попыткой манипулирования осознанными датами и временем. -
Как получить локальное время в текущем часовом поясе?
Ну, первый вопрос — вам это действительно нужно?
Вы должны использовать локальное время только при взаимодействии с людьми, и уровень шаблонов предоставляет фильтры и теги для преобразования дат и времени в часовой пояс по вашему выбору.
Кроме того, 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=<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.1/topics/i18n/timezones/