Переход на летнее время в Python
Я пишу программу, которая имеет дело с часовыми поясами и пересекает их. Две вещи, с которыми я сталкиваюсь больше всего, - это создание объекта datetime из "сейчас", а затем локализация наивного объекта datetime.
Чтобы создать объект datetime в тихом часовом поясе, я сейчас делаю это (python 2.7.2+)
from datetime import datetime
import pytz
la = pytz.timezone("America/Los_Angeles")
now = datetime.now(la)
Это правильно в отношении DST? Если нет, я полагаю, я должен делать:
now2 = la.localize(datetime.now())
У меня вопрос почему? Кто-нибудь может показать мне случай, когда первое неверно, а второе верно?
Что касается моего секундного вопроса, предположим, что у меня была наивная дата и время от некоторого пользовательского ввода за 9 сентября 2012 года в 8:00 утра в Лос-Анджелесе, штат Калифорния. Это правильный способ сделать дату и время так:
la.localize(datetime(2012, 9, 1, 8, 0))
Если нет, то как мне строить эти даты?
4 ответа
Из документации по pytz:
Предпочтительный способ работы со временем - всегда работать в формате UTC, преобразовывая его в местное время только при генерации выходных данных, которые могут быть прочитаны людьми.
Так что в идеале вы должны использовать utcnow
вместо now
,
Предполагая, по какой-то причине, что ваши руки связаны и вам нужно работать по местному времени, вы все равно можете столкнуться с проблемой при попытке локализовать текущее время, если вы делаете это во время перехода на летнее время. Такой же datetime
может произойти дважды, один раз в дневное время и снова в стандартное время, и localize
Метод не знает, как урегулировать конфликт, если вы не укажете это явно с помощью is_dst
параметр.
Итак, чтобы узнать текущее время UTC:
utc = pytz.timezone('UTC')
now = utc.localize(datetime.datetime.utcnow())
И преобразовать его в ваше местное время (но только тогда, когда вам нужно):
la = pytz.timezone('America/Los_Angeles')
local_time = now.astimezone(la)
Редактировать: как указано в комментариях jfs, ваш первый пример с использованием datetime.now(tz)
будет работать во всех случаях. Ваш второй пример терпит неудачу во время осеннего перехода, как я обрисовал выше. Я все еще выступаю за использование UTC вместо местного времени для всего, кроме отображения.
Первое решение является правильным в отношении DST, а второе решение является плохим.
Я приведу пример. Здесь, в Европе, при запуске этого кода:
from datetime import datetime
import pytz # $ pip install pytz
la = pytz.timezone("America/Los_Angeles")
fmt = '%Y-%m-%d %H:%M:%S %Z%z'
now = datetime.now(la)
now2 = la.localize(datetime.now())
now3 = datetime.now()
print(now.strftime(fmt))
print(now2.strftime(fmt))
print(now3.strftime(fmt))
Я получаю следующее:
2012-08-30 12:34:06 PDT-0700
2012-08-30 21:34:06 PDT-0700
2012-08-30 21:34:06
datetime.now(la)
создает дату и время с текущим временем в LA, а также информацию о часовом поясе для LA.
la.localize(datetime.now())
добавляет информацию о часовом поясе к наивной дате и времени, но не конвертирует часовой пояс; это просто предполагает, что время уже было в этом часовом поясе.
datetime.now()
создает наивное время даты (без информации о часовом поясе) с местным временем.
Пока вы находитесь в Лос-Анджелесе, вы не увидите разницы, но если ваш код когда-либо выполняется где-то еще, он, вероятно, не будет делать то, что вы хотели.
Кроме того, если вам когда-либо понадобится серьезно работать с часовыми поясами, лучше проводить все время в UTC, что избавит вас от многих проблем с DST.
Это работает:
# naive datetime
d = datetime.datetime(2016, 11, 5, 16, 43, 45)
utc = pytz.UTC # UTC timezone
pst = pytz.timezone('America/Los_Angeles') # LA timezone
# Convert to UTC timezone aware datetime
d = utc.localize(d)
>>> datetime.datetime(2016, 11, 5, 16, 43, 45, tzinfo=<UTC>)
# show as in LA time zone (not converting here)
d.astimezone(pst)
>>> datetime.datetime(2016, 11, 5, 9, 43, 45,
tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>)
# we get Pacific Daylight Time: PDT
# add 1 day to UTC date
d = d + datetime.timedelta(days=1)
>>> datetime.datetime(2016, 11, 6, 16, 43, 45, tzinfo=<UTC>)
d.astimezone(pst) # now cast to LA time zone
>>> datetime.datetime(2016, 11, 6, 8, 43, 45,
tzinfo=<DstTzInfo 'America/Los_Angeles' PST-1 day, 16:00:00 STD>)
# Daylight saving is applied -> we get Pacific Standard Time PST
Это не работает:
# naive datetime
d = datetime.datetime(2016, 11, 5, 16, 43, 45)
utc = pytz.UTC # UTC timezone
pst = pytz.timezone('America/Los_Angeles') # LA timezone
# convert to UTC timezone aware datetime
d = utc.localize(d)
>>> datetime.datetime(2016, 11, 5, 16, 43, 45, tzinfo=<UTC>)
# convert to 'America/Los_Angeles' timezone: DON'T DO THIS
d = d.astimezone(pst)
>>> datetime.datetime(2016, 11, 5, 9, 43, 45,
tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>)
# we are in Pacific Daylight Time PDT
# add 1 day to LA local date: DON'T DO THAT
d = d + datetime.timedelta(days=1)
>>> datetime.datetime(2016, 11, 6, 9, 43, 45,
tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>)
# Daylight Saving is NOT respected, we are still in PDT time, not PST
Заключение:
datetime.timedelta()
НЕ учитывает переход на летнее время.
Делайте свое время складывать / вычитать в часовом поясе UTC ВСЕГДА. Приведение к местному времени только для вывода / отображения.
Сайт pytz сообщает:
К сожалению, использование аргумента tzinfo стандартных конструкторов datetime '' не работает '' с pytz для многих часовых поясов.
Так что не стоит использовать datetime.now(la)
, Я не знаю специфики, но некоторые часовые пояса работают по более экзотическим правилам, чем мы привыкли, и код datetime Python не может их обработать. Используя код pytz, они должны обрабатываться правильно, так как это цель pytz. Это может также иметь проблемы для времен, которые происходят дважды благодаря скачкам времени при летнем сбережении.
Что касается второго вопроса, это именно то, что показывает документация, так что вы должны быть хорошими.