Часовые пояса

Предварительный просмотр

Когда поддержка часовых поясов включена, 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 %}

get_current_timezone

Вы можете получить название текущего часового пояса, используя тег get_current_timezone :

{% get_current_timezone as TIME_ZONE %}

Возможная альтернатива - активировать контекстный процессор tz() и использовать контекстную переменную TIME_ZONE .

Шаблонные фильтры

Эти фильтры принимают как datetime сознательные, так и наивные объекты . В целях преобразования они предполагают, что наивные объекты находятся в часовом поясе по умолчанию. Они всегда возвращают datetime сознательные объекты .

localtime

Принудительно преобразовывает одно значение в текущий часовой пояс.

Например :

{% load tz %}

{{ value|localtime }}

utc

Принудительное преобразование одного значения в UTC.

Например :

{% load tz %}

{{ value|utc }}

timezone

Принудительное преобразование одного значения в произвольный часовой пояс.

Параметр должен быть экземпляром подкласса tzinfo или именем часового пояса.

Например :

{% load tz %}

{{ value|timezone:"Europe/Paris" }}

Руководство по миграции

Вот как перенести проект, запущенный до того, как 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

Конфигурация

  1. Мне не нужно несколько часовых поясов. Нужно ли включать поддержку часового пояса?

    Да. Когда поддержка часовых поясов включена, Django использует более точную модель местного времени. Это защищает вас от незаметных, невоспроизводимых ошибок при переходе на летнее время (DST).

    Когда вы включаете поддержку часового пояса, вы столкнетесь с некоторыми ошибками, потому что вы используете наивные даты / время, когда Django ожидает осознанные даты / время. Такие ошибки появляются при запуске тестов. Вы быстро узнаете, как избежать недействительных транзакций.

    С другой стороны, ошибки, возникающие из-за неподдерживаемых часовых поясов, гораздо сложнее предотвратить, диагностировать и исправить. Все, что связано с запланированными задачами или арифметикой даты / времени, подвержено тонким ошибкам, которые затронут вас только один или два раза в год.

    Именно по этим причинам поддержка часовых поясов включена по умолчанию в новых проектах, и вам следует сохранить этот параметр, если у вас нет веской причины не делать этого.

  2. Я включил поддержку часового пояса. Я все еще?

    Может быть. Вы лучше защищены от ошибок, связанных с переходом на летнее время, но всегда есть вероятность, что вы ошибетесь, неосторожно преобразовав 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, в зависимости от требований вашего бизнеса.

  3. Как взаимодействовать с базой данных, хранящей дату / время по местному времени?

    Установите параметр TIME_ZONE в часовой пояс, соответствующий этой базе данных в настройке DATABASES .

    Это полезно для подключения к базе данных , которая не поддерживает часовые пояса и не управляются Джанго , когда USE_TZ стоит True .

Устранение неполадок

  1. Мое приложение вылетает вместе с ним , что я делаю не так?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

  2. Я много вижу , это плохой знак?RuntimeWarning: DateTimeField received a naive datetime (YYYY-MM-DD HH:MM:SS) while time zone support is active

    Когда поддержка часового пояса включена, уровень базы данных ожидает получать только объекты, datetime осведомленные о вашем коде. Это предупреждение появляется, когда он получает datetime наивный объект . Это означает, что вы не закончили преобразование кода для поддержки часовых поясов. Пожалуйста , обратитесь к руководству по миграции для дополнительных указаний по этому процессу.

    Между тем, для обеспечения обратной совместимости Django считает, что объект datetime находится в часовом поясе по умолчанию, что обычно верно.

  3. 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)
    
  4. Я получаю ошибку "   "Are time zone definitions for your database installed?

    Если вы используете MySQL, см. Раздел « Определения часовых поясов» в «Примечаниях к MySQL» для получения инструкций по загрузке определений часовых поясов.

Использование

  1. У меня есть канал, и я знаю, что он находится в часовом поясе ** `` Европа / Хельсинки ''. Как я могу превратить его в сознательный объект 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 для API tzinfo . Может понадобиться отловить ошибки pytz.InvalidTimeError . Документация pytz содержит больше примеров . Рекомендуется проконсультироваться с ним, прежде чем пытаться манипулировать datetime сознательными объектами .

  2. Как я могу узнать местное время в текущем часовом поясе?

    Итак, первый вопрос, который нужно задать себе: действительно ли это необходимо?

    Местное время следует использовать только при взаимодействии с людьми, а слой шаблонов предоставляет фильтры и теги для преобразования объектов 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" .

  3. Как я могу увидеть все доступные часовые пояса?

    pytz предоставляет служебные программы , включая список текущих часовых поясов и список всех доступных часовых поясов, в том числе некоторые, которые представляют только исторический интерес.

Copyright ©2021 All rights reserved