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

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

最好的方法是什么?


当前回答

尝试iso8601模块;它正是这样做的。

python.org wiki上的WorkingWithTime页面上还提到了其他几个选项。

其他回答

感谢Mark Amery的出色回答,我设计了一个函数来解释所有可能的ISO日期时间格式:

class FixedOffset(tzinfo):
    """Fixed offset in minutes: `time = utc_time + utc_offset`."""
    def __init__(self, offset):
        self.__offset = timedelta(minutes=offset)
        hours, minutes = divmod(offset, 60)
        #NOTE: the last part is to remind about deprecated POSIX GMT+h timezones
        #  that have the opposite sign in the name;
        #  the corresponding numeric value is not used e.g., no minutes
        self.__name = '<%+03d%02d>%+d' % (hours, minutes, -hours)
    def utcoffset(self, dt=None):
        return self.__offset
    def tzname(self, dt=None):
        return self.__name
    def dst(self, dt=None):
        return timedelta(0)
    def __repr__(self):
        return 'FixedOffset(%d)' % (self.utcoffset().total_seconds() / 60)
    def __getinitargs__(self):
        return (self.__offset.total_seconds()/60,)

def parse_isoformat_datetime(isodatetime):
    try:
        return datetime.strptime(isodatetime, '%Y-%m-%dT%H:%M:%S.%f')
    except ValueError:
        pass
    try:
        return datetime.strptime(isodatetime, '%Y-%m-%dT%H:%M:%S')
    except ValueError:
        pass
    pat = r'(.*?[+-]\d{2}):(\d{2})'
    temp = re.sub(pat, r'\1\2', isodatetime)
    naive_date_str = temp[:-5]
    offset_str = temp[-5:]
    naive_dt = datetime.strptime(naive_date_str, '%Y-%m-%dT%H:%M:%S.%f')
    offset = int(offset_str[-4:-2])*60 + int(offset_str[-2:])
    if offset_str[0] == "-":
        offset = -offset
    return naive_dt.replace(tzinfo=FixedOffset(offset))

我是iso8601utils的作者。它可以在GitHub或PyPI上找到。下面是如何解析示例:

>>> from iso8601utils import parsers
>>> parsers.datetime('2008-09-03T20:56:35.450686Z')
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)

我发现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的维护者。现在比以往任何时候都快!

最初我尝试了:

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)

自Python 3.7以来,datetime标准库有一个用于反转datetime.isoformat()的函数。

classmethod datetime.fromisoformat(date_string):以任何有效的ISO 8601格式返回与date_string对应的日期时间,但以下情况除外:时区偏移可能有小数秒。T分隔符可以由任何单个unicode字符替换。当前不支持顺序日期。不支持分数小时和分钟。示例:>>>从datetime导入datetime>>>日期时间。来自同一格式(“2011-11-04”)datetime.datetime(2011,11,4,0,0)>>>datetime.fromisoformat('20111104')datetime.datetime(2011,11,4,0,0)>>>日期时间。来自同一格式('2011-11-04T00:05:23')datetime.datetime(2011,11,4,0,5,23)>>>日期时间。来自同一格式('2011-11-04T00:05:23Z')datetime.datetime(2011,11,4,0,5,23,tzinfo=datetime.timezone.utc)>>>日期时间。来自同一格式('20111104T000523')datetime.datetime(2011,11,4,0,5,23)>>>datetime.fromisoformat('2011-W01-2T0:05:32.283')datetime.datetime(2011,1,4,0,5,23,283000)>>>日期时间。来自同一格式('2011-11-04 00:05:23.283')datetime.datetime(2011,11,4,0,5,23,283000)>>>日期时间。来自同一格式('2011-11-04 00:05:23.283+00:00')datetime.datetime(2011,11,4,0,5,23,283000,tzinfo=datetime.timezone.utc)>>>日期时间。来自同一格式('2011-11-04T00:05:23+04:00')datetime.datetime(2011,11,4,0,5,23,tzinfo=datetime.timezone(datetime.time增量(秒=1440)))3.7版新增。3.11版本中更改:以前,此方法只支持date.isoformat()或datetime.isoformat()发出的格式。

如果您还没有升级到Python 3.11,请务必阅读文档中的警告!