将时区感知日期字符串转换为UTC并返回Python

Sco*_*ott 9 python timezone datetime

我正在将国家气象服务警报提要解析为Web应用程序.我想在达到到期时清除警报.我还想以他们所属的地理区域的本地时间格式显示到期时间.

警报覆盖整个美国,所以我认为最好的方法是存储和比较UTC时间戳的时间.到期时间作为字符串到达​​Feed中,如下所示: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))
Run Code Online (Sandbox Code Playgroud)

我还能够在几小时内捕获UTC偏移量:

>>> offset_hours = (d.utcoffset().days * 86400 + d.utcoffset().seconds) / 3600
>>> offset_hours
-4
Run Code Online (Sandbox Code Playgroud)

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

>>> import time
>>> expiration_utc_ts = time.mktime(d.utctimetuple())
>>> expiration_utc_ts
1316393520.0
Run Code Online (Sandbox Code Playgroud)

在这一点上,我感觉非常好,我能够将原始字符串转换为表示UTC中的到期时间的时间戳.我能够将当前时间作为UTC时间戳与到期时间进行比较,并确定是否需要清除它:

>>> now_utc_ts = time.mktime(time.gmtime())
>>> now_utc_ts
1316398744.0
>>> now_utc_ts >= expiration_tc_ts
True
Run Code Online (Sandbox Code Playgroud)

我遇到的困难是尝试将存储的UTC时间戳转换回原始的本地化格式.我有从原始转换存储的偏移小时数和我解析的字符串来存储时区标签:

>>> print offset_hours
-4
>>> print timezone
EDT
Run Code Online (Sandbox Code Playgroud)

我想将UTC时间戳转换回本地格式化的时间,但将其转换回a时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
Run Code Online (Sandbox Code Playgroud)

看起来它已经过了一个小时.我不确定错误的引入位置?我把另一个测试放在一起,得到了类似的结果

>>> # 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
Run Code Online (Sandbox Code Playgroud)

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

Dav*_*ave 3

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

\n\n

一个简单的例子:

\n\n
>>> time_tuple = datetime(2011,3,13,2,1,1).utctimetuple()\ntime.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)\n>>> datetime.fromtimestamp(time.mktime(time_tuple))\ndatetime.datetime(2011, 3, 13, 3, 1, 1)\n
Run Code Online (Sandbox Code Playgroud)\n\n

我相当肯定问题出在内部time.mktime()。正如其文档中所述:

\n\n
\n

这是 localtime() 的反函数。它的参数是 struct_time 或完整的 9 元组(因为需要 dst 标志;如果未知,则使用 -1 作为 dst 标志),它表示本地时间而不是 UTC 时间。它返回一个浮点数,以与 time() 兼容。如果输入值无法表示为有效时间,则会引发 OverflowError 或 ValueError(这取决于 Python 还是底层 C 库是否捕获了无效值)。它可以生成时间的最早日期取决于平台。

\n
\n\n

当您将时间元组传递给 时time.mktime(),它需要一个指示时间是否为夏令时的标志。正如您在上面所看到的,utctimetuple()返回一个带有标记的元组,正如它在其文档0中所说的那样:

\n\n
\n

如果日期时间实例 d 是朴素的,则与 d.timetuple()\n 相同,只是 tm_isdst 被强制为 0,无论 d.dst()\n 返回什么。DST 对于 UTC 时间永远不会生效。

\n\n

如果 d 知道,则通过减去 d.utcoffset() 将 d 标准化为 UTC 时间,并返回标准化时间的 time.struct_time。tm_isdst 强制为 0。请注意,如果 d.year 为 MINYEAR 或 MAXYEAR\n 并且 UTC 调整超出年份边界,则结果\xe2\x80\x99s tm_year\n 成员可能为 MINYEAR-1 或 MAXYEAR+1。

\n
\n\n

由于您已告知time.mktime()您的时间不是夏令时,并且它的工作是将所有时间转换为当地时间,并且当前您所在地区是夏令时,因此它会增加一个小时以使其成为夏令时。于是就有了这样的结果。

\n\n
\n\n

虽然我手边没有这篇文章,但几天前我遇到了一种方法,可以将时区感知的日期时间转换为本地时间的天真日期时间。这可能比您当前所做的更适合您的应用程序(使用优秀的pytz模块):

\n\n
import pytz\ndef convert_to_local_time(dt_aware):\n    tz = pytz.timezone(\'America/Los_Angeles\') # Replace this with your time zone string\n    dt_my_tz = dt_aware.astimezone(tz)\n    dt_naive = dt_my_tz.replace(tzinfo=None)\n    return dt_naive\n
Run Code Online (Sandbox Code Playgroud)\n\n

将 \'America/LosAngeles\' 替换为您自己的时区字符串,您可以在pytz.all_timezones.

\n