我需要将"2008-09-03T20:56:35.450686Z"
等datetime
字符串解析为Python的{{1}}类型。
我在Python标准库中找到了RFC 3339,但这不太方便。
这样做的最佳方式是什么?
答案 0 :(得分:372)
python-dateutil 包不仅可以解析RFC 3339日期时间字符串,例如问题中的字符串,还可以解析不符合RFC 3339的其他ISO 8601日期和时间字符串(例如没有UTC偏移的那些,或仅代表日期的那些)。
>>> import dateutil.parser
>>> dateutil.parser.parse('2008-09-03T20:56:35.450686Z') # RFC 3339 format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())
>>> dateutil.parser.parse('2008-09-03T20:56:35.450686') # ISO 8601 extended format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
>>> dateutil.parser.parse('20080903T205635.450686') # ISO 8601 basic format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
>>> dateutil.parser.parse('20080903') # ISO 8601 basic format, date only
datetime.datetime(2008, 9, 3, 0, 0)
警告dateutil.parser
故意是hacky:它试图猜测格式并在不明确的情况下做出不可避免的假设(仅可手工定制)。因此,如果您需要解析未知格式的输入并且可以容忍偶尔的误读,那么只能使用它。 (感谢ivan_pozdeev)
Pypi名称为python-dateutil
,而不是dateutil
(感谢code3monk3y):
pip install python-dateutil
如果您使用的是Python 3.7,请查看关于datetime.datetime.fromisoformat
的{{3}}。
答案 1 :(得分:143)
注意在Python 2.6+和Py3K中,%f字符捕获微秒。
>>> datetime.datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")
请参阅问题here
答案 2 :(得分:136)
Several answers here suggest使用datetime.datetime.strptime
解析带有时区的RFC 3339或ISO 8601日期时间,如问题所示:
2008-09-03T20:56:35.450686Z
这是一个坏主意。
假设您要支持完整的RFC 3339格式,包括支持除零以外的UTC偏移,那么这些答案建议的代码不起作用。实际上,无法工作,因为使用strptime
解析RFC 3339语法是不可能的。 Python的datetime模块使用的格式字符串无法描述RFC 3339语法。
问题是UTC偏移。 RFC 3339 Internet Date/Time Format要求每个日期时间都包含UTC偏移量,并且这些偏移量可以是Z
(“祖鲁时间”的缩写)或+HH:MM
或-HH:MM
格式,例如+05:00
或-10:30
。
因此,这些都是有效的RFC 3339日期时间:
2008-09-03T20:56:35.450686Z
2008-09-03T20:56:35.450686+05:00
2008-09-03T20:56:35.450686-10:30
唉,strptime
和strftime
使用的格式字符串没有与RFC 3339格式的UTC偏移相对应的指令。可以在https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior找到它们支持的指令的完整列表,列表中包含的唯一UTC偏移指令是%z
:
%Z
UTC偏移量,格式为+ HHMM或-HHMM(如果对象天真,则为空字符串)。
示例:(空),+ 0000,-0400,+ 1030
这与RFC 3339偏移的格式不匹配,实际上如果我们尝试在格式字符串中使用%z
并解析RFC 3339日期,我们将失败:
>>> from datetime import datetime
>>> datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%f%z")
Traceback (most recent call last):
File "", line 1, in
File "/usr/lib/python3.4/_strptime.py", line 500, in _strptime_datetime
tt, fraction = _strptime(data_string, format)
File "/usr/lib/python3.4/_strptime.py", line 337, in _strptime
(data_string, format))
ValueError: time data '2008-09-03T20:56:35.450686Z' does not match format '%Y-%m-%dT%H:%M:%S.%f%z'
>>> datetime.strptime("2008-09-03T20:56:35.450686+05:00", "%Y-%m-%dT%H:%M:%S.%f%z")
Traceback (most recent call last):
File "", line 1, in
File "/usr/lib/python3.4/_strptime.py", line 500, in _strptime_datetime
tt, fraction = _strptime(data_string, format)
File "/usr/lib/python3.4/_strptime.py", line 337, in _strptime
(data_string, format))
ValueError: time data '2008-09-03T20:56:35.450686+05:00' does not match format '%Y-%m-%dT%H:%M:%S.%f%z'
(实际上,上面就是你在Python 3中看到的内容。在Python 2中,我们将失败的原因更简单,即strptime
does not implement the %z
directive at all in Python 2。)
此处推荐strptime
的多个答案都通过在其格式字符串中包含文字Z
来解决此问题,该字符串与问题提供者的示例日期时间字符串中的Z
相匹配(以及丢弃它,生成一个没有时区的datetime
对象:
>>> datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
由于这会丢弃原始日期时间字符串中包含的时区信息,因此我们是否应该将此结果视为正确是值得怀疑的。但更重要的是,因为这种方法涉及将特定的UTC偏移硬编码到格式字符串中,所以它会在尝试使用不同的UTC偏移量解析任何RFC 3339日期时间时阻塞:
>>> datetime.strptime("2008-09-03T20:56:35.450686+05:00", "%Y-%m-%dT%H:%M:%S.%fZ")
Traceback (most recent call last):
File "", line 1, in
File "/usr/lib/python3.4/_strptime.py", line 500, in _strptime_datetime
tt, fraction = _strptime(data_string, format)
File "/usr/lib/python3.4/_strptime.py", line 337, in _strptime
(data_string, format))
ValueError: time data '2008-09-03T20:56:35.450686+05:00' does not match format '%Y-%m-%dT%H:%M:%S.%fZ'
除非你某些,你只需要在Zulu时间支持RFC 3339日期时间,而不是其他时区偏移量,不要使用strptime
。请使用此处答案中描述的许多其他方法之一。
答案 3 :(得分:81)
datetime
标准库引入了一个反转datetime.isoformat()
。
classmethod
datetime.fromisoformat(date_string)
:以其中一种格式返回与
datetime
对应的date_string
由date.isoformat()
和datetime.isoformat()
发出。具体来说,此函数支持格式的字符串:
YYYY-MM-DD[*HH[:MM[:SS[.mmm[mmm]]]][+HH:MM[:SS[.ffffff]]]]
其中
*
可以匹配任何单个字符。警告:这不支持解析任意ISO 8601字符串 - 它仅用作反向字符串 操作
datetime.isoformat()
。
使用示例:
from datetime import datetime
date = datetime.fromisoformat('2017-01-01T12:30:59.000000')
答案 4 :(得分:69)
尝试iso8601模块;它就是这样做的。
python.org wiki上的WorkingWithTime页面上提到了其他几个选项。
答案 5 :(得分:34)
import re,datetime s="2008-09-03T20:56:35.450686Z" d=datetime.datetime(*map(int, re.split('[^\d]', s)[:-1]))
答案 6 :(得分:28)
你得到的确切错误是什么?它是否像以下一样?
>>> 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)
答案 7 :(得分:19)
从Python 3.7开始,strptime支持UTC偏移量(source)中的冒号分隔符。所以你可以使用:
import datetime
datetime.datetime.strptime('2018-01-31T09:24:31.488670+00:00', '%Y-%m-%dT%H:%M:%S.%f%z')
答案 8 :(得分:19)
在这些日子里,Arrow也可以用作第三方解决方案:
>>> import arrow
>>> date = arrow.get("2008-09-03T20:56:35.450686Z")
>>> date.datetime
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())
答案 9 :(得分:12)
如果您不想使用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)
答案 10 :(得分:11)
只需使用python-dateutil
模块:
>>> import dateutil.parser as dp
>>> t = '1984-06-02T19:05:00.000Z'
>>> parsed_t = dp.parse(t)
>>> print(parsed_t)
datetime.datetime(1984, 6, 2, 19, 5, tzinfo=tzutc())
答案 11 :(得分:11)
如果您正在使用Django,它会提供dateparse module,它接受一系列类似于ISO格式的格式,包括时区。
如果您不使用Django并且不想使用此处提到的其他库,则可以将the Django source code for dateparse调整为您的项目。
答案 12 :(得分:10)
其中一项注释中的一个简单选项:将let dateString = "07September16 4:09 am"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "ddMMMyy h:mm a"
let dateObj = dateFormatter.date(from: dateString)
dateFormatter.dateFormat = "MMM dd yyyy"
print("Dateobj: \(dateFormatter.string(from: dateObj!))")
替换为'Z'
-并使用Python 3.7+的'+00:00'
:
fromisoformat
尽管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
字符解析为UTC,但 'Z'
的速度却快了x40 (另请参见:A faster strptime):< / p>
fromisoformat
(Windows 10上为Python 3.8.5 x64)
答案 13 :(得分:8)
我发现ciso8601是解析ISO 8601时间戳的最快方法。顾名思义,它是用C实现的。
import ciso8601
ciso8601.parse_datetime('2014-01-09T21:48:00.921000+05:30')
GitHub Repo README显示其与其他答案中列出的所有其他库相比,加速度> 10倍。
我的个人项目涉及大量的ISO 8601解析。很高兴能够只是拨打电话并快10倍。 :)
编辑:我已成为ciso8601的维护者。它现在比以前更快!
答案 14 :(得分:7)
我是iso8601 utils的作者。它可以在on GitHub或PyPI找到。以下是解析示例的方法:
>>> from iso8601utils import parsers
>>> parsers.datetime('2008-09-03T20:56:35.450686Z')
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
答案 15 :(得分:6)
我为ISO 8601标准编写了一个解析器并将其放在GitHub上:https://github.com/boxed/iso8601。此实现支持规范中的所有内容,但持续时间,间隔,周期性间隔以及Python日期时间模块支持的日期范围之外的日期除外。
包括测试! :P
答案 16 :(得分:6)
在不安装第三方模块的情况下,在所有支持的Python版本中将类似ISO 8601的日期字符串转换为UNIX时间戳或datetime.datetime
对象的一种简单方法是使用date parser of SQLite。
#!/usr/bin/env python
from __future__ import with_statement, division, print_function
import sqlite3
import datetime
testtimes = [
"2016-08-25T16:01:26.123456Z",
"2016-08-25T16:01:29",
]
db = sqlite3.connect(":memory:")
c = db.cursor()
for timestring in testtimes:
c.execute("SELECT strftime('%s', ?)", (timestring,))
converted = c.fetchone()[0]
print("%s is %s after epoch" % (timestring, converted))
dt = datetime.datetime.fromtimestamp(int(converted))
print("datetime is %s" % dt)
输出:
2016-08-25T16:01:26.123456Z is 1472140886 after epoch
datetime is 2016-08-25 12:01:26
2016-08-25T16:01:29 is 1472140889 after epoch
datetime is 2016-08-25 12:01:29
答案 17 :(得分:5)
这适用于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)
答案 18 :(得分:5)
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')
答案 19 :(得分:4)
因为ISO 8601允许存在多种可选冒号和破折号,基本上是CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]
。如果你想使用strptime,你需要先删除这些变化。
目标是生成一个utc datetime对象。
2016-06-29T19:36:29.3453Z
,则为<{1}}:
datetime.datetime.strptime(timestamp.translate(None, ':-'), "%Y%m%dT%H%M%S.%fZ")
<小时/> 如果您想处理
2016-06-29T19:36:29.3453-0400
或2008-09-03T20:56:35.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' is a bad directive in format '%Y%m%dT%H%M%S.%f%z'
之类的内容),那么您需要手动偏移Z
(UTC)的时间。注意%z
在python版本中可能不适用于您的系统&lt; 3因为它依赖于c库支持,它不同于system / 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
答案 20 :(得分:3)
另一种对ISO-8601使用专门的解析器的方法是使用dateutil解析器的isoparse函数:
from dateutil import parser
date = parser.isoparse("2008-09-03T20:56:35.450686+01:00")
print(date)
输出:
2008-09-03 20:56:35.450686+01:00
documentation for the standard Python function datetime.fromisoformat中也提到了此功能:
更全功能的ISO 8601解析器dateutil.parser.isoparse是 在第三方软件包dateutil中可用。
答案 21 :(得分:2)
如果解析无效的日期字符串,python-dateutil将抛出异常,因此您可能希望捕获异常。
from dateutil import parser
ds = '2012-60-31'
try:
dt = parser.parse(ds)
except ValueError, e:
print '"%s" is an invalid date' % ds
答案 22 :(得分:2)
如今,Maya: Datetimes for Humans™来自流行的“请求:HTTP for Humans™”程序包的作者:
>>> import maya
>>> str = '2008-09-03T20:56:35.450686Z'
>>> maya.MayaDT.from_rfc3339(str).datetime()
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=<UTC>)
答案 23 :(得分:2)
对于适用于2.X标准库的内容,请尝试:
calendar.timegm(time.strptime(date.split(".")[0]+"UTC", "%Y-%m-%dT%H:%M:%S%Z"))
calendar.timegm是time.mktime缺少的gm版本。
答案 24 :(得分:1)
感谢很棒Mark Amery's answer我设计的功能可以解释日期时间的所有可能的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))
答案 25 :(得分:1)
如果仍然使用 pandas
,我可以推荐 pandas
中的 Timestamp
。你可以在那里
ts_1 = pd.Timestamp('2020-02-18T04:27:58.000Z')
ts_2 = pd.Timestamp('2020-02-18T04:27:58.000')
Rant:令人难以置信的是,我们仍然需要担心 2021 年的日期字符串解析等问题。
答案 26 :(得分:0)
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
进行解析。
答案 27 :(得分:0)
最初我尝试过:
ROWID
但这在负时区上不起作用。但是,我在Python 3.7.3中工作得很好:
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)
一些测试,请注意输出仅以微秒为单位有所不同。在我的机器上达到6位精度,但YMMV:
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)