Преобразование строки даты с учетом часового пояса в UTC и обратно в Python - PullRequest
9 голосов
/ 19 сентября 2011

Я анализирую ленту оповещений Национальной службы погоды в веб-приложении. Я хотел бы удалить оповещения, когда они истекают. Я также хотел бы отобразить срок действия в формате местного времени для географической области, к которой они относятся.

Предупреждения охватывают всю территорию США, поэтому я думаю, что лучший способ - хранить и сравнивать время в метках времени UTC. Время истечения поступает в ленту в виде строки, подобной этой: 2011-09-09T22:12:00-04:00.

Я использую пакет Labix dateutils для анализа строки с учетом часового пояса:

>>> from dateutil.parser import parse
>>> d = parse("2011-09-18T15:52:00-04:00")
>>> d
datetime.datetime(2011, 9, 18, 15, 52, tzinfo=tzoffset(None, -14400))

Я также могу зафиксировать смещение UTC в часах:

>>> offset_hours = (d.utcoffset().days * 86400 + d.utcoffset().seconds) / 3600
>>> offset_hours
-4

Используя методы datetime.utctimetuple() и time.mktime(), я могу преобразовать проанализированную дату в метку времени UTC:

>>> import time
>>> expiration_utc_ts = time.mktime(d.utctimetuple())
>>> expiration_utc_ts
1316393520.0

На данный момент, я чувствую себя довольно хорошо, что я могу преобразовать необработанные строки во временную метку, представляющую время истечения в UTC. Я могу сравнить текущее время как метку времени UTC с истечением срока действия и определить, нужно ли его очищать:

>>> now_utc_ts = time.mktime(time.gmtime())
>>> now_utc_ts
1316398744.0
>>> now_utc_ts >= expiration_tc_ts
True

Трудность, с которой я столкнулся, заключается в попытке преобразовать мою сохраненную временную метку UTC обратно в исходный локализованный формат. У меня есть часы смещения, сохраненные из исходного преобразования, и строка, которую я проанализировал для хранения метки часового пояса:

>>> print offset_hours
-4
>>> print timezone
EDT

Я бы хотел преобразовать временную метку UTC обратно в локально отформатированное время, но преобразование ее обратно в datetime, похоже, не работает:

>>> import datetime
>>> datetime.datetime.fromtimestamp(expiration_utc_ts) + datetime.timedelta(hours=offset_hours)
datetime.datetime(2011, 9, 18, 16, 52) # The hour is 16 but it should be 15

Похоже, он выключен на час. Я не уверен, где ошибка была введена? Я собрал другой тест и получил похожие результаты:

>>> # Running this at 21:29pm EDT
>>> utc_now = datetime.datetime.utcnow()
>>> utc_now_ts = time.mktime(right_now.utctimetuple())
>>> datetime.datetime.fromtimestamp(utc_now_ts)
datetime.datetime(2011, 9, 18, 22, 29, 47) # Off by 1 hour

Может ли кто-нибудь помочь мне найти мою ошибку? Я не уверен, что это проблема летнего времени? Я столкнулся с некоторыми вещами, которые заставляют меня поверить, что это может быть попытка локализовать мои даты и время, но на данный момент я довольно озадачен. Я надеялся провести все эти вычисления / сравнения в зависимости от часового пояса.

Ответы [ 2 ]

3 голосов
/ 28 сентября 2011

Проблема в том, что летнее время применяется дважды.

Тривиальный пример:

>>> time_tuple = datetime(2011,3,13,2,1,1).utctimetuple()
time.struct_time(tm_year=2011, tm_mon=3, tm_mday=13, tm_hour=2, tm_min=1, tm_sec=1, tm_wday=6, tm_yday=72, tm_isdst=0)
>>> datetime.fromtimestamp(time.mktime(time_tuple))
datetime.datetime(2011, 3, 13, 3, 1, 1)

Я вполне уверен, что ошибка лежит в пределах time.mktime().Как сказано в его документации :

Это обратная функция localtime ().Его аргументом является struct_time или полный 9-кортеж (поскольку необходим флаг dst; используйте -1 в качестве флага dst, если он неизвестен), который выражает время по местному времени, а не по UTC.Возвращает число с плавающей запятой для совместимости с time ().Если входное значение не может быть представлено в качестве допустимого времени, будет сгенерировано либо OverflowError, либо ValueError (это зависит от того, будет ли неверное значение перехвачено Python или базовыми библиотеками C).Самая ранняя дата, для которой он может генерировать время, зависит от платформы.

Когда вы передаете кортеж времени в time.mktime(), он ожидает флаг о том, является ли время при переходе на летнее время.время или нет.Как вы можете видеть выше, utctimetuple() возвращает кортеж с этим флагом, помеченным 0, как сказано в документации :

Если экземпляр datetime dнаивно, это то же самое, что и d.timetuple (), за исключением того, что tm_isdst принудительно устанавливается в 0 независимо от того, что возвращает d.dst ().DST никогда не действует в течение времени UTC.

Если d знает, d нормализуется к времени UTC, вычитая d.utcoffset (), и возвращается time.struct_time для нормализованного времени.Значение tm_isdst равно 0. Обратите внимание, что членом результата tm_year может быть MINYEAR-1 или MAXYEAR + 1, если d.year был MINYEAR или MAXYEAR, а корректировка UTC выливается за границу года.

Поскольку выtime.mktime() сказал, что ваше время не летнее, и его задача - переводить все время в местное время, и в настоящее время это летнее время в вашем регионе, оно добавляет час , чтобы сделать его летнимвремя.Отсюда и результат.


Хотя у меня нет удобного поста, пару дней назад я натолкнулся на метод преобразования дат с учетом часовых поясов в наивные по местному времени.Это может работать намного лучше для вашего приложения, чем то, что вы в настоящее время делаете (использует превосходный модуль pytz ):

import pytz
def convert_to_local_time(dt_aware):
    tz = pytz.timezone('America/Los_Angeles') # Replace this with your time zone string
    dt_my_tz = dt_aware.astimezone(tz)
    dt_naive = dt_my_tz.replace(tzinfo=None)
    return dt_naive

Замените 'America / LosAngeles' на вашу собственную строку часового пояса, котораяВы можете найти где-нибудь в pytz.all_timezones.

0 голосов
/ 20 августа 2014

datetime.fromtimestamp() - правильный метод для получения местного времени из метки времени POSIX. Проблема в вашем вопросе заключается в том, что вы конвертируете осведомленный объект datetime в метку времени POSIX, используя time.mktime(), что неверно. Вот один из правильных способов сделать это :

expiration_utc_ts = (d - datetime(1970, 1, 1, tzinfo=utc)).total_seconds()
local_dt = datetime.fromtimestamp(expiration_utc_ts)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...