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

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

最好的方法是什么?


当前回答

我发现ciso8601是解析ISO 8601时间戳的最快方法。

它还完全支持RFC 3339,以及一个用于严格解析RFC 3339时间戳的专用函数。

示例用法:

>>> import ciso8601
>>> ciso8601.parse_datetime('2014-01-09T21')
datetime.datetime(2014, 1, 9, 21, 0)
>>> ciso8601.parse_datetime('2014-01-09T21:48:00.921000+05:30')
datetime.datetime(2014, 1, 9, 21, 48, 0, 921000, tzinfo=datetime.timezone(datetime.timedelta(seconds=19800)))
>>> ciso8601.parse_rfc3339('2014-01-09T21:48:00.921000+05:30')
datetime.datetime(2014, 1, 9, 21, 48, 0, 921000, tzinfo=datetime.timezone(datetime.timedelta(seconds=19800)))

GitHub Repo README显示了它们相对于其他答案中列出的所有其他库的加速。

我的个人项目涉及大量ISO 8601解析。很高兴能够切换通话并加快速度。:)

编辑:我后来成为了ciso8601的维护者。现在比以往任何时候都快!

其他回答

python dateutil中的等参函数

python dateutil包具有dateutil.parser.isose,不仅可以解析RFC 3339日期时间字符串(如问题中的字符串),还可以解析其他不符合RFC 3339的ISO 8601日期和时间字符串(例如没有UTC偏移量的字符串,或仅表示日期的字符串)。

>>> import dateutil.parser
>>> dateutil.parser.isoparse('2008-09-03T20:56:35.450686Z') # RFC 3339 format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())
>>> dateutil.parser.isoparse('2008-09-03T20:56:35.450686') # ISO 8601 extended format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
>>> dateutil.parser.isoparse('20080903T205635.450686') # ISO 8601 basic format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
>>> dateutil.parser.isoparse('20080903') # ISO 8601 basic format, date only
datetime.datetime(2008, 9, 3, 0, 0)

python dateutil包还具有dateutil.parser.parse。与isose相比,它可能不那么严格,但这两个函数都非常宽容,都会尝试解释传入的字符串。如果要消除任何误读的可能性,需要使用比这两个函式更严格的函数。

与Python 3.7+内置datetime.datetime.fromisoformat的比较

dateutil.parser.isorse是一个完整的ISO-8601格式解析器,但在Python≤3.10中,fromsoformat故意不是。在Python 3.11中,fromsoformat支持有效ISO 8601中的几乎所有字符串。请参阅isoformat的文档以了解此警告。(参见此答案)。

最初我尝试了:

from operator import neg, pos
from time import strptime, mktime
from datetime import datetime, tzinfo, timedelta

class MyUTCOffsetTimezone(tzinfo):
    @staticmethod
    def with_offset(offset_no_signal, signal):  # type: (str, str) -> MyUTCOffsetTimezone
        return MyUTCOffsetTimezone((pos if signal == '+' else neg)(
            (datetime.strptime(offset_no_signal, '%H:%M') - datetime(1900, 1, 1))
          .total_seconds()))

    def __init__(self, offset, name=None):
        self.offset = timedelta(seconds=offset)
        self.name = name or self.__class__.__name__

    def utcoffset(self, dt):
        return self.offset

    def tzname(self, dt):
        return self.name

    def dst(self, dt):
        return timedelta(0)


def to_datetime_tz(dt):  # type: (str) -> datetime
    fmt = '%Y-%m-%dT%H:%M:%S.%f'
    if dt[-6] in frozenset(('+', '-')):
        dt, sign, offset = strptime(dt[:-6], fmt), dt[-6], dt[-5:]
        return datetime.fromtimestamp(mktime(dt),
                                      tz=MyUTCOffsetTimezone.with_offset(offset, sign))
    elif dt[-1] == 'Z':
        return datetime.strptime(dt, fmt + 'Z')
    return datetime.strptime(dt, fmt)

但这在负时区不起作用。然而,我在Python 3.7.3中工作得很好:

from datetime import datetime


def to_datetime_tz(dt):  # type: (str) -> datetime
    fmt = '%Y-%m-%dT%H:%M:%S.%f'
    if dt[-6] in frozenset(('+', '-')):
        return datetime.strptime(dt, fmt + '%z')
    elif dt[-1] == 'Z':
        return datetime.strptime(dt, fmt + 'Z')
    return datetime.strptime(dt, fmt)

一些测试注意到,输出只在微秒的精度上有所不同。我的机器精度达到6位数,但YMMV:

for dt_in, dt_out in (
        ('2019-03-11T08:00:00.000Z', '2019-03-11T08:00:00'),
        ('2019-03-11T08:00:00.000+11:00', '2019-03-11T08:00:00+11:00'),
        ('2019-03-11T08:00:00.000-11:00', '2019-03-11T08:00:00-11:00')
    ):
    isoformat = to_datetime_tz(dt_in).isoformat()
    assert isoformat == dt_out, '{} != {}'.format(isoformat, dt_out)

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

包括测试!:P

要获得与2.X标准库兼容的功能,请尝试:

calendar.timegm(time.strptime(date.split(".")[0]+"UTC", "%Y-%m-%dT%H:%M:%S%Z"))

calendar.timegm是time.mktime缺少的gm版本。

从Python 3.7开始,strptime支持UTC偏移中的冒号分隔符(源代码)。因此,您可以使用:

import datetime

def parse_date_string(date_string: str) -> datetime.datetime
    try:
       return datetime.datetime.strptime(date_string, '%Y-%m-%dT%H:%M:%S.%f%z')
    except ValueError:
       return datetime.datetime.strptime(date_string, '%Y-%m-%dT%H:%M:%S%z')

编辑:

正如Martijn所指出的,如果您使用isoformat()创建了datetime对象,那么可以简单地使用datetime.fromisoformat)。

编辑2:

正如Mark Amery所指出的,我添加了一个尝试。。除了块以说明丢失的小数秒。