Преобразование временной зоны Datetime с использованием pytz
Это еще одно сообщение на pytz
.
Существует две функции для преобразования объектов datetime между двумя часовыми поясами. Вторая функция работает для всех случаев. Первая функция не выполняется в двух случаях: (3) и (4). Аналогичное сообщение qaru.site/info/15596/... не имело такой проблемы. Любое объяснение, основанное на различии между localize(datetime.datetime)
и replace(tzinfo)
, будет большой помощью.
>>> from dateutil.parser import parse
>>> import pytz
Первая функция (багги)
В приведенной ниже функции используется datetime.datetime.replace(tzinfo)
.
def buggy_timezone_converter(input_dt, current_tz='UTC', target_tz='US/Eastern'):
'''input_dt is a datetime.datetime object'''
current_tz = pytz.timezone(current_tz)
target_tz = pytz.timezone(target_tz)
target_dt = input_dt.replace(tzinfo=current_tz).astimezone(target_tz)
return target_tz.normalize(target_dt)
Обратите внимание на четыре преобразования даты и времени.
(1) от UTC до EST - OK
>>> buggy_timezone_converter(parse('2013-02-26T04:00:00'))
Out[608]: datetime.datetime(2013, 2, 25, 23, 0, tzinfo=<DstTzInfo 'US/Eastern' EST-1 day, 19:00:00 STD>)
(2) от UTC до EDT - OK
>>> buggy_timezone_converter(parse('2013-05-26T04:00:00'))
Out[609]: datetime.datetime(2013, 5, 26, 0, 0, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)
(3) от EST до UTC - Не в порядке. Смещение времени составляет 4 часа 56 минут. Предполагается, что он будет 5 часов
>>> buggy_timezone_converter(parse('2013-02-26T04:00:00'), target_tz='UTC', current_tz='US/Eastern')
Out[610]: datetime.datetime(2013, 2, 26, 8, 56, tzinfo=<UTC>)
(4) от EDT до UTC - не в порядке. Смещение времени составляет 4 часа 56 минут. Предполагается, что он будет 4 часа. Летнее время не учитывается.
>>> buggy_timezone_converter(parse('2013-05-26T04:00:00'), current_tz='US/Eastern', target_tz='UTC')
Out[611]: datetime.datetime(2013, 5, 26, 8, 56, tzinfo=<UTC>)
Вторая функция (отлично работает)
В приведенной ниже функции используется pytz.timezone.localize(datetime.datetime)
. Он отлично работает
def good_timezone_converter(input_dt, current_tz='UTC', target_tz='US/Eastern'):
current_tz = pytz.timezone(current_tz)
target_tz = pytz.timezone(target_tz)
target_dt = current_tz.localize(input_dt).astimezone(target_tz)
return target_tz.normalize(target_dt)
(1) от UTC до EST - OK
>>> good_timezone_converter(parse('2013-02-26T04:00:00'))
Out[618]: datetime.datetime(2013, 2, 25, 23, 0, tzinfo=<DstTzInfo 'US/Eastern' EST-1 day, 19:00:00 STD>)
(2) от UTC до EDT - OK
>>> good_timezone_converter(parse('2013-05-26T04:00:00'))
Out[619]: datetime.datetime(2013, 5, 26, 0, 0, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)
(3) от EST до UTC - ОК.
>>> good_timezone_converter(parse('2013-02-26T04:00:00'), current_tz='US/Eastern', target_tz='UTC')
Out[621]: datetime.datetime(2013, 2, 26, 9, 0, tzinfo=<UTC>)
(4) от EDT до UTC - ОК.
>>> good_timezone_converter(parse('2013-05-26T04:00:00'), current_tz='US/Eastern', target_tz='UTC')
Out[620]: datetime.datetime(2013, 5, 26, 8, 0, tzinfo=<UTC>)
Ответы
Ответ 1
Я предполагаю, что у вас есть следующие вопросы:
- Почему первая функция работает для часовой пояс UTC?
- почему он не работает для
'US/Eastern'
часового пояса (экземпляр DstTzInfo
)?
- почему вторая функция работает для всех приведенных примеров?
Первая функция неверна, поскольку вместо dsttzinfo_instance.localize(d)
используется d.replace(tzinfo=dsttzinfo_instance)
.
Вторая функция правильная большая часть времени, за исключением двусмысленных или несуществующих времен, например, во время переходов DST - вы можете изменить поведение, передав параметр is_dst
на .localize()
: False
(по умолчанию)/True
/None
(вызвать исключение).
Первая функция работает для часовой пояс UTC, потому что у нее фиксированное смещение utc (ноль) для любой даты. Другие часовые пояса, такие как America/New_York
, могут иметь разные разности utc в разное время (летнее время, время войны, в любое время, которое может думать какой-то локальный политик, это хорошая идея - это может быть что угодно - база данных tz работает в большинстве случаев). Для реализации методов tzinfo.utcoffset(dt)
, tzinfo.tzname(dt)
, tzinfo.dst(dt)
pytz
используется коллекция экземпляров DstTzInfo
, каждая из которых имеет различный набор атрибутов (_tzname, _utcoffset, _dst)
. Учитывая dt
(дата/время) и is_dst
, метод .localize()
выбирает подходящий (в большинстве случаев, но не всегда) экземпляр DstTzInfo
из коллекции. pytz.timezone('America/New_York')
возвращает экземпляр DstTzInfo
с атрибутами (_tzname, _utcoffset, _dst)
, которые соответствуют некоторому недокументированному моменту времени (разные версии pytz
могут возвращать разные значения - текущая версия может возвращать экземпляр tzinfo
, который соответствует самой ранней дате для который доступен для зоныinfo - вы не хотите этого значения большую часть времени: я думаю, что мотивация выбора значения по умолчанию заключается в том, чтобы выделить ошибку (передав pytz.timezone
в datetime
конструктор или .replace()
метод).
Подводя итог: .localize()
выбирает соответствующие значения utcoffset, tzname, dst, .replace()
использует значение по умолчанию (неуместное). UTC имеет только один набор utcoffset, tzname, dst, поэтому значение по умолчанию может использоваться, а метод .replace()
работает с часовым поясом UTC. Вам необходимо передать объект datetime и параметр is_dst
, чтобы выбрать соответствующие значения для других часовых поясов, таких как 'America/New_York'
.
В принципе, pytz
мог бы вызвать метод localize()
для реализации методов utcoffset()
, tzname()
, dst()
, даже если dt.tzinfo == self
: он сделает эти методы O (log n) во времени, когда n
- это количество интервалов с разными значениями (utcoffset, tzname, dst), но конструктор datetime
и .replace()
будут работать как есть, то есть явный вызов localize()
необходим только для передачи is_dst
.