Spec-Zone.ru › Django 4.0

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

Обзор

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

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

Даже если ваш сайт доступен только в одном часовом поясе, все равно рекомендуется хранить данные в UTC в вашей базе данных. Основная причина — летнее время (DST). Многие страны используют систему DST, где время переводится вперед весной и назад осенью. Если вы работаете с местным временем, вы, вероятно, столкнетесь с ошибками дважды в год, когда происходят переходы. Это, вероятно, не имеет значения для вашего блога, но это проблема, если вы переплачиваете или недоплачиваете своим клиентам на один час дважды в год, каждый год. Решением этой проблемы является использование UTC в коде и использование местного времени только при взаимодействии с конечными пользователями.

Поддержка часовых поясов по умолчанию отключена. Чтобы включить ее, установите USE_TZ = True в вашем файле настроек.

Примечание

В Django 5.0 поддержка часовых поясов будет включена по умолчанию.

Поддержка часовых поясов использует zoneinfo, который является частью стандартной библиотеки Python с Python 3.9. Пакет backports.zoneinfo автоматически устанавливается вместе с Django, если вы используете Python 3.8.

Изменено в Django 3.2:

Добавлена поддержка реализаций часовых поясов, отличных от pytz.

Изменено в Django 4.0:

zoneinfo стал реализацией часового пояса по умолчанию. Вы можете продолжить использовать pytz в течение цикла выпуска 4.x с помощью настройки USE_DEPRECATED_PYTZ.

Примечание

Файл по умолчанию settings.py, созданный django-admin startproject, включает USE_TZ = True для удобства.

Если у вас возникли проблемы, начните с часто задаваемых вопросов о часовых поясах.

Концепции

Наивные и осознанные объекты 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 не работает надежно для часовых поясов с DST. Использование UTC обычно безопасно; если вы используете другие часовые пояса, вы должны внимательно изучить документацию zoneinfo.

Примечание

Объекты datetime.time Python также имеют атрибут tzinfo, а PostgreSQL имеет соответствующий тип time with time zone. Однако, как говорится в документации PostgreSQL, этот тип «обладает свойствами, которые приводят к сомнительной полезности».

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

Интерпретация наивных объектов datetime

Когда USE_TZ True, Django по-прежнему принимает наивные объекты datetime для сохранения обратной совместимости. Когда слой базы данных получает такой объект, он пытается сделать его осознанным, интерпретируя его в по умолчанию часовом поясе и выводит предупреждение.

К сожалению, во время переходов на летнее/зимнее время некоторые даты и время не существуют или являются неоднозначными. Поэтому вы всегда должны создавать осознанные объекты datetime, когда поддержка часовых поясов включена. (См. Using ZoneInfo section of the zoneinfo docs для примеров использования атрибута fold для указания смещения, которое должно применяться к datetime во время перехода на летнее/зимнее время.)

На практике это редко является проблемой. Django предоставляет осознанные объекты datetime в моделях и формах, и чаще всего новые объекты datetime создаются из существующих объектов с помощью арифметики timedelta. Единственное datetime, которое часто создается в коде приложения, — это текущее время, и timezone.now() автоматически делает все правильно.

Часовой пояс по умолчанию и текущий часовой пояс

Часовой пояс по умолчанию — это часовой пояс, определенный настройкой TIME_ZONE.

Текущий часовой пояс — это часовой пояс, используемый для рендеринга.

Вы должны установить текущий часовой пояс на фактический часовой пояс конечного пользователя с помощью activate(). В противном случае используется часовой пояс по умолчанию.

Примечание

Как объясняется в документации TIME_ZONE, Django устанавливает переменные окружения, чтобы его процесс работал в часовом поясе по умолчанию. Это происходит независимо от значения USE_TZ и текущего часового пояса.

Когда USE_TZ True, это полезно для сохранения обратной совместимости с приложениями, которые по-прежнему полагаются на местное время. Однако, как объяснено выше, это не совсем надежно, и вы всегда должны работать с осознанными датами и временем в UTC в собственном коде. Например, используйте fromtimestamp() и установите параметр tz в utc.

Выбор текущего часового пояса

Текущий часовой пояс эквивалентен текущему языковому параметру для переводов. Однако нет эквивалента заголовка HTTP Accept-Language, который Django мог бы использовать для автоматического определения часового пояса пользователя. Вместо этого Django предоставляет функции выбора часовых поясов. Используйте их, чтобы создать логику выбора часового пояса, которая имеет смысл для вас.

Большинство веб-сайтов, которые заботятся о часовых поясах, спрашивают пользователей, в каком часовом поясе они живут, и сохраняют эту информацию в профиле пользователя. Для анонимных пользователей они используют часовой пояс своей основной аудитории или UTC. zoneinfo.available_timezones() предоставляет набор доступных часовых поясов, которые вы можете использовать для создания карты из вероятных местоположений в часовые пояса.

Вот пример, который сохраняет текущий часовой пояс в сессии. (Он полностью пропускает обработку ошибок для простоты.)

Добавьте следующий мидлваэр в MIDDLEWARE:

import zoneinfo

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(zoneinfo.ZoneInfo(tzname))
        else:
            timezone.deactivate()
        return self.get_response(request)

Создайте представление, которое может установить текущий часовой пояс:

from django.shortcuts import redirect, render

# Prepare a map of common locations to timezone choices you wish to offer.
common_timezones = {
    'London': 'Europe/London',
    'Paris': 'Europe/Paris',
    'New York': 'America/New_York',
}

def set_timezone(request):
    if request.method == 'POST':
        request.session['django_timezone'] = request.POST['timezone']
        return redirect('/')
    else:
        return render(request, 'template.html', {'timezones': 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 city, tz in timezones %}
        <option value="{{ tz }}"{% if tz == TIME_ZONE %} selected{% endif %}>{{ city }}</option>
        {% endfor %}
    </select>
    <input type="submit" value="Set">
</form>

Часовые пояса в формах

Когда вы включаете поддержку часовых поясов, Django интерпретирует даты и время, введенные в формах, в текущем часовом поясе и возвращает осознанные объекты datetime в cleaned_data.

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

Работа с часовыми поясами в шаблонах

При включении поддержки часовых поясов 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.

Фильтры шаблонов

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

localtime

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

Например:

{% load tz %}

{{ value|localtime }}

utc

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

Например:

{% load tz %}

{{ value|utc }}

timezone

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

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

Например:

{% load tz %}

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

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

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

База данных

PostgreSQL

База данных PostgreSQL хранит даты и время как timestamp with time zone. На практике это означает, что она преобразует даты и время из часового пояса соединения в UTC при сохранении и из UTC в часовой пояс соединения при получении.

Следовательно, если вы используете PostgreSQL, вы можете свободно переключаться между USE_TZ = False и USE_TZ = True. Часовой пояс подключения к базе данных будет установлен в TIME_ZONE или UTC соответственно, чтобы Django получал правильные даты и время во всех случаях. Вам не нужно выполнять никаких преобразований данных.

Другие базы данных

Другие бэкэнды хранят даты и время без информации о часовом поясе. Если вы переключаетесь с 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, к каждой сериализованной дате и времени.

Часто задаваемые вопросы

Настройка

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

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

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

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

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

  2. Я включил поддержку часовых поясов. Я в безопасности?

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

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

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

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

  1. Моё приложение падает с TypeError: can't compare offset-naive and offset-aware datetimes – в чём проблема?

    Давайте воспроизведём эту ошибку, сравнив обычную и осознанную дату и время:

    >>> from django.utils import timezone
    >>> aware = timezone.now()
    >>> naive = timezone.make_naive(aware)
    >>> 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 по мере необходимости.

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

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

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

  3. now.date() вчера! (или завтра)

    Если вы всегда использовали обычные даты и время, вы, вероятно, считаете, что можете преобразовать дату и время в дату, вызвав её метод date(). Вы также считаете, что date очень похожа на datetime, за исключением того, что она менее точная.

    Ничего из этого не верно в среде с поддержкой часовых поясов:

    >>> import datetime
    >>> import zoneinfo
    >>> paris_tz = zoneinfo.ZoneInfo("Europe/Paris")
    >>> new_york_tz = zoneinfo.ZoneInfo("America/New_York")
    >>> paris = datetime.datetime(2012, 3, 3, 1, 30, tzinfo=paris_tz)
    # This is the correct way to convert between time zones.
    >>> new_york = 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=zoneinfo.ZoneInfo(key='Europe/Paris'))
    >>> new_york
    datetime.datetime(2012, 3, 2, 19, 30, tzinfo=zoneinfo.ZoneInfo(key='America/New_York'))
    

    Как показывает этот пример, та же самая дата и время имеет разную дату в зависимости от часового пояса, в котором она представлена. Но реальная проблема фундаментальнее.

    Дата и время представляет собой момент во времени. Она абсолютна: она не зависит ни от чего. Напротив, дата – это календарное понятие. Это период времени, границы которого зависят от часового пояса, в котором рассматривается дата. Как видите, эти два понятия фундаментально различны, и преобразование даты и времени в дату не является детерминированным действием.

    Что это значит на практике?

    Обычно следует избегать преобразования datetime в date. Например, вы можете использовать фильтр шаблонов date для отображения только части даты даты и времени. Этот фильтр преобразует дату и время в текущий часовой пояс перед форматированием, гарантируя, что результаты отображаются правильно.

    Если вам действительно нужно выполнить это преобразование самостоятельно, вы должны сначала убедиться, что дата и время преобразованы в соответствующий часовой пояс. Обычно это будет текущий часовой пояс:

    >>> from django.utils import timezone
    >>> timezone.activate(zoneinfo.ZoneInfo("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()
    >>> local = paris.astimezone(current_tz)
    >>> local
    datetime.datetime(2012, 3, 3, 8, 30, tzinfo=zoneinfo.ZoneInfo(key='Asia/Singapore'))
    >>> local.date()
    datetime.date(2012, 3, 3)
    
  4. Я получаю ошибку “Are time zone definitions for your database installed?”

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

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

  1. У меня есть строка "2012-02-21 10:28:45" и я знаю, что она находится в "Europe/Helsinki" часовом поясе. Как преобразовать её в осознанную дату и время?

    Здесь вам нужно создать необходимый ZoneInfo объект и прикрепить его к обычной дате и времени:

    >>> import zoneinfo
    >>> from django.utils.dateparse import parse_datetime
    >>> naive = parse_datetime("2012-02-21 10:28:45")
    >>> naive.replace(tzinfo=zoneinfo.ZoneInfo("Europe/Helsinki"))
    datetime.datetime(2012, 2, 21, 10, 28, 45, tzinfo=zoneinfo.ZoneInfo(key='Europe/Helsinki'))
    
  2. Как получить местное время в текущем часовом поясе?

    Ну, первый вопрос: вам это действительно нужно?

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

    Кроме того, 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=zoneinfo.ZoneInfo(key='Europe/Paris'))
    

    В этом примере текущий часовой пояс – "Europe/Paris".

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

    zoneinfo.available_timezones() предоставляет набор всех допустимых ключей для часовых поясов IANA, доступных вашей системе. См. документацию для использования.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.0/topics/i18n/timezones/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API