UTC+03:00
будет правильным ответом только на текущий момент времени, но в целом это заявление некорректно. Если вы посмотрите на базу данных часовых поясов, то увидите, к примеру, что Берлин и Вена, несмотря на смещение UTC+01:00
, имеют разные часовые пояса 'Europe/Berlin'
и 'Europe/Vienna'
. Почему так? Причина в том, что они имели разное летнее время (DST) в разные периоды истории.
И так происходит по всему миру. Переход на летнее время - огромная проблема, ведь мы предполагаем, что время имеет непрерывный мотононный ход. С переходом на летнее время у нас каждый год есть час, который повторяется дважды, и есть час, который мы просто пропускаем. Если при записи в лог вы указываете локальное время, у вас можем нарушится порядок строк лога при сортировке.
Здравый смысл называется Всемирное координированное время (UTC). UTC - это таймзона без перехода на летнее время и без каких бы то ни было изменений в прошлом.
Главное правило, которое никогда не подведёт:
TZ
.Из UTC вы можете конвертировать время в локальное для любого часового пояса.
import datetime datetime.tzinfo()
Класс tzinfo()
модуля datetime
это абстрактный базовый класс, означающий, что этот класс не должен вызываться напрямую. Необходимо определить свой подкласс datetime.tzinfo
для сбора информации о конкретном часовом поясе.
Созданный подкласс datetime.tzinfo
будет передаваться конструкторам объектов datetime.datetime
() и datetime.time()
. Последние объекты рассматривают свои атрибуты как находящиеся в местном времени, а объект tzinfo
поддерживает методы, показывающие смещение местного времени от UTC, имя часового пояса и смещение DST, и все это относительно переданного им объекта даты или времени.
В общем необходимо создать конкретный подкласс и предоставить реализации стандартных методов класса datetime.tzinfo
, которые в последствии будут использоваться.
Модуль datetime
предоставляет простой подкласс datetime.timezone()
, который может представлять часовые пояса с фиксированным смещением от UTC.
datetime.tzinfo
:Эти методы вызываются объектом datetime.datetime
() или datetime.time
() в ответ на их методы с такими же именами. Объект datetime.datetime()
передает себя в качестве аргумента, а объект datetime.time()
передает None
в качестве аргумента.
Когда значение None
не указано, дизайнер должен выбрать лучший ответ. Например, возвращение None
подходит, если класс хочет сказать, что объекты времени не участвуют в протоколах tzinfo
. Для utcoffset(None)
может быть более полезно возвращать стандартное смещение UTC, так как нет другого соглашения для обнаружения стандартного смещения.
tzinfo.utcoffset(dt)
:Метод tzinfo.utcoffset(dt)
возвращает смещение местного времени от UTC, как объект datetime.timedelta()
.
Метод представляет общее смещение от UTC. Например, если объект tzinfo
представляет настройки часового пояса и DST, то метод tzinfo.utcoffset()
должен вернуть их сумму. Если смещение UTC неизвестно, то должен вернуть None
. В противном случае возвращаемое значение должно быть объектом timedelta
строго между -timedelta(hours=24)
и timedelta(hours=24)
. То есть величина смещения должна быть меньше одного дня.
Если tzinfo.utcoffset()
не возвращает None
, то метод tzinfo.dst()
также не должен возвращать None
. Реализация по умолчанию tzinfo.utcoffset()
вызывает исключение NotImplementedError
.
tzinfo.dst(dt)
:Метод tzinfo.utcoffset(dt)
возвращает корректировку перехода на летнее время DST как объект datetime.timedelta()
или None
, если информация о DST неизвестна.
Необходимо вернуть timedelta(0)
, если летнее время не действует. Если действует DST, вернуть смещение как объект datetime.timedelta()
. Обратите внимание, что смещение DST уже добавлено к смещению UTC, возвращаемому методом tzinfo.utcoffset()
, поэтому нет необходимости обращаться к tzinfo.dst()
, если вы не заинтересованы в получении информации о DST отдельно.
Например, datetime.timetuple()
вызывает метод tzinfo.dst()
своего атрибута .tzinfo
, чтобы определить, как должен быть установлен флаг tm_isdst
, а tzinfo.fromutc()
вызывает метод tzinfo.dst()
для учета изменений DST при пересечении часовых поясов.
В этом смысле экземпляр tz
подкласса datetime.tzinfo()
, который моделирует как стандартное, так и дневное время, должен быть согласованным: tz.utcoffset(dt) - tz.dst(dt)
должен возвращать один и тот же результат для каждой dt = datetime.datetime()
с помощью dt.tzinfo == tz
. Для подклассов tzinfo
это выражение выдает "стандартное смещение" часового пояса, которое не должно зависеть от даты или времени, а только от географического местоположения.
Реализация dt.astimezone()
полагается на это и не может обнаружить нарушения. Ответственность за обеспечение этого лежит на программисте. Если подкласс tzinfo
не может этого гарантировать, он может переопределить реализацию метода tzinfo.fromutc()
по умолчанию для правильной работы с dt.astimezone()
.
Большинство реализаций tzinfo.dst()
, вероятно, будут выглядеть так:
def dst(self, dt): # a fixed-offset class: doesn't account for DST return timedelta(0) # или def dst(self, dt): # Code to set dston and dstoff to the time zone's DST # transition times based on the input dt.year, and expressed # in standard local time. if dston <= dt.replace(tzinfo=None) < dstoff: return timedelta(hours=1) else: return timedelta(0)
Реализация tzinfo.dst()
по умолчанию вызывает исключение NotImplementedError
.
tzinfo.tzname(dt)
:Метод tzinfo.tzname(dt)
возвращает имя часового пояса в виде строки, соответствующее объекту dt = datetime.datetime()
. Возвращает None
, если имя строки неизвестно. По умолчанию реализация tzinfo.tzname()
вызывает исключение NotImplementedError
.
Модуль datetime
не определяет конкретные именах строк и не требует, чтобы возвращаемое имя означало что-либо конкретное. Например, "GMT", "UTC", "-500", "-5: 00", "EDT", "America/New York" являются действительными ответами.
Обратите внимание, что tzinfo.utcoffset()
это метод, а не фиксированная строка, в первую очередь потому, что некоторые подклассы datetime.tzinfo
захотят возвращать разные имена в зависимости от конкретного переданного значения dt
, особенно если класс tzinfo
учитывает дневное время.
tzinfo.fromutc(dt)
:Метод tzinfo.fromutc(dt)
вызывается из реализации по умолчанию dt.astimezone()
.
Когда вызывается dt.astimezone()
, то dt.tzinfo
является self
и данные даты и времени dt
должны рассматриваться как выражающие время UTC. Цель метода tzinfo.fromutc()
- настроить данные даты и времени, возвращая эквивалентное местное времени.
Большинство подклассов datetime.tzinfo
должны иметь возможность наследовать реализацию tzinfo.fromutc()
по умолчанию без проблем. Метод должен обрабатывать часовые пояса с фиксированным смещением, а так же часовые пояса, учитывающие как стандартное, так и летнее время, причем последнее, даже если времена перехода на летнее время отличаются в разные годы.
Пример часового пояса, в котором реализация tzinfo.fromutc()
по умолчанию может обрабатываться некорректно во всех случаях - это случай, когда стандартное смещение от UTC зависит от конкретной прошедшей даты и времени.
Реализация метода tzinfo.fromutc()
по умолчанию действует так:
def fromutc(self, dt): # raise ValueError error if dt.tzinfo is not self dtoff = dt.utcoffset() dtdst = dt.dst() # raise ValueError if dtoff is None or dtdst is None delta = dtoff - dtdst # this is self's standard offset if delta: dt += delta # convert to standard local time dtdst = dt.dst() # raise ValueError if dtdst is None if dtdst: return dt + dtdst else: return dt