Каноническое смещение от UTC с использованием pytz?

Мне любопытно, как назвать «каноническое» смещение часового пояса, которое предлагается для определенных селекторов часового пояса (если есть даже такая вещь, как каноническое смещение, в чем я даже не уверен).

Например, в Windows XP вы можете видеть, что для Eastern Time (US & Canada) в раскрывающемся списке всегда отображается GMT-05:00 На самом деле это неверно для всего года, поскольку при переходе на летнее время смещение с UTC составляет только -4:00. С другой стороны, все упоминают, что US/Eastern отличается от UTC на 5 часов. Мне было интересно, как этот -5:00 называется. Идентификатор времени электронной почты? Каноническое смещение часового пояса?

Кроме того, если я создам часовой пояс US/Eastern с pytz, есть ли способ получить это -5:00 независимо от того, находится ли фактическое время сейчас в летнее время или нет? Я имею в виду... Я хотел бы знать, есть ли функция или... что-то, чтобы получить -5:00, независимо от того, запускаю ли я это сегодня или в середине августа (когда DST включен, а фактическое смещение равно просто -4:00)

Селектор часового пояса Windows XP

Изображение из http://www.microsoft.com/library/media/1033/windowsxp/images/using/setup/tips/67446-change-time-zone.gif

Заранее спасибо.


person BorrajaX    schedule 15.12.2014    source источник


Ответы (1)


Если мы под «каноническим» подразумеваем utcoffset дат, которые не находятся в DST, то проблема сводится к поиску дат (для каждого часового пояса), которые не являются DST.

Мы могли бы сначала попробовать текущую дату. Если это не DST, то нам повезло. Если это так, то мы могли бы пройтись по списку дат перехода utc (которые хранятся в tzone._utc_transition_times), пока не найдем ту, которая не является DST:

import pytz
import datetime as DT
utcnow = DT.datetime.utcnow()

canonical = dict()
for name in pytz.all_timezones:
    tzone = pytz.timezone(name)
    try:
        dstoffset = tzone.dst(utcnow, is_dst=False)
    except TypeError:
        # pytz.utc.dst does not have a is_dst keyword argument
        dstoffset = tzone.dst(utcnow)
    if dstoffset == DT.timedelta(0):
        # utcnow happens to be in a non-DST period
        canonical[name] = tzone.localize(utcnow, is_dst=False).strftime('%z') 
    else:
        # step through the transition times until we find a non-DST datetime
        for transition in tzone._utc_transition_times[::-1]:
            dstoffset = tzone.dst(transition, is_dst=False) 
            if dstoffset == DT.timedelta(0):
                canonical[name] = (tzone.localize(transition, is_dst=False)
                                   .strftime('%z'))
                break

for name, utcoffset in canonical.iteritems():
    print('{} --> {}'.format(name, utcoffset)) 

# All timezones have been accounted for
assert len(canonical) == len(pytz.all_timezones)

урожаи

...
Mexico/BajaNorte --> -0800
Africa/Kigali --> +0200
Brazil/West --> -0400
America/Grand_Turk --> -0400
Mexico/BajaSur --> -0700
Canada/Central --> -0600
Africa/Lagos --> +0100
GMT-0 --> +0000
Europe/Sofia --> +0200
Singapore --> +0800
Africa/Tripoli --> +0200
America/Anchorage --> -0900
Pacific/Nauru --> +1200

Обратите внимание, что приведенный выше код обращается к закрытому атрибуту tzone._utc_transition_times. Это деталь реализации в pytz. Поскольку он не является частью общедоступного API, его существование в будущих версиях pytz не гарантируется. На самом деле, он даже не существует для всех часовых поясов в текущей версии pytz — в частности, он не существует для часовых поясов, в которых нет времени перехода на летнее время, таких как, например, 'Africa/Bujumbura'. (Вот почему я сначала потрудился проверить, не находится ли utcnow в периоде времени, отличном от DST.)

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

Вот как выглядел бы код без использования _utc_transition_times:

import pytz
import datetime as DT
utcnow = DT.datetime.utcnow()

canonical = dict()
for name in pytz.all_timezones:
    tzone = pytz.timezone(name)
    try:
        dstoffset = tzone.dst(utcnow, is_dst=False)
    except TypeError:
        # pytz.utc.dst does not have a is_dst keyword argument
        dstoffset = tzone.dst(utcnow)
    if dstoffset == DT.timedelta(0):
        # utcnow happens to be in a non-DST period
        canonical[name] = tzone.localize(utcnow, is_dst=False).strftime('%z') 
    else:
        # step through the transition times until we find a non-DST datetime
        date = utcnow
        while True:
            date = date - DT.timedelta(days=1)
            dstoffset = tzone.dst(date, is_dst=False) 
            if dstoffset == DT.timedelta(0):
                canonical[name] = (tzone.localize(date, is_dst=False)
                                   .strftime('%z'))
                break

for name, utcoffset in canonical.iteritems():
    print('{} --> {}'.format(name, utcoffset)) 

# All timezones have been accounted for
assert len(canonical) == len(pytz.all_timezones)
person unutbu    schedule 15.12.2014