我需要将RFC 3339字符串(如“2008-09-03T20:56:55.450686Z”)解析为Python的datetime类型。

我在Python标准库中找到了strptime,但它不是很方便。

最好的方法是什么?


当前回答

datetime.fromisoformat()在Python 3.11中得到了改进,可以解析大多数ISO 8601格式

datetime.fromisoformat()现在可以用于解析大多数ISO 8601格式,只有支持小数小时和分的格式除外。以前,此方法只支持datetime.isoformat()发出的格式。

>>> from datetime import datetime
>>> datetime.fromisoformat('2011-11-04T00:05:23Z')
datetime.datetime(2011, 11, 4, 0, 5, 23, tzinfo=datetime.timezone.utc)
>>> datetime.fromisoformat('20111104T000523')
datetime.datetime(2011, 11, 4, 0, 5, 23)
>>> datetime.fromisoformat('2011-W01-2T00:05:23.283')
datetime.datetime(2011, 1, 4, 0, 5, 23, 283000)

其他回答

你得到的确切错误是什么?它像下面这样吗?

>>> datetime.datetime.strptime("2008-08-12T12:20:30.656234Z", "%Y-%m-%dT%H:%M:%S.Z")
ValueError: time data did not match format:  data=2008-08-12T12:20:30.656234Z  fmt=%Y-%m-%dT%H:%M:%S.Z

如果是,您可以将输入字符串拆分为“.”,然后将微秒添加到获得的日期时间中。

试试看:

>>> def gt(dt_str):
        dt, _, us= dt_str.partition(".")
        dt= datetime.datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S")
        us= int(us.rstrip("Z"), 10)
        return dt + datetime.timedelta(microseconds=us)

>>> gt("2008-08-12T12:20:30.656234Z")
datetime.datetime(2008, 8, 12, 12, 20, 30, 656234)

我已经为ISO 8601标准编写了一个解析器,并将其放在GitHub上:https://github.com/boxed/iso8601.此实现支持规范中的所有内容,但持续时间、间隔、周期性间隔和Python datetime模块支持的日期范围之外的日期除外。

包括测试!:P

从Python 3.7开始,您基本上可以使用datetime.datetime.strptime解析RFC 3339日期时间,如下所示:

from datetime import datetime

def parse_rfc3339(datetime_str: str) -> datetime:
    try:
        return datetime.strptime(datetime_str, "%Y-%m-%dT%H:%M:%S.%f%z")
    except ValueError:
        # Perhaps the datetime has a whole number of seconds with no decimal
        # point. In that case, this will work:
        return datetime.strptime(datetime_str, "%Y-%m-%dT%H:%M:%S%z")

这有点尴尬,因为我们需要尝试两种不同的格式字符串,以便同时支持小数秒的日期时间(如2022-01-01T12:12:12.123Z)和没有小数秒的(如2021-01-01T12:12Z),这两种格式在RFC 3339下都是有效的。但只要我们做一点逻辑,这就行得通。

此方法需要注意的一些注意事项:

它在技术上并不完全支持RFC 3339,因为RFC 3339允许您使用空格而不是t来分隔日期和时间,尽管RFC 3339声称是ISO 8601的概要文件,但ISO 8601不允许这样做。如果您想支持RFC 3339的这种愚蠢的怪癖,可以在函数的开头添加datetime_str=datetime_str.replace(“”,“T”)。我上面的实现比严格的RFC 3339解析器应该更宽松,因为它将允许时区偏移,如+0500而不带冒号,而RFC 3339不支持。如果您不仅想解析known-to-be-RFC-339日期时间,而且还想严格验证您获得的日期时间是否为RFC 3339,请使用另一种方法或添加您自己的逻辑来验证时区偏移格式。这个函数肯定不支持所有的ISO 8601,它包括比RFC 3339更广泛的格式。(例如,2009-W01-1是有效的ISO 8601日期。)它在Python 3.6或更早版本中不起作用,因为在那些旧版本中,%z说明符只匹配+0500或-0430或+0000等时区偏移,而不是+05:00或-04:30或z等RFC 3339时区偏移。

这适用于Python 3.2以上版本的stdlib(假设所有时间戳都是UTC):

from datetime import datetime, timezone, timedelta
datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ").replace(
    tzinfo=timezone(timedelta(0)))

例如

>>> datetime.utcnow().replace(tzinfo=timezone(timedelta(0)))
... datetime.datetime(2015, 3, 11, 6, 2, 47, 879129, tzinfo=datetime.timezone.utc)

Python>=3.11

fromsoformat现在直接解析Z:

from datetime import datetime

s = "2008-09-03T20:56:35.450686Z"

datetime.fromisoformat(s)
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=datetime.timezone.utc)

Python 3.7到3.10

一个注释中的简单选项:将“Z”替换为“+00:00”-并使用fromsoformat:

from datetime import datetime

s = "2008-09-03T20:56:35.450686Z"

datetime.fromisoformat(s.replace('Z', '+00:00'))
# datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=datetime.timezone.utc)

为什么更喜欢来自同一格式?

虽然strptime的%z可以将“z”字符解析为UTC,但fromsoformat的速度要快~x40(另请参阅:更快的strptime):

%timeit datetime.fromisoformat(s.replace('Z', '+00:00'))
388 ns ± 48.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit dateutil.parser.isoparse(s)
11 µs ± 1.05 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit datetime.strptime(s, '%Y-%m-%dT%H:%M:%S.%f%z')
15.8 µs ± 1.32 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit dateutil.parser.parse(s)
87.8 µs ± 8.54 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

(Windows 10上的Python 3.9.12 x64)