如何parsingISO 8601格式的date?
我需要将RFC 3339stringparsing为Python的datetime
types,如"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议的代码将不起作用。 事实上,它不能工作,因为使用strptime
parsingRFC 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
唉, strptime
和strftime
使用的格式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-0400
或2008-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。