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

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

最好的方法是什么?


当前回答

如果不想使用dateutil,可以尝试使用以下函数:

def from_utc(utcTime,fmt="%Y-%m-%dT%H:%M:%S.%fZ"):
    """
    Convert UTC time string to time.struct_time
    """
    # change datetime.datetime to time, return time.struct_time type
    return datetime.datetime.strptime(utcTime, fmt)

测试:

from_utc("2007-03-04T21:08:12.123Z")

结果:

datetime.datetime(2007, 3, 4, 21, 8, 12, 123000)

其他回答

def parseISO8601DateTime(datetimeStr):
    import time
    from datetime import datetime, timedelta

    def log_date_string(when):
        gmt = time.gmtime(when)
        if time.daylight and gmt[8]:
            tz = time.altzone
        else:
            tz = time.timezone
        if tz > 0:
            neg = 1
        else:
            neg = 0
            tz = -tz
        h, rem = divmod(tz, 3600)
        m, rem = divmod(rem, 60)
        if neg:
            offset = '-%02d%02d' % (h, m)
        else:
            offset = '+%02d%02d' % (h, m)

        return time.strftime('%d/%b/%Y:%H:%M:%S ', gmt) + offset

    dt = datetime.strptime(datetimeStr, '%Y-%m-%dT%H:%M:%S.%fZ')
    timestamp = dt.timestamp()
    return dt + timedelta(hours=dt.hour-time.gmtime(timestamp).tm_hour)

注意,如果字符串不以Z结尾,我们可以使用%Z进行解析。

这适用于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.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所指出的,我添加了一个尝试。。除了块以说明丢失的小数秒。

Django的parse_datetime()函数支持UTC偏移的日期:

parse_datetime('2016-08-09T15:12:03.65478Z') =
datetime.datetime(2016, 8, 9, 15, 12, 3, 654780, tzinfo=<UTC>)

因此,它可以用于解析整个项目中字段中的ISO 8601日期:

from django.utils import formats
from django.forms.fields import DateTimeField
from django.utils.dateparse import parse_datetime

class DateTimeFieldFixed(DateTimeField):
    def strptime(self, value, format):
        if format == 'iso-8601':
            return parse_datetime(value)
        return super().strptime(value, format)

DateTimeField.strptime = DateTimeFieldFixed.strptime
formats.ISO_INPUT_FORMATS['DATETIME_INPUT_FORMATS'].insert(0, 'iso-8601')

因为ISO 8601允许出现许多可选冒号和破折号的变体,基本上是CCYY MM DDThh:MM:ss[Z|(+|-)hh:MM]。如果你想使用strptime,你需要先去掉这些变体。目标是生成utc-datetime对象。如果您只需要一个适用于UTC的Z后缀的基本案例,如2016-06-29T19:36:29.3453Z:

datetime.datetime.strptime(timestamp.translate(None, ':-'), "%Y%m%dT%H%M%S.%fZ")

如果您想处理时区偏移,如2016-06-29T19:36:29.3453-0400或2008-09-03T20:56:55.450686+05:00,请使用以下命令。这些将把所有变体转换成没有变量分隔符的东西,如20080903T205635.450686+0500,使其更一致/更容易解析。

import re
# this regex removes all colons and all 
# dashes EXCEPT for the dash indicating + or - utc offset for the timezone
conformed_timestamp = re.sub(r"[:]|([-](?!((\d{2}[:]\d{2})|(\d{4}))$))", '', timestamp)
datetime.datetime.strptime(conformed_timestamp, "%Y%m%dT%H%M%S.%f%z" )

如果您的系统不支持%z strptime指令(您看到类似ValueError的内容:“z”是格式为“%Y%m%dT%H%m%S.%f%z”的错误指令),则需要手动从z(UTC)偏移时间。注意%z在python版本<3的系统上可能不起作用,因为它依赖于c库支持,而c库支持随系统/python构建类型(例如Jython、Cython等)而变化。

import re
import datetime

# this regex removes all colons and all 
# dashes EXCEPT for the dash indicating + or - utc offset for the timezone
conformed_timestamp = re.sub(r"[:]|([-](?!((\d{2}[:]\d{2})|(\d{4}))$))", '', timestamp)

# split on the offset to remove it. use a capture group to keep the delimiter
split_timestamp = re.split(r"[+|-]",conformed_timestamp)
main_timestamp = split_timestamp[0]
if len(split_timestamp) == 3:
    sign = split_timestamp[1]
    offset = split_timestamp[2]
else:
    sign = None
    offset = None

# generate the datetime object without the offset at UTC time
output_datetime = datetime.datetime.strptime(main_timestamp +"Z", "%Y%m%dT%H%M%S.%fZ" )
if offset:
    # create timedelta based on offset
    offset_delta = datetime.timedelta(hours=int(sign+offset[:-2]), minutes=int(sign+offset[-2:]))
    # offset datetime with timedelta
    output_datetime = output_datetime + offset_delta