Часовые пояса
Обзор
Когда поддержка часовых поясов включена, 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() для определения того, являются ли объекты 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, чтобы сохранить обратную совместимость. Когда уровень базы данных получает их, он пытается сделать их явными, интерпретируя их в по умолчанию часовой пояс и выводит предупреждение.
К сожалению, во время переходов DST некоторые даты и времена не существуют или являются неоднозначными. В таких ситуациях pytz вызывает исключение. Вот почему вы всегда должны создавать явные объекты datetime, когда включена поддержка часовых поясов.
На практике это редко проблема. Django предоставляет вам явные объекты datetime в моделях и формах, и чаще всего новые объекты datetime создаются из существующих с помощью арифметики timedelta. Единственный объект datetime, который часто создаётся в коде приложения, — это текущее время, и timezone.now() автоматически делает всё правильно.
По умолчанию часовой пояс и текущий часовой пояс
По умолчанию часовой пояс — это часовой пояс, определённый настройкой TIME_ZONE.
Текущий часовой пояс — это часовой пояс, используемый для отображения.
Вы должны установить текущий часовой пояс на фактический часовой пояс конечного пользователя с помощью activate(). В противном случае используется часовой пояс по умолчанию.
Примечание
Как описано в документации для TIME_ZONE, Django устанавливает переменные окружения, чтобы его процесс работал в часовом поясе по умолчанию. Это происходит независимо от значения USE_TZ и текущего часового пояса.
Когда USE_TZ True, это полезно для сохранения обратной совместимости с приложениями, которые всё ещё полагаются на местное время. Однако, как описано выше, это не совсем надежно, и вы всегда должны работать с явными датами и временами в UTC в собственном коде. Например, используйте fromtimestamp() и установите параметр tz в utc.
Выбор текущего часового пояса
Текущий часовой пояс эквивалентен текущему языковому окружению для переводов. Однако нет эквивалента заголовка Accept-Language HTTP, который Django мог бы использовать для автоматического определения часового пояса пользователя. Вместо этого Django предоставляет функции выбора часового пояса. Используйте их для создания логики выбора часового пояса, которая имеет смысл для вас.
Большинство веб-сайтов, которые заботятся о часовых поясах, просто спрашивают пользователей, в каком часовом поясе они живут, и хранят эту информацию в профиле пользователя. Для анонимных пользователей они используют часовой пояс своей основной аудитории или UTC. pytz предоставляет помощники, такие как список часовых поясов по странам, которые вы можете использовать для предварительного выбора наиболее вероятных вариантов.
Вот пример, в котором текущий часовой пояс сохраняется в сессии. (Он полностью пропускает обработку ошибок ради простоты.)
Добавьте следующий middleware в MIDDLEWARE:
import pytz
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(pytz.timezone(tzname))
else:
timezone.deactivate()
return self.get_response(request)
Создайте представление, которое может установить текущий часовой пояс:
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.
Если текущий часовой пояс вызывает исключение для дат и времён, которые не существуют или являются неоднозначными, потому что они попадают на переход DST (часовые пояса, предоставляемые 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– в чем проблема?Давайте воспроизведём эту ошибку, сравнив наивный и осознанный объект datetime:
>>> 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 для APItzinfo. Кроме того, вы можете захотеть перехватить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.2/topics/i18n/timezones/