【问题标题】:Converting timezone-aware date string to UTC and back in Python在 Python 中将时区感知日期字符串转换为 UTC 并返回
【发布时间】:2011-09-19 01:39:46
【问题描述】:

我正在将 National Weather Service 警报馈送解析到 Web 应用程序中。我想在警报达到到期时间时清除它们。我还想以本地时间格式显示它们所属地理区域的到期时间。

警报覆盖整个美国,因此我认为最好的方法是存储和比较 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

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

【问题讨论】:

标签: python datetime timezone


【解决方案1】:

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

一个简单的例子:

>>> 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() 之内。正如它在documentation 中所说:

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

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

如果日期时间实例 d 是幼稚的,这与 d.timetuple() 相同 除了 tm_isdst 被强制为 0 而不管 d.dst() 返回。 DST 在 UTC 时间内永远不会生效。

如果 d 知道,则通过减去 d 将 d 标准化为 UTC 时间 d.utcoffset(),一个 time.struct_time 的归一化时间是 回来。 tm_isdst 被强制为 0。注意结果的 tm_year 如果 d.year 是 MINYEAR 或 MAXYEAR,则成员可能是 MINYEAR-1 或 MAXYEAR+1 并且 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 的某个位置找到该字符串。

【讨论】:

  • 不正确。 mktime() 预计 本地时间 而 OP 通过 UTC 时间。如果本地时区不是 UTC,它将失败。不要使用 naive_datetime_object.utctimetuple() - 它不会将其转换为 UTC(OP 使用 aware_datetime.object.utctimetuple() 可以按预期工作)。如果aware_dt 不是UTC,您可能需要在.astimezone() 之后调用tz.normalize()。
【解决方案2】:

datetime.fromtimestamp() 是从 POSIX 时间戳获取本地时间的正确方法。您问题中的问题是您使用不正确的 time.mktime() 将感知日期时间对象转换为 POSIX 时间戳。这里是one of correct ways to do it:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-14
    • 1970-01-01
    • 1970-01-01
    • 2021-08-14
    • 2018-05-22
    • 1970-01-01
    • 2021-04-19
    相关资源
    最近更新 更多