Python strptime() 和时区?

Posted

技术标签:

【中文标题】Python strptime() 和时区?【英文标题】:Python strptime() and timezones? 【发布时间】:2011-03-19 08:06:54 【问题描述】:

我有一个来自 Blackberry IPD 备份的 CSV 转储文件,它是使用 IPDDump 创建的。 这里的日期/时间字符串看起来像这样 (其中EST 是澳大利亚时区):

Tue Jun 22 07:46:22 EST 2010

我需要能够在 Python 中解析这个日期。起初,我尝试使用 datettime 中的strptime() 函数。

>>> datetime.datetime.strptime('Tue Jun 22 12:10:20 2010 EST', '%a %b %d %H:%M:%S %Y %Z')

但是,由于某种原因,返回的 datetime 对象似乎没有任何 tzinfo 与之关联。

我确实在this page 上读到过,显然datetime.strptime 默默地丢弃了tzinfo,但是,我检查了文档,但我找不到任何与here 相关的内容。

我已经能够使用第三方 Python 库 dateutil 解析日期,但是我仍然很好奇我是如何错误地使用内置的 strptime() 的?有什么方法可以让strptime() 与时区完美搭配?

【问题讨论】:

你不能...将所有日期转换为 GMT 吗? @Robus:嗯,我希望这样做 - 但我假设 strftime/datetime 可以以某种方式做到这一点?无论哪种方式,我都需要存储/解析日期时间在 EST 时区或它们发生在我身上的任何时区这一事实。该脚本需要能够解析带有时区信息的通用日期时间(例如,ETC 可以是任何其他时区)。 EST 也是美国时区的缩写。 (类似地,BST 既是英国时区的缩写,也是巴西时区的缩写。)这样的缩写本质上是模棱两可的。改为使用相对于 UTC/GMT 的偏移量。 (如果您需要支持缩写,则需要使映射语言环境依赖,这是一个混乱的老鼠洞。) EST timezone abbreviation is ambiguous。另见:Parsing date/time string with timezone abbreviated name in Python? 【参考方案1】:

datetime module documentation 说:

返回一个date_string对应的datetime,根据格式解析。这相当于datetime(*(time.strptime(date_string, format)[0:6]))

看到[0:6]了吗?这让你(year, month, day, hour, minute, second)。没有其他的。没有提到时区。

有趣的是,[Win XP SP2, Python 2.6, 2.7] 将您的示例传递给 time.strptime 不起作用,但如果您去掉“%Z”和“EST”,它确实有效。也可以使用“UTC”或“GMT”而不是“EST”。 “PST”和“MEZ”不起作用。令人费解。

值得注意的是,这已从 3.2 版开始更新,并且相同的文档现在还说明了以下内容:

当 %z 指令提供给 strptime() 方法时,将生成一个可感知的日期时间对象。结果的 tzinfo 将设置为时区实例。

请注意,这不适用于 %Z,因此大小写很重要。请参阅以下示例:

In [1]: from datetime import datetime

In [2]: start_time = datetime.strptime('2018-04-18-17-04-30-AEST','%Y-%m-%d-%H-%M-%S-%Z')

In [3]: print("TZ NAME: tz".format(tz=start_time.tzname()))
TZ NAME: None

In [4]: start_time = datetime.strptime('2018-04-18-17-04-30-+1000','%Y-%m-%d-%H-%M-%S-%z')

In [5]: print("TZ NAME: tz".format(tz=start_time.tzname()))
TZ NAME: UTC+10:00

【讨论】:

相关 Python 错误:%Z in strptime doesn't match EST and others【参考方案2】:

我建议使用python-dateutil。到目前为止,它的解析器已经能够解析我向它抛出的所有日期格式。

>>> from dateutil import parser
>>> parser.parse("Tue Jun 22 07:46:22 EST 2010")
datetime.datetime(2010, 6, 22, 7, 46, 22, tzinfo=tzlocal())
>>> parser.parse("Fri, 11 Nov 2011 03:18:09 -0400")
datetime.datetime(2011, 11, 11, 3, 18, 9, tzinfo=tzoffset(None, -14400))
>>> parser.parse("Sun")
datetime.datetime(2011, 12, 18, 0, 0)
>>> parser.parse("10-11-08")
datetime.datetime(2008, 10, 11, 0, 0)

等等。不用处理 strptime() 格式的废话……只需给它一个日期,它就会做正确的事。

更新:糟糕。我错过了您在原始问题中提到您使用dateutil,对此感到抱歉。但我希望这个答案对其他在遇到日期解析问题并看到该模块的实用程序时偶然发现这个问题的人仍然有用。

【讨论】:

鉴于有这么多人倾向于使用 python-dateutil,我想指出该库的一个限制。 >>> parser.parse("Thu, 25 Sep 2003 10:49:41,123 -0300") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/Users/wanghq/awscli/lib/python2.7/site-packages/dateutil/parser.py", line 748, in parse return DEFAULTPARSER.parse(timestr, **kwargs) File "/Users/wanghq/awscli/lib/python2.7/site-packages/dateutil/parser.py", line 310, in parse res, skipped_tokens = self._parse(timestr, **kwargs) TypeError: 'NoneType' object is not iterable @wanghq 你需要用句号替换最后一个逗号。然后parser.parse("Thu, 25 Sep 2003 10:49:41.123 -0300") returns: datetime.datetime(2003, 9, 25, 10, 49, 41, 123000, tzinfo=tzoffset(None, -10800)) @flyingfoxlee,是的,我明白这一点。我只是想告诉人们 python-dateutil 的局限性。它做神奇的事情,但有时无法做到这一点。所以“只要给它一个日期,它就会做正确的事。”不是 100% 正确的。 dateutil.parser.parse("10-27-2016 09:06 AM PDT") 返回:datetime.datetime(2016, 10, 27, 9, 6) 无法确定时区... 这取决于一个人的目标。 dateutil parser 使用起来可能很简单,但strptime() 更快。此外,它的格式很容易学习。【参考方案3】:

您的时间字符串类似于rfc 2822 (date format in email, http headers) 中的时间格式。您可以仅使用 stdlib 解析它:

>>> from email.utils import parsedate_tz
>>> parsedate_tz('Tue Jun 22 07:46:22 EST 2010')
(2010, 6, 22, 7, 46, 22, 0, 1, -1, -18000)

查看为各种 Python 版本生成时区感知日期时间对象的解决方案:parsing date with timezone from an email。

在这种格式中,EST is semantically equivalent to -0500。不过,一般来说,a timezone abbreviation is not enough, to identify a timezone uniquely.

【讨论】:

【参考方案4】:

遇到了这个确切的问题。

我最终做了什么:

# starting with date string
sdt = "20190901"
std_format = '%Y%m%d'

# create naive datetime object
from datetime import datetime
dt = datetime.strptime(sdt, sdt_format)

# extract the relevant date time items
dt_formatters = ['%Y','%m','%d']
dt_vals = tuple(map(lambda formatter: int(datetime.strftime(dt,formatter)), dt_formatters))

# set timezone
import pendulum
tz = pendulum.timezone('utc')

dt_tz = datetime(*dt_vals,tzinfo=tz)

【讨论】:

【参考方案5】:

由于strptime 返回一个具有tzinfo 属性的日期时间对象,我们可以简单地将其替换为所需的时区。

>>> import datetime

>>> date_time_str = '2018-06-29 08:15:27.243860'
>>> date_time_obj = datetime.datetime.strptime(date_time_str, '%Y-%m-%d %H:%M:%S.%f').replace(tzinfo=datetime.timezone.utc)
>>> date_time_obj.tzname()
'UTC'

【讨论】:

并非所有时间戳字符串都是基于 UTC 的(例如,问题中的那个)。 这在很多时区都无法正常工作。例如:为Asia/Kolkata 执行此操作会得到tzinfo=<DstTzInfo 'Asia/Kolkata' LMT+5:53:00 STD 的偏移量 @iudeen 您所描述的是错误本地化的pytz 时区对象的结果。使用 pytz,您必须进行本地化,不要使用替换!但是,对于 Python 3.9,您应该改用 zoneinfo,这完全避免了这个陷阱。安全到replace 那里。【参考方案6】:

作为Joe Shaw's answer 的扩展,dateutil 的解析器提供了提供从 IANA 时区名称派生的时区名称缩写到时区对象的映射的可能性。

import dateutil

tzdict = 'EST': dateutil.tz.gettz('America/New_York'),
          'EDT': dateutil.tz.gettz('America/New_York')

dt = dateutil.parser.parse("Tue Jun 22 07:46:22 EST 2010", tzinfos=tzdict)

print(dt)
# 2010-06-22 07:46:22-04:00
      
print(repr(dt))
# datetime.datetime(2010, 6, 22, 7, 46, 22, tzinfo=tzfile('US/Eastern'))

相对于固定 UTC 偏移的优势在于,如果您使用获得的 datetime 对象执行任何 timedelta 算术,则将考虑时区规则(例如 DST 转换)。

【讨论】:

以上是关于Python strptime() 和时区?的主要内容,如果未能解决你的问题,请参考以下文章

带时区的Strptime

Python3 将本地时间转换成指定时区时间

将时区缩写解析为 UTC [重复]

48 python判断时间是否落在两个时区之间(只比较时刻不比较日期)

Python之字符串转换为日期结合时区的日期操作

时区不正确