如何解析ISO 8601格式的日期?


642

我需要将RFC 3339字符串解析"2008-09-03T20:56:35.450686Z"为Python的datetime类型。

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

做这个的最好方式是什么?




3
需要明确的是:ISO 8601是主要标准。RFC 3339是ISO 8601的一个自称“配置文件”,对ISO 8601规则进行了一些不明智的覆盖
罗勒·布尔克

3
不要错过下面用于反转isoformat()的python3.7 +解决方案
Brad M

2
此问题不应作为对链接帖子的欺骗而关闭。由于这个请求解析 ISO 8601时间字符串(Python 3.7之前版本本身不支持),而另一个则是使用过时的方法将datetime对象格式化为一个纪元字符串。
abccd

Answers:


462

蟒蛇-dateutil包可以解析不仅RFC 3339日期时间字符串像在的问题,还包括其他ISO 8601的日期和时间字符串不符合RFC 3339(如那些没有UTC偏移,或那些代表仅一个日期)。

>>> import dateutil.parser
>>> dateutil.parser.isoparse('2008-09-03T20:56:35.450686Z') # RFC 3339 format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())
>>> dateutil.parser.isoparse('2008-09-03T20:56:35.450686') # ISO 8601 extended format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
>>> dateutil.parser.isoparse('20080903T205635.450686') # ISO 8601 basic format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
>>> dateutil.parser.isoparse('20080903') # ISO 8601 basic format, date only
datetime.datetime(2008, 9, 3, 0, 0)

请注意,这dateutil.parser.isoparse可能比更严格的方法更严格dateutil.parser.parse,但是它们两者都是相当宽容的,并且会尝试解释您传入的字符串。如果要消除任何误读的可能性,则需要使用比这两种方法都更严格的方法功能。

Pypi名称是python-dateutil,不是dateutil(感谢code3monk3y):

pip install python-dateutil

如果您使用的是Python 3.7,请查看有关的答案datetime.datetime.fromisoformat


75
对于懒惰者,它是通过python-dateutilnot 安装的dateutil,因此:pip install python-dateutil
cod3monk3y 2014年

29
请注意,这dateutil.parser是故意的hacky:它会尝试猜测格式,并在模棱两可的情况下做出不可避免的假设(只能手动设置)。因此,仅在需要解析未知格式的输入并且可以容忍偶尔的误读时才使用它。
ivan_pozdeev 2015年

2
同意 例如,传递的“日期”为9999。这将返回与datetime(9999,当前月份,当前日期)相同的日期。我认为这不是有效日期。
timbo

1
@ivan_pozdeev您将推荐使用什么包进行非猜测式解析?
bgusach

2
:@ivan_pozdeev有一个更新读取ISO8601日期模块dateutil.readthedocs.io/en/stable/...
theEpsilon

196

Python 3.7+中的新功能


datetime标准库中引入了一个功能反转datetime.isoformat()

classmethod datetime.fromisoformat(date_string)

以和发出的格式之一返回datetime对应于的。date_stringdate.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')

6
那真是怪了。因为a datetime可能包含tzinfo,因此输出时区,但是datetime.fromisoformat()不解析tzinfo?似乎像个虫子..
亨迪·爱侣湾

20
不要错过文档中的注释,它不接受所有有效的ISO 8601字符串,仅接受由生成的字符串isoformat"2008-09-03T20:56:35.450686Z"由于结尾Z,它不接受问题中的示例,但它确实接受"2008-09-03T20:56:35.450686"
Flimm

26
为了正确支持Z输入脚本,可以使用进行修改date_string.replace("Z", "+00:00")
jox

7
请注意,在几秒钟内,它只能处理0、3或6个小数位。如果输入数据具有1、2、4、5、7或更多小数位,则解析将失败!
Felk

1
@JDOaktown本示例使用本机Python的datetime库,而不是dateutil的解析器。如果采用这种方法,如果小数位不为0、3或6,则实际上将失败。
abccd

174

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

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

在这里查看问题


4
注意-如果使用的是朴素的日期时间-我认为您根本没有TZ-Z可能不匹配任何内容。
Danny Staple

24
该答案(以当前的编辑形式)取决于将特定的UTC偏移量(即“ Z”,表示+00:00)硬编码为格式字符串。这是一个坏主意,因为它将无法解析具有不同UTC偏移量的任何日期时间并引发异常。请参阅我的答案,其中描述了strptime实际上如何解析RFC 3339 。
Mark Amery 2015年

1
在我的情况下,%f捕捉到的是微秒而不是Z,datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f') 所以就成功了
ashim888 '02

Py3K意味着Python 3000吗?!?
罗宾诺

2
@Robino IIRC,“巨蟒3000”是为了什么,现在被称为Python的3个古老的名字
扔掉账户

161

这里有几个答案 建议使用解析时区的RFC 3339或ISO 8601日期时间,就像问题中展示的那样: datetime.datetime.strptime

2008-09-03T20:56:35.450686Z

这是一个坏主意。

假设您要支持完整的RFC 3339格式,包括对非零的UTC偏移量的支持,那么这些答案所建议的代码将不起作用。事实上,它不能工作,因为解析RFC 3339语法使用strptime是不可能的。Python的datetime模块使用的格式字符串无法描述RFC 3339语法。

问题是UTC偏移量。在RFC 3339互联网日期/时间格式要求每个日期时间包括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

可惜的是,所使用的格式字符串通过strptimestrftime没有指令,对应于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%z在Python 2根本没有实现该指令。)

推荐使用以下strptime所有方法的多个答案都可以通过Z在其格式字符串中包含一个字面量来解决此问题,该字面量与Z问题质询者的示例datetime字符串中的匹配(并丢弃它,从而生成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)

由于这会丢弃原始datetime字符串中包含的时区信息,因此我们是否应该甚至将此结果都视为正确还值得怀疑。但更重要的是,由于此方法涉及将特定的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。请改用此处答案中描述的许多其他方法之一。


79
令人困惑的是,为什么strptime没有针对ISO格式时区信息的指令,以及为何无法对其进行解析。难以置信。
Csaba Toth 2015年

2
@CsabaToth完全同意-如果我有时间杀死,也许我会尝试将其添加到语言中。或者,如果您愿意的话,也可以这样做-与我不同,我看到您有一些C经验。
Mark Amery

1
@CsabaToth-为什么难以置信?对于大多数人来说,它足够好用,或者他们发现足够简单的解决方法。如果您需要该功能,则该功能为开源,可以添加它。或付钱给你做。为什么有人应该自愿安排自己的空闲时间来解决您的特定问题?让源与你同在。
Peter M.-代表莫妮卡

2
@PeterMasiar令人难以置信,因为通常人们会发现python中的东西已经被深思熟虑地实现了。我们对细节的关注使我们宠坏了,所以当我们偶然发现某种用“非Python语言”表达的语言时,我们会将玩具扔出婴儿车,就像我现在要这么做的那样。Whaaaaaaaaaa Whaa wahaaaaa :-(
Robino

2
strptime()Python 3.7中的版本现在支持此答案中描述为不可能的所有内容(时区偏移量中的'Z'文字和':')。不幸的是,还有一个极端的情况使RFC 3339从根本上与ISO 8601不兼容,即前者允许负的零时区偏移-00:00,而后者则不允许。
SergiyKolesnikov

75

尝试使用iso8601模块;它正是这样做的。

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


简单为 iso8601.parse_date("2008-09-03T20:56:35.450686Z")
Pakman

3
问题不是“我如何解析ISO 8601日期”,而是“我如何解析这种确切的日期格式”。
尼古拉斯·赖利 Nicholas Riley)2012年

3
@tiktak OP询问“我需要解析像X一样的字符串”,并且我尝试了两个库,对此我的答复是使用另一个库,因为iso8601仍然存在重要问题。我参与或缺乏参与这样一个项目与答案完全无关。
托比亚

2
请注意,iso8601的pip版本自2007年以来就没有更新,并且存在一些非常严重的错误。我建议您自己应用一些重要的补丁程序,或者找到已经完成的许多github分支之一github.com/keithhackbarth/pyiso8601-strict
keithhackbarth 2013年

6
iso8601(也称为pyiso8601)已于2014年2月更新。最新版本支持更广泛的ISO 8601字符串集。我在某些项目中一直使用良好的效果。
Dave Hein 2014年

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

73
我不同意,这实际上是不可读的,据我所知,即使提供了时区数据,祖鲁语(Z)也没有考虑到使此日期时间幼稚的Zulu(Z)。
umbrae 2011年

14
我觉得它很可读。实际上,这可能是在不安装其他软件包的情况下进行转换的最简单,最有效的方法。
Tobia 2012年

2
我想这等于d = datetime.datetime(* map(int,re.split('\ D',s)[:-1])))。

4
一个变化:datetime.datetime(*map(int, re.findall('\d+', s))
jfs

3
这样会产生没有时区的朴素的datetime对象,对吗?那么UTC位会在翻译中丢失吗?
w00t 2014年

32

您得到的确切错误是什么?像下面吗?

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

10
您不能仅去除.Z,因为这意味着时区,并且可以不同。我需要将日期转换为UTC时区。
亚历山大·阿捷缅科

普通的datetime对象没有时区的概念。如果您所有的时间都以“ Z”结尾,则您获得的所有日期时间均为UTC(祖鲁时间)。
tzot

如果时区不是"""Z",则必须以小时/分钟为单位的偏移量,可以直接将其添加到datetime对象中/从datetime对象中减去。您可以创建一个tzinfo子类来处理它,但是可能不建议这样做。
消除单一否定

8
此外,“%f”是微秒说明符,因此(时区未使用)strptime字符串看起来像:“%Y-%m-%dT%H:%M:%S.%f”。
quodlibetor 2012年

1
如果给定的日期时间字符串的UTC偏移量不是“ Z”,则将引发异常。它不支持整个RFC 3339格式,对于正确处理UTC偏移量的其他解决方案而言,它是次等的回答。
Mark Amery 2015年

24

从Python 3.7开始,strptime在UTC偏移量()中支持冒号分隔符。因此,您可以使用:

import datetime
datetime.datetime.strptime('2018-01-31T09:24:31.488670+00:00', '%Y-%m-%dT%H:%M:%S.%f%z')

编辑:

正如Martijn所指出的那样,如果您使用isoformat()创建了datetime对象,则只需使用datetime.fromisoformat()


4
但是在3.7中,您可以datetime.fromisoformat()自动处理类似输入的字符串:datetime.datetime.isoformat('2018-01-31T09:24:31.488670+00:00')
马丁·彼得斯

2
好点子。我同意,我建议使用datetime.fromisoformat()datetime.isoformat()
Andreas Profous


17

只需使用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())

文献资料


1
这不是@Flimms上面的答案吗?
leo

1
您在几秒钟内看到他在哪里解析?我通过尝试获取时间来找到这篇文章,所以我认为其他人也一样。
布莱尔23年

1
不是我系统上的UTC。相反,以秒为单位的输出是unix纪元时间,就好像日期在我的本地时区一样。
Elliot

1
这个答案是错误的,不应该被接受。可能整个问题应标记为stackoverflow.com/questions/11743019/…
三点

@tripleee实际上,我只是检查了代码,它似乎返回了正确的答案:(455051100epochconverter.com进行了检查),除非我丢失了什么?
布莱格23年

13

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

5
此答案取决于将特定的UTC偏移量(即“ Z”,表示+00:00)硬编码为传递给的格式字符串strptime。这是一个坏主意,因为它将无法解析具有不同UTC偏移量的任何日期时间并引发异常。请参阅我的答案,该答案描述了用strptime解析RFC 3339实际上是不可能的。
Mark Amery 2015年

1
它是硬编码的,但足以满足仅需要解析zulu的情况。
萨沙

1
@alexander是-例如,如果您知道日期字符串是使用JavaScript的toISOString方法生成的,则可能是这种情况。但是在此答案中没有提及对Zulu时间日期的限制,也没有问题表明这就是所需要的,仅使用dateutil它通常同样方便且解析的范围更窄。
马克·阿默里


11

我发现ciso8601是解析ISO 8601时间戳的最快方法。顾名思义,它是用C实现的。

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

GitHub库自述相对于其他答案中列出的所有其他库显示了它们的> 10倍加速。

我的个人项目涉及很多ISO 8601解析。能够切换通话并加快10倍速度真是太好了。:)

编辑:我从此成为ciso8601的维护者。现在比以往更快!


这看起来像一个很棒的图书馆!遗憾的是,对于那些希望在Google App Engine上优化ISO8601解析的人,由于它是C库,因此我们无法使用它,但是您的基准测试datetime.strptime()很有见地,可以证明native 是第二个最快的解决方案。感谢您将所有这些信息放在一起!
hamx0r

3
@ hamx0r,请注意这datetime.strptime()不是完整的ISO 8601解析库。如果您使用的是Python 3.7,则可以使用该datetime.fromisoformat()方法,该方法更加灵活。您可能对此解析器的更完整列表感兴趣,这些列表应尽快合并到ciso8601自述文件中。
movermeyer '18

ciso8601的工作原理相当不错,但是必须首先执行“ pip install pytz”,因为没有pytz依赖项,就无法解析带有时区信息的时间戳。示例如下所示:dob = ciso8601.parse_datetime(result ['dob'] ['date'])
Dirk,

2
@Dirk,仅在Python 2中。但即使如此,也在下一个版本中将其删除
movermeyer

8

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

2
此答案取决于将特定的UTC偏移量(即“ Z”,表示+00:00)硬编码为传递给的格式字符串strptime。这是一个坏主意,因为它将无法解析具有不同UTC偏移量的任何日期时间并引发异常。请参阅我的答案,该答案描述了用strptime解析RFC 3339实际上是不可能的。
Mark Amery 2015年

1
从理论上讲,是的,这失败了。实际上,我从未遇到过Zulu时间以外的ISO 8601格式的日期。对于我的偶发需求,这很好用,并且不依赖于某些外部库。
本杰明·里格斯

4
您可以使用timezone.utc代替timezone(timedelta(0))。另外,如果您提供utctzinfo对象
jfs

不管您是否遇到过,它都不符合规范。
theannouncer

您可以%Z在最新版本的Python中使用for时区。
sventechie

7

我是iso8601 utils的作者。可以在GitHubPyPI 上找到它。这是解析示例的方法:

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

6

datetime.datetime在不安装第三方模块的情况下,在所有受支持的Python版本中将类似于ISO 8601的日期字符串转换为UNIX时间戳或对象的一种直接方法是使用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

11
谢谢。这太恶心了。我喜欢它。
wchargin

1
多么不可思议,超赞,漂亮的骇客!谢谢!
Havok

6

我已经为ISO 8601标准编写了一个解析器,并将其放在GitHub上:https : //github.com/boxed/iso8601。此实现支持规范中的所有内容,但持续时间,间隔,周期性间隔和日期不在Python datetime模块支持的日期范围内。

测试包括在内!:P


2
通常,到工具或库的链接应随附使用说明,对链接资源如何适用于问题的具体说明,或一些示例代码,或者如果可能的话,还应包括所有上述内容。
塞缪尔·柳

6

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

4

因为ISO 8601允许出现许多可选的冒号和破折号,基本上是这样CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]。如果要使用strptime,则需要先删除这些变化。

目标是生成utc datetime对象。


如果您只想使用带有Z后缀的UTC的基本情况,例如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使其更一致/更容易解析。

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


如果您的系统不支持%zstrptime指令(您看到类似的信息ValueError: 'z' is a bad directive in format '%Y%m%dT%H%M%S.%f%z'),那么您需要手动将时间与Z(UTC)相抵消。注意%z在python版本<3中可能无法在您的系统上运行,因为它取决于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

2

对于适用于2.X标准库的内容,请尝试:

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

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


1
这只是忽略了时区'2013-01-28T14:01:01.335612-08:00'->解析为UTC,而不是PDT
gatoatigrado 2013年

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

2

如今,流行的“请求:HTTP for Humans™”软件包的作者发表了《Maya:Datetimes 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>)

2

对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

标准Python函数datetime.fromisoformat文档中也提到了此函数

第三方软件包dateutil中提供了功能更全的ISO 8601解析器dateutil.parser.isoparse。


1

多亏了马克·阿默里(Mark Amery)的出色回答,我设计了函数来说明所有可能的日期时间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))

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


0

最初我尝试使用:

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)

请问你为什么这样做frozenset(('+', '-'))?普通的元组不('+', '-')应该能够完成相同的事情吗?
Prahlad Yeri,

当然可以,但这不是线性扫描而不是完美的哈希查找吗?
AT
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.