Потеря информации о летнем времени с помощью преобразований pytz и UTC

Может быть, это ошибка 4 утра, но я думаю, что я все делаю правильно, но не похоже, что DST переводит метку времени UTC в локализованную дату и время.

>>> from datetime import datetime
>>> import pytz
>>> eastern = pytz.timezone("US/Eastern")
>>> utc = pytz.utc
>>> local_now = eastern.localize(datetime.now())
>>> utc_now = local_now.astimezone(utc)
>>> seconds = int(utc_now.strftime("%s"))
>>> utc_then = utc.localize(datetime.fromtimestamp(seconds))
>>> local_then = utc_then.astimezone(eastern)
>>> print utc_now, utc_then
2013-06-16 10:05:27.893005+00:00 2013-06-16 11:05:27+00:00
>>> print local_now, local_then
2013-06-16 06:05:27.893005-04:00 2013-06-16 07:05:27-04:00

3 ответа

Решение
             o------------o
             |            |  DT.datetime.utcfromtimestamp (*)
             |            |<-----------------------------------o
             |            |                                    |
             |  datetime  |                                    |
             |            |  DT.datetime.fromtimestamp         |
             |            |<----------------------------o      |
             |            |                             |      |
             o------------o                             |      |
                |   ^                                   |      |
     .timetuple |   |                                   |      |
  .utctimetuple |   | DT.datetime(*tup[:6])             |      |
                v   |                                   |      |
             o------------o                          o------------o
             |            |-- calendar.timegm (*) -->|            |
             |            |                          |            |
             |            |---------- time.mktime -->|            |
             |  timetuple |                          |  timestamp |
             |            |<-- time.localtime -------|            |
             |            |                          |            |
             |            |<-- time.gmtime (*)-------|            |
             o------------o                          o------------o

(*) Interprets its input as being in UTC and returns output in UTC

Как показано на диаграмме, когда у вас есть дата и время в формате UTC, такие как utc_now, чтобы получить его метку времени, используйте

seconds = calendar.timegm(utc_date.utctimetuple())

Если у вас есть метка времени, чтобы перейти к дате в UTC, используйте

DT.datetime.utcfromtimestamp(seconds)

import datetime as DT
import pytz
import calendar
eastern = pytz.timezone("US/Eastern")
utc = pytz.utc
now = DT.datetime(2013, 6, 16, 10, 0, 0)
local_now = eastern.localize(now)
utc_now = local_now.astimezone(utc)
seconds = calendar.timegm(utc_now.utctimetuple())

print(seconds)
# 1371391200

utc_then = utc.localize(DT.datetime.utcfromtimestamp(seconds))
local_then = utc_then.astimezone(eastern)

print utc_now, utc_then
# 2013-06-16 14:00:00+00:00 2013-06-16 14:00:00+00:00
print local_now, local_then
# 2013-06-16 10:00:00-04:00 2013-06-16 10:00:00-04:00

PS. Обратите внимание, что timetuple() а также utctimetuple() методы сбрасывают микросекунды с даты и времени. Чтобы преобразовать дату и время в метку времени, сохраняющую микросекунды, используйте mata.

Вам следует избегать datetime.now если вы хотите написать переносимый код, так как он всегда использует местный часовой пояс, так local_now = eastern.localize(datetime.now()) будет работать только если часовой пояс на локальной машине восточный. Всегда старайтесь использовать utcnow и по той же причине utcfromtimestamp,

Кроме того, используя strftime("%s") преобразовать дату и время в отметку времени не работает.

from datetime import datetime
import pytz

utc_now = pytz.utc.localize(datetime.utcnow())
eastern = pytz.timezone("US/Eastern")
local_now = utc_now.astimezone(eastern)

# seconds = utc_now.timestamp()  python3
seconds = (utc_now - pytz.utc.localize(datetime.utcfromtimestamp(0))).total_seconds()
utc_then = pytz.utc.localize(datetime.utcfromtimestamp(seconds))

local_then = utc_then.astimezone(eastern)

print("%s - %s" % (utc_now, utc_then))
print("%s - %s" % (local_now, local_then))

Чтобы получить местный часовой пояс как pytz.timezone объект, вы могли бы использовать tzlocal модуль:

#!/usr/bin/env python
from datetime import datetime
import pytz # pip install pytz
from tzlocal import get_localzone # pip install tzlocal

local_tz = get_localzone()
local_now = datetime.now(local_tz)
utc_now = local_now.astimezone(pytz.utc)
seconds = (utc_now - datetime(1970, 1, 1, tzinfo=pytz.utc)).total_seconds()

utc_then = datetime.fromtimestamp(seconds, pytz.utc)
local_then = utc_then.astimezone(local_tz)
print("%s %s" % (utc_now, utc_then))
print("%s %s" % (local_now, local_then))
  • не использовать datetime.now() - это может быть неоднозначно, например, во время изменения летнего времени. Либо передайте tzinfo явно, как в моем примере, либо используйте datetime.utcnow()
  • не использовать utc_now.strftime('%s') - он игнорирует информацию о часовом поясе (он использует текущий местный часовой пояс) и не является переносимым. использование datetime.timestamp() метод или его аналоги вместо
  • не использовать utc.localize(datetime.fromtimestamp(seconds)) - .fromtimestamp() возвращает наивный объект datetime в местном часовом поясе, который может отличаться от UTC. Либо передайте tzinfo явно, как в моем примере, либо используйте datetime.utcfromtimestamp() получить наивный объект datetime, который представляет время UTC
  • не использовать datetime.utctimetuple() с наивными объектами datetime - он не конвертирует их в UTC. Если объект уже в UTC: utc_now.timetuple() возвращается в то же время.

Чтобы вызвать исключение для неоднозначного местного времени, используйте localize(is_dst=None):

aware_dt = tz.localize(naive_dt, is_dst=None)

Без is_dst=Noneвероятность того, что tz.localize() возвращает неверный результат во время изменения летнего времени. Если в вашем конкретном случае предпочтительнее получить возможно неправильный результат, чем исключение, вы можете передать явное is_dst=False напомнить себе об этом.

В общем, pytz документация рекомендует tz.normalize() после .astimezone() звоните, если ни часовой пояс источника, ни пункт назначения не являются UTC.

Другие вопросы по тегам