将timezone-aware日期字符串转换为UTC,然后返回Python。

时间:2021-01-03 02:03:49

I'm parsing the National Weather Service alerts feed into a web application. I'd like to purge the alerts when they hit their expiration time. I'd also like to display the expiration time in the local time format for the geographic area they pertain to.

我正在将国家天气服务警报信息解析为web应用程序。我想在警报到期时清除警报。我还想以本地时间格式显示它们所属的地理区域的过期时间。

The alerts cover the whole US, so I think the best approach is to store and compare the times in UTC timestamps. The expiration time arrives in the feed as a string like this: 2011-09-09T22:12:00-04:00.

警报覆盖了整个美国,所以我认为最好的方法是使用UTC时间戳存储和比较时间。过期时间以字符串形式出现在提要中:2011-09-09T22:12:00-04:00。

I'm using the Labix dateutils package to parse the string in a timezone-aware manner:

我正在使用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))

I'm also able to capture the UTC offset in hours:

我还可以捕获UTC偏移数小时:

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

Using the datetime.utctimetuple() and time.mktime() methods, I'm able to convert the parsed date to a UTC timestamp:

使用datetime.utctimetuple()和time.mktime()方法,我可以将解析后的日期转换为UTC时间戳:

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

At this point, I feel pretty good that I'm able to convert the raw strings into a timestamp representing the expiration time in UTC. I'm able to compare the current time as a UTC timestamp to the expiration and determine if it needs to be purged:

此时,我感觉很好,我能够将原始字符串转换为表示UTC过期时间的时间戳。我可以将当前时间作为UTC时间戳与过期时间进行比较,并确定是否需要清除它:

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

The difficulty I'm having is trying to convert my stored UTC timestamp back to the original localized format. I have the offset hours stored from the original conversion and a string I parsed to store the timezone label:

我遇到的困难是试图将存储的UTC时间戳转换回原始的本地化格式。我有从原始转换中存储的偏移时间和我解析的用于存储时区标签的字符串:

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

I'd like to convert the UTC timestamp back to a locally formatted time, but converting it back to a datetime doesn't seem to be working:

我想将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

It looks like it's off by an hour. I'm not sure where the error was introduced? I put together another test and got similar results:

看起来快一个小时了。我不知道错误是从哪里引入的?我做了另一个测试,得到了类似的结果:

>>> # 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

Can someone help me find my mistake? I'm not sure if it's a daylight savings issue? I came across some stuff that leads me to believe it might be trying to localize my dates and times but at this point I'm pretty stumped. I was hoping to do all of these calculations/comparisons in a timezone-agnostic manner.

有人能帮我找到我的错误吗?我不确定这是不是夏令时问题?我遇到了一些东西,让我相信它可能试图本地化我的日期和时间,但在这一点上,我很为难。我希望以一种时间域不可知的方式进行所有这些计算/比较。

2 个解决方案

#1


3  

The problem is that Daylight Savings time is being applied twice.

问题是夏时制被应用了两次。

A trivial example:

一个简单的例子:

>>> 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)

I am fairly certain that the fault lies within time.mktime(). As it says in its documentation:

我相当确定错误在time.mktime()内。正如它在文件中所说:

This is the inverse function of localtime(). Its argument is the struct_time or full 9-tuple (since the dst flag is needed; use -1 as the dst flag if it is unknown) which expresses the time in local time, not UTC. It returns a floating point number, for compatibility with time(). If the input value cannot be represented as a valid time, either OverflowError or ValueError will be raised (which depends on whether the invalid value is caught by Python or the underlying C libraries). The earliest date for which it can generate a time is platform-dependent.

这是localtime()的逆函数。它的参数是struct_time或full 9-tuple(因为需要dst标志;使用-1作为dst标志(如果未知),表示本地时间,而不是UTC。它返回一个浮点数,与时间的兼容性()。如果输入值不能表示为有效时间,则将引发OverflowError或ValueError(这取决于是否由Python或底层C库捕获无效值)。它能够生成时间的最早日期是平台相关的。

When you pass a time tuple to time.mktime(), it expects a flag on whether the time is in daylight savings time or not. As you can see above, utctimetuple() returns a tuple with that flag marked 0, as it says it will do in its documentation:

当您将时间元组传递给time.mktime()时,它会要求一个标志来确定时间是否在夏令时中。正如您可以看到的,utctimetuple()返回一个带有标记为0的元组,就像它在其文档中说的那样:

If datetime instance d is naive, this is the same as d.timetuple() except that tm_isdst is forced to 0 regardless of what d.dst() returns. DST is never in effect for a UTC time.

如果datetime实例d是幼稚的,那么它与d.timetuple()是相同的,但是无论d.dst()返回什么,tm_isdst都被强制为0。DST在UTC时间内不会生效。

If d is aware, d is normalized to UTC time, by subtracting d.utcoffset(), and a time.struct_time for the normalized time is returned. tm_isdst is forced to 0. Note that the result’s tm_year member may be MINYEAR-1 or MAXYEAR+1, if d.year was MINYEAR or MAXYEAR and UTC adjustment spills over a year boundary.

如果d是已知的,那么d通过减去d.utcoffset()和一个时间被规范化为UTC时间。返回规范化时间的struct_time。tm_isdst*为0。注意,结果的tm_year成员可能是MINYEAR-1或MAXYEAR+1,如果d。年是明年或上年度,而UTC的调整超过一年的界限。

Since you have told time.mktime() that your time is not DST, and its job is to convert all times into local time, and it is currently daylight savings time in your area, it adds an hour to make it daylight savings time. Hence the result.

因为您已经告诉time.mktime()您的时间不是DST,它的工作是将所有时间转换为本地时间,并且它现在是您所在地区的夏令时,它增加了一个小时来实现夏令时。因此结果。


While I don't have the post handy, I came across a method a couple of days ago to convert timezone-aware datetimes into naive ones in your local time. This might work much better for your application than what you are currently doing (uses the excellent pytz module):

虽然我手边没有这个帖子,但几天前我偶然发现了一种方法,可以将timezone-aware datetimes转换为本地时间的简单数据。这对于您的应用程序可能比您当前正在做的工作(使用优秀的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

Replace 'America/LosAngeles' with your own timezone string, which you can find somewhere in pytz.all_timezones.

用您自己的时区字符串替换“America/LosAngeles”,您可以在pytz.all_timezone中找到它。

#2


0  

datetime.fromtimestamp() is the correct method to get local time from POSIX timestamp. The issue in your question is that you convert an aware datetime object to POSIX timestamp using time.mktime() that is incorrect. Here's one of correct ways to do it:

fromtimestamp()是从POSIX时间戳获取本地时间的正确方法。您的问题是,您使用不正确的time.mktime()将一个aware datetime对象转换为POSIX时间戳。这里有一个正确的方法:

expiration_utc_ts = (d - datetime(1970, 1, 1, tzinfo=utc)).total_seconds()
local_dt = datetime.fromtimestamp(expiration_utc_ts)

#1


3  

The problem is that Daylight Savings time is being applied twice.

问题是夏时制被应用了两次。

A trivial example:

一个简单的例子:

>>> 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)

I am fairly certain that the fault lies within time.mktime(). As it says in its documentation:

我相当确定错误在time.mktime()内。正如它在文件中所说:

This is the inverse function of localtime(). Its argument is the struct_time or full 9-tuple (since the dst flag is needed; use -1 as the dst flag if it is unknown) which expresses the time in local time, not UTC. It returns a floating point number, for compatibility with time(). If the input value cannot be represented as a valid time, either OverflowError or ValueError will be raised (which depends on whether the invalid value is caught by Python or the underlying C libraries). The earliest date for which it can generate a time is platform-dependent.

这是localtime()的逆函数。它的参数是struct_time或full 9-tuple(因为需要dst标志;使用-1作为dst标志(如果未知),表示本地时间,而不是UTC。它返回一个浮点数,与时间的兼容性()。如果输入值不能表示为有效时间,则将引发OverflowError或ValueError(这取决于是否由Python或底层C库捕获无效值)。它能够生成时间的最早日期是平台相关的。

When you pass a time tuple to time.mktime(), it expects a flag on whether the time is in daylight savings time or not. As you can see above, utctimetuple() returns a tuple with that flag marked 0, as it says it will do in its documentation:

当您将时间元组传递给time.mktime()时,它会要求一个标志来确定时间是否在夏令时中。正如您可以看到的,utctimetuple()返回一个带有标记为0的元组,就像它在其文档中说的那样:

If datetime instance d is naive, this is the same as d.timetuple() except that tm_isdst is forced to 0 regardless of what d.dst() returns. DST is never in effect for a UTC time.

如果datetime实例d是幼稚的,那么它与d.timetuple()是相同的,但是无论d.dst()返回什么,tm_isdst都被强制为0。DST在UTC时间内不会生效。

If d is aware, d is normalized to UTC time, by subtracting d.utcoffset(), and a time.struct_time for the normalized time is returned. tm_isdst is forced to 0. Note that the result’s tm_year member may be MINYEAR-1 or MAXYEAR+1, if d.year was MINYEAR or MAXYEAR and UTC adjustment spills over a year boundary.

如果d是已知的,那么d通过减去d.utcoffset()和一个时间被规范化为UTC时间。返回规范化时间的struct_time。tm_isdst*为0。注意,结果的tm_year成员可能是MINYEAR-1或MAXYEAR+1,如果d。年是明年或上年度,而UTC的调整超过一年的界限。

Since you have told time.mktime() that your time is not DST, and its job is to convert all times into local time, and it is currently daylight savings time in your area, it adds an hour to make it daylight savings time. Hence the result.

因为您已经告诉time.mktime()您的时间不是DST,它的工作是将所有时间转换为本地时间,并且它现在是您所在地区的夏令时,它增加了一个小时来实现夏令时。因此结果。


While I don't have the post handy, I came across a method a couple of days ago to convert timezone-aware datetimes into naive ones in your local time. This might work much better for your application than what you are currently doing (uses the excellent pytz module):

虽然我手边没有这个帖子,但几天前我偶然发现了一种方法,可以将timezone-aware datetimes转换为本地时间的简单数据。这对于您的应用程序可能比您当前正在做的工作(使用优秀的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

Replace 'America/LosAngeles' with your own timezone string, which you can find somewhere in pytz.all_timezones.

用您自己的时区字符串替换“America/LosAngeles”,您可以在pytz.all_timezone中找到它。

#2


0  

datetime.fromtimestamp() is the correct method to get local time from POSIX timestamp. The issue in your question is that you convert an aware datetime object to POSIX timestamp using time.mktime() that is incorrect. Here's one of correct ways to do it:

fromtimestamp()是从POSIX时间戳获取本地时间的正确方法。您的问题是,您使用不正确的time.mktime()将一个aware datetime对象转换为POSIX时间戳。这里有一个正确的方法:

expiration_utc_ts = (d - datetime(1970, 1, 1, tzinfo=utc)).total_seconds()
local_dt = datetime.fromtimestamp(expiration_utc_ts)