Потеря информации о летнем времени с помощью преобразований 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.