我需要将RFC 3339字符串(如“2008-09-03T20:56:55.450686Z”)解析为Python的datetime类型。
我在Python标准库中找到了strptime,但它不是很方便。
最好的方法是什么?
我需要将RFC 3339字符串(如“2008-09-03T20:56:55.450686Z”)解析为Python的datetime类型。
我在Python标准库中找到了strptime,但它不是很方便。
最好的方法是什么?
当前回答
因为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
其他回答
尝试iso8601模块;它正是这样做的。
python.org wiki上的WorkingWithTime页面上还提到了其他几个选项。
import re
import datetime
s = "2008-09-03T20:56:35.450686Z"
d = datetime.datetime(*map(int, re.split(r'[^\d]', s)[:-1]))
最初我尝试了:
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.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)
从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时区偏移。