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

Обзор

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

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

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

По умолчанию поддержка часовых поясов отключена. Чтобы включить его, установите в вашем файле настроек. По умолчанию для поддержки часовых поясов используется pytz , который устанавливается при установке Django; Django также поддерживает использование других реализаций часовых поясов, например, путем передачи объектов непосредственно функциям в .USE_TZ = Truezoneinfotzinfodjango.utils.timezone

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

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

Примечание

settings.pyФайл по умолчанию, созданный для удобства, включает .django-admin startprojectUSE_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 не работает надежно для часовых поясов с DST. Использование UTC в целом безопасно; если вы используете другие часовые пояса, вам следует внимательно изучить документацию pytz .

Примечание

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

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.

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

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

Вывод с учетом часовых поясов в шаблонах

Когда вы включаете поддержку часового пояса, 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 хранит дату и время в формате . На практике это означает, что он преобразует дату и время из часового пояса соединения в UTC при хранении и из UTC в часовой пояс соединения при извлечении.timestamp with time zone

Как следствие, если вы используете PostgreSQL, вы можете переключаться между и свободно. Часовой пояс соединения с базой данных будет установлен на или соответственно, так что Django будет получать правильные даты во всех случаях. Вам не нужно выполнять какие-либо преобразования данных.USE_TZ = FalseUSE_TZ = TrueTIME_ZONEUTC

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

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

Код

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

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

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

Для моделей с DateTimeFields это различие делает невозможным написать прибор, работающий как с поддержкой часового пояса, так и без нее.

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

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

FAQ

Настройка

  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

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

    >>> 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
    

    Если вы столкнулись с этой ошибкой, скорее всего, ваш код сравнивает эти две вещи:

    • дата-время, предоставляемое Django - например, значение, считываемое из формы или поля модели. Поскольку вы включили поддержку часового пояса, он знает.
    • дата-время, сгенерированное вашим кодом, что наивно (иначе вы бы это не читали).

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

    Если вы пишете подключаемое приложение, которое, как ожидается, будет работать независимо от значения USE_TZ, вы можете найти его django.utils.timezone.now()полезным. Эта функция возвращает текущую дату и время как наивное datetime when и как известное datetime when . При необходимости вы можете прибавлять или вычитать .USE_TZ = FalseUSE_TZ = Truedatetime.timedelta

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

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

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

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

    Если вы всегда использовали наивное время даты, вы, вероятно, считаете, что можете преобразовать дату и время в дату, вызвав ее date() метод. Вы также считаете, что a dateочень похоже на a 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. Например, вы можете использовать date фильтр шаблона, чтобы отображать только часть даты в формате 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" "Europe/Helsinki"

    Это как раз то, для чего нужен 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это расширение tzinfo API-интерфейса pytz. Кроме того, вы можете захотеть поймать pytz.InvalidTimeError. Документация pytz содержит больше примеров . Вы должны просмотреть его, прежде чем пытаться манипулировать известными датами.

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

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

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

    Кроме того, Python знает, как сравнивать известные даты и время с учетом смещения UTC, когда это необходимо. Намного проще (и, возможно, быстрее) написать всю вашу модель и просмотреть код в формате UTC. Таким образом, в большинстве случаев django.utils.timezone.now()будет достаточно даты и времени в формате UTC, возвращаемого с помощью .

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

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

Copyright ©2021 All rights reserved