Преобразование часового пояса Datetime с использованием pytz

Это просто еще один пост на pytz.

Есть две функции для преобразования объектов datetime между двумя часовыми поясами. Вторая функция работает во всех случаях. Первая функция не работает в двух случаях (3) и (4). В аналогичном сообщении SO такой проблемы не было. Любое объяснение, основанное на разнице между 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 - ОК

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

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

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

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

person Asif Rehan    schedule 17.12.2014    source источник
comment
Невозможно воспроизвести: >>> timezone_converter(datetime.datetime(2013,02,26,4,0,0,0), target_tz='UTC', current_tz='US/Eastern') datetime.datetime(2013, 2, 26, 9, 0, tzinfo=<UTC>)   -  person Mark Ransom    schedule 17.12.2014
comment
Извините, забыл добавить строки импорта ››› от dateutil.parser import parse ››› import pytz   -  person Asif Rehan    schedule 18.12.2014
comment
Либо ваш вопрос дублирует pytz localize vs datetime replace, либо вам следует задать более конкретный вопрос (обновите текущий вопрос), если вас не устраивают существующие ответы. Пожалуйста, используйте разные имена для первой и второй функции, например, convert_tz_replace() и convert_tz().   -  person jfs    schedule 20.12.2014
comment
@MarkRansom: проверьте еще раз (обновите pytz). timezone_converter_replace(datetime(2013,2,26,4,0,0,0), target_tz='UTC', current_tz='US/Eastern') - ›datetime.datetime(2013, 2, 26, 8, 56, tzinfo=<UTC>) т. Е. .replace() не выполняется, как указано в документации.   -  person jfs    schedule 20.12.2014
comment
@ J.F.Sebastian, это прямая копия / вставка из моего теста. Не знаю, почему у меня разные результаты, но это было. Может, это какая-то подсказка.   -  person Mark Ransom    schedule 20.12.2014


Ответы (1)


Я предполагаю, что у вас есть следующие вопросы:

  • почему первая функция работает для часового пояса UTC?
  • почему это не удается для 'US/Eastern' часового пояса (DstTzInfo экземпляр)?
  • почему вторая функция работает для всех приведенных примеров?

Первая функция неверна, потому что она использует d.replace(tzinfo=dsttzinfo_instance) вместо dsttzinfo_instance.localize(d).

Вторая функция верна большую часть времени, за исключением неоднозначного или несуществующего времени, например, во время перехода на летнее время - вы можете изменить поведение, передав параметр is_dst в .localize(): False (по умолчанию) / _ 8 _ / _ 9_ (вызвать исключение).

Первая функция работает для часового пояса 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 экземпляр, который соответствует самой ранней дате, для которой указана информация о зоне доступно - вам не нужно это значение большую часть времени: я думаю, что мотивация выбора значения по умолчанию состоит в том, чтобы выделить ошибку (передача 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.

person jfs    schedule 21.12.2014