如何parsingISO 8601格式的date?

我需要将RFC 3339stringparsing为Python的datetimetypes,如"2008-09-03T20:56:35.450686Z"

我已经在Python标准库中find了strptime ,但是不是很方便。

什么是最好的方法来做到这一点?

python-dateutil包不仅可以parsing问题中的RFC 3339date时间string,还可以parsing不符合RFC 3339的其他ISO 8601date和时间string(例如没有UTC偏移量的string,或者代表只有一个date)。

 >>> 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:它试图猜测的格式,并作出不可避免的假设(只能手工定制)在不明确的情况下。 所以只有在需要parsing未知格式的input时才使用它,并且可以容忍偶然的误读。 (感谢ivan_pozdeev )

Pypi的名字是python-dateutil ,而不是dateutil (感谢code3monk3y ):

 pip install python-dateutil 

注意在Python 2.6+和Py3K中,%f字符捕获微秒。

 >>> datetime.datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ") 

在这里看到问题

这里 有几个 答案 build议使用datetime.datetime.strptime来parsing带有时区的RFC 3339或ISO 8601date时间,就像问题中展示的一样:

 2008-09-03T20:56:35.450686Z 

这是一个坏主意。

假设您要支持完整的RFC 3339格式,包括对UTC以外的零偏移的支持,那么这些答案build议的代码将不起作用。 事实上,它不能工作,因为使用strptimeparsingRFC 3339语法是不可能的。 Pythondate时间模块使用的格式string不能描述RFC 3339语法。

问题是UTC抵消。 RFC 3339互联网date/时间格式要求每个date时间都包括一个UTC偏移量,这些偏移量既可以是Z (Zulu时间的缩写),也可以是+HH:MM-HH:MM格式,如+05:00-10:30

因此,这些都是有效的RFC 3339date时间:

  • 2008-09-03T20:56:35.450686Z
  • 2008-09-03T20:56:35.450686+05:00
  • 2008-09-03T20:56:35.450686-10:30

唉, strptimestrftime使用的格式string没有与RFC 3339格式的UTC偏移相对应的指令。 他们支持的指令的完整列表可以在https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behaviorfind,唯一包含在列表中的UTC偏移指令是;%z

%Z

UTC偏移量,格式为+ HHMM或-HHMM(如果对象是天真的,则为空string)。

例如:(空),+0000,-0400,+1030

这与RFC 3339偏移量的格式不匹配,事实上,如果我们尝试在格式string中使用%z并parsingRFC 3339date,我们将失败:

 >>> 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在Python 2中根本没有实现%z指令 。)

在这里推荐strptime所有的解决scheme,通过在其格式string中包含一个字面值Z来匹配来自问题提交者的示例datetimestring的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) 

由于这丢弃了包含在原始date时间string中的时区信息,所以我们是否应该把这个结果看作是正确的,这是值得怀疑的。 但更重要的是,由于这种方法涉及到将特定的UTC偏移量硬编码到格式string中 ,它会在尝试使用不同的UTC偏移量parsing任何RFC 3339date时间时窒息:

 >>> 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' 

除非您确定只需要在祖鲁语时间支持RFC 3339date时间,而不需要使用其他时区偏移量的date时间,则不要使用strptime 。 使用其中一个在这里的答案描述的其他方法。

试试iso8601模块; 它确实如此。

在Python.org上的WorkingWithTime页面上提到了其他几个选项。

导入re,datetime
 S = “2008-09-03T20:56:35.450686Z”
 d = datetime.datetime(* map(int,re.split('[^ \ d]',s)[: -  1]))

你得到什么确切的错误? 是否如下所示:

 >>> datetime.datetime.strptime("2008-08-12T12:20:30.656234Z", "%Y-%m-%dT%H:%M:%SZ") ValueError: time data did not match format: data=2008-08-12T12:20:30.656234Z fmt=%Y-%m-%dT%H:%M:%SZ 

如果是的话,你可以将inputstring拆分为“。”,然后将微秒添加到你得到的date时间。

尝试这个:

 >>> 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) >>> 

没有人提到它呢。 在这些日子里, Arrow也可以作为第三方的解决scheme。

 >>> import arrow >>> date = arrow.get("2008-09-03T20:56:35.450686Z") >>> date.datetime datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc()) 

如果你不想使用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) 

testing:

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

结果:

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

如果你正在使用Django,它提供了dateparse模块 ,它接受一系列类似于ISO格式的格式,包括时区。

如果你没有使用Django,并且你不想使用这里提到的其他库中的一个,那么你可以调整Django的dateparse源代码到你的项目中。

比你们都做得简单得多。

如果你想获得自纪元以来的秒数,可以使用python-dateutil将其转换为date时间对象,然后使用strftime方法将其转换为秒。 像这样:

 >>> import dateutil.parser as dp >>> t = '1984-06-02T19:05:00.000Z' >>> parsed_t = dp.parse(t) >>> t_in_seconds = parsed_t.strftime('%s') >>> t_in_seconds '455047500' 

资源

注意:这会将给定的datetime时间转换为纪元时间。 但是,您可以使用strftime()函数将该datetime时间转换为任何格式。 这里parsed_t对象的types是datetime

我已经编写了ISO 8601标准的parsing器,并把它放在github上: https : //github.com/boxed/iso8601这个实现支持规范中的所有内容,除了支持date之外的持续时间,间隔和周期性间隔和datepythons datetime模块的范围。

包括testing! :P

我是iso8601utils的作者。 它可以在github或PyPI上find。 以下是你如何parsing你的例子:

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

希望这可以帮助!

Django的parse_datetime ()函数支持UTC偏移量的date:

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

所以它可以用于在整个项目中的字段中parsingiso-8601date:

 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') 

对于与2.X标准库一起工作的东西,请尝试:

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

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

由于RFC 3339允许存在许多可选冒号和破折号的变体,基本上是CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm] 。 如果你想使用strptime,你需要首先去掉这些变体。

目标是生成一个utcdate时间对象。


如果你只是想要一个基本的情况下工作的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-04002008-09-03T20:56:35.450686+05:00使用以下内容。 这些将所有的变化转换成没有可变的分隔符,如20080903T205635.450686+0500 ,使它更一致/更容易parsing。

 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版本<3的系统上工作,因为它依赖于系统/ python构buildtypes(即Jython,Cython等)不同的c库支持。

 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 

python-dateutil会在parsing无效的datestring的时候抛出一个exception,所以你可能想要捕捉exception。

 from dateutil import parser ds = '2012-60-31' try: dt = parser.parse(ds) except ValueError, e: print '"%s" is an invalid date' % ds 

这适用于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) 

感谢Mark Amery的回答,我devise了一个函数来说明所有可能的ISO格式的date时间:

 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 eg, 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)) 

在所有支持的Python版本中,将类似ISO 8601的datestring转换为UNIX时间戳或datetime.datetime对象而不安装第三方模块的直接方法是使用SQLite的dateparsing器 。

 #!/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 

我发现ciso8601是parsingISO 8601时间戳的最快方法。 顾名思义,它是在C中实现的

 import ciso8601 ciso8601.parse_datetime('2014-01-09T21:48:00.921000+05:30') 

GitHub Repo自述文件展示了> 10倍的加速比其他答案中列出的其他库。

我的个人项目涉及到很多ISO 8601parsing。 能够切换通话并且快10倍是很好的。 🙂

 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) 

请注意,如果string不是以Z结尾,我们可以使用%z来parsing。