Преобразование временной зоны 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.