Часовые пояса ¶
Предварительный просмотр ¶
Когда поддержка часовых поясов включена, Django хранит информацию о дате и времени в формате UTC в базе данных, внутренне использует объекты даты и времени с учетом часовых поясов и преобразует их в шаблоны и формы в используя часовой пояс пользователя.
Это очень полезно, если ваши пользователи находятся в нескольких часовых поясах, и вы хотите отображать информацию о времени в соответствии с местным временем каждого из них.
Даже если ваш веб-сайт используется только в одном часовом поясе, рекомендуется хранить данные в базе данных в формате UTC. Основная причина - переход на летнее время (DST). Во многих странах переход на летнее время увеличивается на один час весной и сокращается на час осенью. Если вы работаете по местному времени, вы можете получать ошибки дважды в год, во время перехода (документация pytz решает эти проблемыболее детально). Вероятно, это не проблема для вашего блога, но если вы завышаете или занижаете цену для своих клиентов на час, два раза в год, каждый год. Решение этой проблемы - использовать в коде UTC и местное время только при взаимодействии с пользователями сайта.
По умолчанию поддержка часовых поясов отключена. Чтобы активировать его, укажите в вашем файле настроек. Для поддержки часовых поясов используется pytz, который устанавливается при установке Django.USE_TZ = True
Заметка
Файл, settings.py
созданный по умолчанию, определяется для удобства.django-admin startproject
USE_TZ = True
Заметка
Существует также другой, но связанный параметр с именем, USE_L10N
который определяет, включены ли форматы регионализации. См. Дополнительные сведения в разделе « Регионализация форматов» .
Если у вас возникла какая-то конкретная проблема, сначала прочтите часто задаваемые вопросы о часовых поясах .
Концепции ¶
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
не работает надежно для часовых поясов с летним временем. Использование времени UTC в целом безопасно; если вы используете другие часовые пояса, вам следует внимательно прочитать документацию pytz .
Заметка
Объекты datetime.time
Python также имеют атрибут tzinfo
, а PostgreSQL имеет соответствующий тип (время с часовым поясом). Однако, как поясняется в документации PostgreSQL, этот тип «предоставляет свойства, которые делают его полезность сомнительной».time with time zone
Django поддерживает только time
наивные объекты и выдает исключение, если вы пытаетесь зарегистрировать time
сознательный объект , поскольку часовой пояс не имеет смысла для объекта time
без связанной даты.
Интерпретация datetime
наивных объектов ¶
Когда USE_TZ
стоит True
, Django по-прежнему принимает datetime
наивные объекты , чтобы сохранить обратную совместимость. Когда уровень базы данных получает один, он пытается уведомить его, интерпретируя его в часовом поясе по умолчанию, и генерирует предупреждение.
К сожалению, при переходе на летнее время некоторые объекты 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
.
Выбор текущего часового пояса ¶
Текущий часовой пояс эквивалентен текущему языку переводов. Однако нет эквивалента HTTP-заголовку, Accept-Language
который Django мог бы использовать для автоматического определения часового пояса пользователя. Вместо этого Django предоставляет функции выбора часового пояса . Используйте их для построения логики выбора часового пояса, которая имеет смысл в вашей ситуации.
Большинство веб-сайтов, связанных с часовыми поясами, запрашивают у пользователей их домашний часовой пояс и сохраняют эту информацию в профиле пользователя. Для анонимных пользователей они используют часовой пояс большей части своей аудитории или UTC. pytz предлагает служебные программы , такие как список часовых поясов для каждой страны, которые можно использовать для предварительного выбора наиболее вероятных вариантов.
Вот пример, который хранит текущий часовой пояс в сеансе (для простоты обработка ошибок полностью отсутствует).
Добавьте следующее промежуточное ПО в 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
.
Если текущий часовой пояс вызывает исключение для дат / времени, которые не существуют или являются неоднозначными, потому что они находятся в переходе на летнее время (часовые пояса в pytz do), эти даты / время будут сообщен как недействительный.
Отображение осознанных часовых поясов в шаблонах ¶
Когда вы включаете поддержку часового пояса, Django преобразует объекты datetime
в текущий часовой пояс, когда они отображаются в шаблоне. Это поведение очень похоже на регионализацию форматов .
Предупреждение
Django не конвертирует 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 %}
Шаблонные фильтры ¶
Эти фильтры принимают как datetime
сознательные, так и наивные объекты . В целях преобразования они предполагают, что наивные объекты находятся в часовом поясе по умолчанию. Они всегда возвращают datetime
сознательные объекты .
localtime
¶
Принудительно преобразовывает одно значение в текущий часовой пояс.
Например :
{% load tz %}
{{ value|localtime }}
Руководство по миграции ¶
Вот как перенести проект, запущенный до того, как Django поддерживает часовые пояса.
База данных ¶
PostgreSQL ¶
Движок PostgreSQL хранит дату / время в формате . На практике это означает, что он преобразует объекты в UTC из часового пояса соединения при сохранении и из UTC в часовой пояс соединения при выборе.timestamp with time zone
datetime
Следовательно, если вы используете PostgreSQL, вы можете свободно переключаться с на и наоборот. Часовой пояс соединения с базой данных будет установлен на и соответственно , поэтому Django будет получать правильные даты / время во всех случаях. Вам не нужно преобразовывать данные.USE_TZ = False
USE_TZ = True
TIME_ZONE
UTC
Другие базы данных ¶
Другие движки хранят дату / время без информации о часовом поясе. Если вы меняете с на , вам необходимо преобразовать данные из местного времени в UTC, что не является детерминированным, если ваше местное время подлежит переходу на летнее время.USE_TZ = False
USE_TZ = True
Код ¶
Первый шаг - добавить файл настроек. На этом этапе в принципе все должно работать. Если вы создаете наивные объекты в своем коде, Django при необходимости информирует их.USE_TZ = True
datetime
Однако эти преобразования могут завершиться сбоем при переходе на летнее время, а это значит, что вы еще не получаете всех преимуществ поддержки часовых поясов. Также вероятно, что вы столкнетесь с некоторыми проблемами, поскольку невозможно сравнить наивную дату / время с осознанной датой / временем. Поскольку 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
это различие делает невозможным создание снимка, работающего как с поддержкой часового пояса, так и без нее.
Снимки, созданные с помощью Django 1.4 или ранее, используют «наивный» формат. Если ваш проект содержит такие снимки и вы включили поддержку часового пояса, вы увидите исключения при их загрузке. Чтобы избавиться от этих предупреждений, вы должны преобразовать ваши снимки в «осведомленный» формат.USE_TZ = False
RuntimeWarning
loaddata
Затем вы можете регенерировать снимки с помощью dumpdata
. Или, если они не слишком большие, вы можете отредактировать их и добавить смещение UTC, соответствующее вашему, TIME_ZONE
в каждый datetime
сериализованный объект .
FAQ ¶
Конфигурация ¶
Мне не нужно несколько часовых поясов. Нужно ли включать поддержку часового пояса?
Да. Когда поддержка часовых поясов включена, Django использует более точную модель местного времени. Это защищает вас от незаметных, невоспроизводимых ошибок при переходе на летнее время (DST).
Когда вы включаете поддержку часового пояса, вы столкнетесь с некоторыми ошибками, потому что вы используете наивные даты / время, когда Django ожидает осознанные даты / время. Такие ошибки появляются при запуске тестов. Вы быстро узнаете, как избежать недействительных транзакций.
С другой стороны, ошибки, возникающие из-за неподдерживаемых часовых поясов, гораздо сложнее предотвратить, диагностировать и исправить. Все, что связано с запланированными задачами или арифметикой даты / времени, подвержено тонким ошибкам, которые затронут вас только один или два раза в год.
Именно по этим причинам поддержка часовых поясов включена по умолчанию в новых проектах, и вам следует сохранить этот параметр, если у вас нет веской причины не делать этого.
Я включил поддержку часового пояса. Я все еще?
Может быть. Вы лучше защищены от ошибок, связанных с переходом на летнее время, но всегда есть вероятность, что вы ошибетесь, неосторожно преобразовав
datetime
наивные объекты вdatetime
сознательные, и наоборот.Если ваше приложение подключается к другим системам, например, если оно запрашивает веб-службу, обязательно правильно установите дату и время. Чтобы передавать их надежно, их представление должно включать смещение UTC или их значение должно быть в формате UTC (или и то, и другое!).
Наконец, наша календарная система содержит интересные крайние случаи. Например, вы не всегда можете вычесть один год непосредственно из заданной даты:
>>> import datetime >>> def one_year_before(value): # Wrong example. ... return value.replace(year=value.year - 1) >>> one_year_before(datetime.datetime(2012, 3, 1, 10, 0)) datetime.datetime(2011, 3, 1, 10, 0) >>> one_year_before(datetime.datetime(2012, 2, 29, 10, 0)) Traceback (most recent call last): ... ValueError: day is out of range for month
Чтобы правильно реализовать такую функцию, вы должны решить, будет ли 2012-02-29 минус один год 2011-02-28 или 2011-03-01, в зависимости от требований вашего бизнеса.
Как взаимодействовать с базой данных, хранящей дату / время по местному времени?
Установите параметр
TIME_ZONE
в часовой пояс, соответствующий этой базе данных в настройкеDATABASES
.Это полезно для подключения к базе данных , которая не поддерживает часовые пояса и не управляются Джанго , когда
USE_TZ
стоитTrue
.
Устранение неполадок ¶
Мое приложение вылетает вместе с ним , что я делаю не так?
TypeError: can't compare offset-naive
and 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
наивный объект when и как сознательный объект when . При необходимости вы можете добавлять или вычитать из него значение .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
наивный объект . Это означает, что вы не закончили преобразование кода для поддержки часовых поясов. Пожалуйста , обратитесь к руководству по миграции для дополнительных указаний по этому процессу.Между тем, для обеспечения обратной совместимости Django считает, что объект
datetime
находится в часовом поясе по умолчанию, что обычно верно.now.date()
вчера! (или завтра)Если вы всегда использовали
datetime
наивные объекты , вы, вероятно, думаете, что можете преобразовать объект вdatetime
объект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
может представлять разную дату в зависимости от часового пояса, в котором он представлен. Но настоящая проблема более фундаментальна.Объект
datetime
представляет момент времени . Он абсолютен и ни от чего не зависит. Напротив, дата - это понятие календаря . Это период времени, пределы которого зависят от часового пояса, в котором считается дата. Как видите, эти две концепции принципиально разные, и преобразование объектаdatetime
в объектdate
не является детерминированной операцией.Что это значит на практике?
Как правило, вам следует избегать преобразования объекта
datetime
в форматdate
. Например, вы можете использовать шаблонный фильтрdate
для отображения только части объекта датыdatetime
. Этот фильтр преобразует объектdatetime
в текущий часовой пояс перед его форматированием, тем самым обеспечивая правильность вывода.Если вам действительно нужно выполнить преобразование самостоятельно, сначала следует позаботиться о преобразовании объекта
datetime
в правильный часовой пояс. Обычно это текущий часовой пояс:>>> from django.utils import timezone >>> timezone.activate(pytz.timezone("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() # 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» для получения инструкций по загрузке определений часовых поясов.
Использование ¶
У меня есть канал, и я знаю, что он находится в часовом поясе ** `` Европа / Хельсинки ''. Как я могу превратить его в сознательный объект datetime?
"2012-02-21 10:28:45"
Это как раз роль 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 предоставляет служебные программы , включая список текущих часовых поясов и список всех доступных часовых поясов, в том числе некоторые, которые представляют только исторический интерес.