Spec-Zone.ru › Django 3.0

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

Обзор

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

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

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

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

Примечание

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

Примечание

Также есть независимый, но связанный параметр USE_L10N, который управляет тем, активирует ли Django локализацию форматов. Подробнее см. Локализация форматов.

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

Концепции

Простые и расширенные объекты datetime

Объекты datetime.datetime Python имеют атрибут tzinfo, который может использоваться для хранения информации о часовом поясе, представленной как экземпляр подкласса datetime.tzinfo. Если этот атрибут установлен и описывает смещение, объект datetime является расширенным. В противном случае он простой.

Вы можете использовать is_aware() и is_naive(), чтобы определить, являются ли даты и время расширенными или простыми.

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

import datetime

now = datetime.datetime.now()

Когда поддержка часовых поясов включена (USE_TZ=True), Django использует расширенные объекты datetime. Если ваш код создаёт объекты datetime, они также должны быть расширенными. В этом режиме пример выше становится:

from django.utils import timezone

now = timezone.now()

Предупреждение

Работа с расширенными объектами datetime не всегда интуитивна. Например, аргумент tzinfo стандартного конструктора datetime не работает надёжно для часовых поясов с летним временем. Использование UTC, как правило, безопасно; если вы используете другие часовые пояса, внимательно изучите документацию pytz.

Примечание

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

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

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

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

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

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

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

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

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

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

Примечание

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

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

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

Текущий часовой пояс эквивалентен текущему языковому параметру для переводов. Однако нет эквивалента HTTP-заголовку Accept-Language, который 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.

Если текущий часовой пояс генерирует исключение для дат и времени, которые не существуют или являются неоднозначными, потому что они попадают в переход на летнее время (часовые пояса, предоставляемые pytz, делают это), такие даты и время будут сообщены как недопустимые значения.

Учёт часового пояса в шаблонах

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

Предупреждение

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

localtime

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

Например:

{% load tz %}

{{ value|localtime }}

utc

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

Например:

{% load tz %}

{{ value|utc }}

timezone

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

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

Например:

{% load tz %}

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

Руководство по миграции часовых поясов

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

База данных

PostgreSQL

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

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

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

Другие бэкенды хранят значения datetime без информации о часовом поясе. Если вы переключаетесь с USE_TZ = False на USE_TZ = True, вы должны преобразовать свои данные из местного времени в UTC — что не является детерминированным, если в вашем местном времени действует летнее время.

Код

Первый шаг — добавить USE_TZ = True в ваш файл настроек. На этом этапе все должно работать в основном. Если вы создаете объекты datetime без часового пояса в своём коде, Django делает их осознанными при необходимости.

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

Поэтому второй шаг — переписать ваш код, где вы создаёте объекты datetime, чтобы сделать их осознанными. Это можно сделать постепенно. django.utils.timezone определяет некоторые полезные вспомогательные функции для совместимости кода: now(), is_aware(), is_naive(), make_aware() и make_naive().

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

RuntimeWarning: DateTimeField ModelName.field_name received a naive
datetime (2012-01-01 00:00:00) while time zone support is active.

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

import warnings
warnings.filterwarnings(
    'error', r"DateTimeField .* received a naive datetime",
    RuntimeWarning, r'django\.db\.models\.fields',
)

Файлы данных

При сериализации объекта datetime с часовым поясом смещение UTC включается, как это:

"2011-09-01T13:20:30+03:00"

В то время как для объекта datetime без часового пояса — нет:

"2011-09-01T13:20:30"

Для моделей с DateTimeField это различие делает невозможным создание файла данных, который работает как с поддержкой часовых поясов, так и без неё.

Файлы данных, сгенерированные с помощью USE_TZ = False, или до Django 1.4, используют формат «без часового пояса». Если ваш проект содержит такие файлы данных, после включения поддержки часовых поясов вы увидите RuntimeWarning при их загрузке. Чтобы избавиться от предупреждений, вы должны преобразовать свои файлы данных в формат «с часовым поясом».

Вы можете перегенерировать файлы данных с помощью loaddata, а затем dumpdata. Или, если они достаточно маленькие, вы можете отредактировать их, чтобы добавить смещение UTC, соответствующее вашему TIME_ZONE, к каждому сериализованному значению datetime.

ЧАВО по часовым поясам

Настройка

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

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

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

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

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

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

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

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

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

    Это полезно для подключения к базе данных, которая не поддерживает часовые пояса и не управляется Django, когда 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, когда 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. Это указывает на то, что вы ещё не завершили адаптацию своего кода для поддержки часовых поясов. Обратитесь к руководству по миграции для советов по этому процессу.

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

  3. 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 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. У меня есть строка "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 для 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 предоставляет помощники, включая список текущих часовых поясов и список всех доступных часовых поясов – некоторые из которых имеют лишь историческое значение.

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

Spec-Zone.ru

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