Как найти временную метку Unix следующего дня для того же часа, включая DST, в Python?
В Python я могу найти метку времени Unix по местному времени, зная часовой пояс, например так (используя pytz):
>>> import datetime as DT
>>> import pytz
>>> mtl = pytz.timezone('America/Montreal')
>>> naive_time3 = DT.datetime.strptime('2013/11/03', '%Y/%m/%d')
>>> naive_time3
datetime.datetime(2013, 11, 3, 0, 0)
>>> localized_time3 = mtl.localize(naive_time3)
>>> localized_time3
datetime.datetime(2013, 11, 3, 0, 0, tzinfo=<DstTzInfo 'America/Montreal' EDT-1 day, 20:00:00 DST>)
>>> localized_time3.timestamp()
1383451200.0
Все идет нормально. naive_time
не знает о часовом поясе, тогда как localized_time
знает свою полночь 2013/11/03 в Монреале, так что отметка времени (UTC) Unix хороша. Этот часовой пояс также является моим местным часовым поясом, и эта временная метка выглядит правильно:
$ date -d @1383451200
Sun Nov 3 00:00:00 EDT 2013
Теперь часы были настроены на один час назад 3 ноября в 2:00 здесь, в Монреале, поэтому мы получили дополнительный час в тот день. Это означает, что здесь было 25 часов между 2013/11/03 и 2013/11/04. Это показывает это:
>>> naive_time4 = DT.datetime.strptime('2013/11/04', '%Y/%m/%d')
>>> localized_time4 = mtl.localize(naive_time4)
>>> localized_time4
datetime.datetime(2013, 11, 4, 0, 0, tzinfo=<DstTzInfo 'America/Montreal' EST-1 day, 19:00:00 STD>)
>>> (localized_time4.timestamp() - localized_time3.timestamp()) / 3600
25.0
Теперь я ищу простой способ получить localized_time4
объект из localized_time3
зная, что я хочу получить следующий локализованный день в тот же час (здесь, в полночь). Я старался timedelta
, но я считаю, что он не знает о часовых поясах или летнее время:
>>> localized_time4td = localized_time3 + DT.timedelta(1)
>>> localized_time4td
datetime.datetime(2013, 11, 4, 0, 0, tzinfo=<DstTzInfo 'America/Montreal' EDT-1 day, 20:00:00 DST>)
>>> (localized_time4td.timestamp() - localized_time3.timestamp()) / 3600
24.0
Моя цель - получить информацию о записях в журнале, которые хранятся вместе с меткой времени Unix для каждого местного дня. Конечно, если я использую localized_time3.timestamp()
и добавить 24 * 3600
здесь (который будет таким же, как localized_time4td.timestamp()
), Я пропущу все записи в журнале, которые произошли между localized_time4td.timestamp()
а также localized_time4td.timestamp() + 3600
,
Другими словами, функция или метод, который я ищу, должны знать, когда добавить 25 часов, 24 часа или 23 часа иногда к метке времени Unix, в зависимости от того, когда происходят сдвиги летнего времени.
4 ответа
Без использования нового пакета:
def add_day(x):
d = x.date()+DT.timedelta(1)
return mtl.localize(x.replace(year=d.year, month=d.month, day=d.day, tzinfo=None))
Полный скрипт:
import datetime as DT
import pytz
import calendar
mtl = pytz.timezone('America/Montreal')
naive_time3 = DT.datetime.strptime('2013/11/03', '%Y/%m/%d')
print repr(naive_time3)
#datetime.datetime(2013, 11, 3, 0, 0)
localized_time3 = mtl.localize(naive_time3)
print repr(localized_time3)
#datetime.datetime(2013, 11, 3, 0, 0, tzinfo=<DstTzInfo 'America/Montreal' EDT-1 day, 20:00:00 DST>)
print calendar.timegm(localized_time3.utctimetuple())
#1383451200.0
def add_day(x):
d = x.date()+DT.timedelta(1)
return mtl.localize(x.replace(year=d.year, month=d.month, day=d.day, tzinfo=None))
print repr(add_day(localized_time3))
#datetime.datetime(2013, 11, 4, 0, 0, tzinfo=<DstTzInfo 'America/Montreal' EST-1 day, 19:00:00 STD>)
(calendar
для Python2.)
В конце этого ответа я постепенно предоставляю несколько решений с наиболее надежным решением, которое пытается решить следующие проблемы:
- utc смещение из-за DST
- прошлые даты, когда местный часовой пояс мог иметь различное смещение utc по причине, не связанной с летним временем.
dateutil
и решения stdlib терпят неудачу здесь на некоторых системах, особенно Windows - неоднозначные времена во время летнего времени (не знаю,
Arrow
предоставляет интерфейс для обработки) - несуществующие времена во время летнего времени (то же самое)
Чтобы найти временную метку POSIX для завтрашней полуночи (или другого фиксированного часа) в заданном часовом поясе, вы можете использовать код из Как получить время UTC для "полуночи" для данного часового пояса?:
from datetime import datetime, time, timedelta
import pytz
DAY = timedelta(1)
tz = pytz.timezone('America/Montreal')
tomorrow = datetime(2013, 11, 3).date() + DAY
midnight = tz.localize(datetime.combine(tomorrow, time(0, 0)), is_dst=None)
timestamp = (midnight - datetime(1970, 1, 1, tzinfo=pytz.utc)).total_seconds()
dt.date()
метод возвращает одну и ту же наивную дату как для наивного, так и для часового пояса dt
объекты.
Явная формула для отметки времени используется для поддержки версии Python до Python 3.3. Иначе .timestamp()
метод может быть использован в Python 3.3+.
Чтобы избежать двусмысленности при разборе входных данных во время переходов DST, которые неизбежны для .localize()
метод, если вы не знаете is_dst
Параметр, вы можете использовать метки времени Unix, хранящиеся с датами:
from datetime import datetime, time, timedelta
import pytz
DAY = timedelta(1)
tz = pytz.timezone('America/Montreal')
local_dt = datetime.fromtimestamp(timestamp_from_the_log, tz)
tomorrow = local_dt.date() + DAY
midnight = tz.localize(datetime.combine(tomorrow, time(0, 0)), is_dst=None)
timestamp = (midnight - datetime(1970, 1, 1, tzinfo=pytz.utc)).total_seconds()
Для поддержки других фиксированных часов (не только полуночи):
tomorrow = local_dt.replace(tzinfo=None) + DAY # tomorrow, same time
dt_plus_day = tz.localize(tomorrow, is_dst=None)
timestamp = dt_plus_day.timestamp() # use the explicit formula before Python 3.3
is_dst=None
вызывает исключение, если дата результата является неоднозначной или не существует. Чтобы избежать исключения, вы можете выбрать время, которое ближе всего к предыдущей дате со вчерашнего дня (то же самое состояние летнего времени, т.е. is_dst=local_dt.dst()
):
from datetime import datetime, time, timedelta
import pytz
DAY = timedelta(1)
tz = pytz.timezone('America/Montreal')
local_dt = datetime.fromtimestamp(timestamp_from_the_log, tz)
tomorrow = local_dt.replace(tzinfo=None) + DAY
dt_plus_day = tz.localize(tomorrow, is_dst=local_dt.dst())
dt_plus_day = tz.normalize(dt_plus_day) # to detect non-existent times
timestamp = (dt_plus_day - datetime(1970, 1, 1, tzinfo=pytz.utc)).total_seconds()
.localize()
уважает данное время, даже если оно не существует, поэтому .normalize()
требуется, чтобы исправить время. Вы можете поднять исключение здесь, если normalize()
метод изменяет свой ввод (в этом случае обнаруживается несуществующее время) для согласованности с другими примерами кода.
(Спасибо @rdodev за указание на Стрелку).
Используя Arrow, эта операция становится простой:
>>> import arrow
>>> import datetime as DT
>>> lt3 = arrow.get(DT.datetime(2013, 11, 3), 'America/Montreal')
>>> lt3
<Arrow [2013-11-03T00:00:00-04:00]>
>>> lt4 = arrow.get(DT.datetime(2013, 11, 4), 'America/Montreal')
>>> lt4
<Arrow [2013-11-04T00:00:00-05:00]>
>>> lt4.timestamp - (lt3.replace(days=1).timestamp)
0
>>> (lt3.replace(days=1).timestamp - lt3.timestamp) / 3600
25.0
Используя стрелки replace
метод, единственные имена единиц заменяют это свойство, в то время как множественное число добавляет к нему. Так lt3.replace(days=1)
4 ноября 2013 г. пока lt3.replace(day=1)
1 ноября 2013 г.
Здесь альтернатива на основе dateutil
:
>>> # In Spain we changed DST 10/26/2013
>>> import datetime
>>> import dateutil.tz
>>> # tzlocal gets the timezone of the computer
>>> dt1 = datetime.datetime(2013, 10, 26, 14, 00).replace(tzinfo=dateutil.tz.tzlocal())
>>> print dt1
2013-10-26 14:00:00+02:00
>>> dt2 = dt1 + datetime.timedelta(1)
>>> print dt2
2013-10-27 14:00:00+01:00
# see if we hace 25 hours of difference
>>> import time
>>> (time.mktime(dt2.timetuple()) - time.mktime(dt1.timetuple())) / 3600.0
25.0
>>> (float(dt2.strftime('%s')) - float(dt1.strftime('%s'))) / 3600 # the same
25.0