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

Posted

技术标签:

【中文标题】如何解析 ISO 8601 格式的日期?【英文标题】:How do I parse an ISO 8601-formatted date? 【发布时间】:2010-09-12 18:30:03 【问题描述】:

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

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

最好的方法是什么?

【问题讨论】:

Python 错误:issue15873: datetime: add ability to parse RFC 3339 dates and times 相关:Convert timestamps with offset to datetime obj using strptime 要明确:ISO 8601 是主要标准。 RFC 3339 是 ISO 8601 的自称“配置文件”,它制定了一些 ISO 8601 规则的 unwise overrides。 不要错过下面的python3.7+反转isoformat()的解决方案 这个问题不应该因为链接的帖子而被关闭。由于这个要求解析一个 ISO 8601 时间字符串(python pre 到 3.7 本机不支持),另一个要求 格式化一个日期时间对象到一个使用过时方法的纪元字符串。 【参考方案1】:

isoparse 来自 python-dateutil 的函数

python-dateutil 包具有 dateutil.parser.isoparse 不仅可以解析 RFC 3339 日期时间字符串,如问题中的字符串,还可以解析其他不符合 RFC 3339 的 ISO 8601 日期和时间字符串(例如没有 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)

python-dateutil 包也有dateutil.parser.parse。与isoparse相比,它可能不那么严格,但它们都相当宽容,并会尝试解释你传入的字符串。如果你想消除任何误读的可能性,你需要使用比任何一个更严格的东西这些功能。

与 Python 3.7+ 的内置 datetime.datetime.fromisoformat 比较

datutil.parser.isoparse 是一个完整的 ISO-8601 格式解析器,但 fromisoformat 故意不是。请参阅后一个函数的文档以了解此警告性警告。 (见this answer)。

【讨论】:

对于懒人来说,它是通过python-dateutil而不是dateutil安装的,所以:pip install python-dateutil 请注意dateutil.parser 是故意的hacky:它试图猜测格式并在模棱两可的情况下做出不可避免的假设(只能手动定制)。因此,仅当您需要解析未知格式的输入并且可以容忍偶尔的误读时才使用它。 同意。一个示例是传递 9999 的“日期”。这将返回与 datetime(9999, current month, current day) 相同的值。在我看来不是一个有效的日期。 @ivan_pozdeev 你会推荐什么包来进行非猜测解析? @ivan_pozdeev 读取 iso8601 日期的模块有更新:dateutil.readthedocs.io/en/stable/…【参考方案2】:

自 Python 3.7 起,datetime 标准库具有反转 datetime.isoformat() 的函数。

classmethod datetime.fromisoformat(date_string):

date.isoformat()datetime.isoformat() 发出的格式之一返回与date_string 对应的datetime

具体来说,此函数支持以下格式的字符串:

YYYY-MM-DD[*HH[:MM[:SS[.mmm[mmm]]]][+HH:MM[:SS[.ffffff]]]]

* 可以匹配任何单个字符。

注意:这不支持解析任意 ISO 8601 字符串 - 它仅用作 datetime.isoformat() 的逆运算。

例子:

>>> from datetime import datetime
>>> datetime.fromisoformat('2011-11-04')
datetime.datetime(2011, 11, 4, 0, 0)

请务必阅读文档中的注意事项!

【讨论】:

这很奇怪。因为datetime 可能包含tzinfo,因此输出时区,但datetime.fromisoformat() 不解析tzinfo?看起来像一个错误.. 不要错过文档中的注释,它不接受 所有 有效的 ISO 8601 字符串,只接受由 isoformat 生成的字符串。它不接受问题"2008-09-03T20:56:35.450686Z" 中的示例,因为尾随Z,但它确实接受"2008-09-03T20:56:35.450686" 要正确支持Z,可以使用date_string.replace("Z", "+00:00")修改输入脚本。 请注意,对于几秒钟,它只能处理精确的 0、3 或 6 位小数。如果输入数据有 1、2、4、5、7 位或更多小数位,解析将失败! @JDOaktown 这个例子使用原生 Python 的 datetime 库,而不是 dateutil 的解析器。如果使用这种方法,如果小数位不是 0、3 或 6,它实际上会失败。【参考方案3】:

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

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

见问题here

【讨论】:

注意 - 如果使用 Naive 日期时间 - 我认为你根本没有 TZ - Z 可能不匹配任何东西。 这个答案(以其当前的编辑形式)依赖于将特定的 UTC 偏移量(即“Z”,表示 +00:00)硬编码到格式字符串中。这是一个坏主意,因为它将无法解析具有不同 UTC 偏移量的任何日期时间并引发异常。请参阅 my answer,它描述了使用 strptime 解析 RFC 3339 实际上是不可能的。 在我的例子中 %f 捕获了微秒而不是 Z,datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f') 所以这成功了 Py3K 是指 Python 3000 吗?!? @Robino IIRC,“Python 3000”是现在称为 Python 3 的旧名称。【参考方案4】:

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 或 @ 987654336@.

因此,这些都是有效的 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

+HHMM 或 -HHMM 形式的 UTC 偏移量(如果对象是幼稚的,则为空字符串)。

示例:(空)、+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 匹配(并丢弃它,生成@987654350 @没有时区的对象):

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

除非您确定只需要支持祖鲁时间的 RFC 3339 日期时间,而不需要支持其他时区偏移的日期时间,否则不要使用 strptime。请改用此处答案中描述的许多其他方法之一。

【讨论】:

令人费解的是为什么 strptime 没有 ISO 格式时区信息的指令,以及为什么它不能被解析。难以置信。 @CsabaToth 完全同意 - 如果我有时间消磨时间,也许我会尝试将它添加到语言中。或者你也可以这样做,如果你愿意的话——我看你有一些 C 经验,不像我。 @CsabaToth - 为什么不可思议?它对大多数人来说足够好,或者他们发现了足够简单的解决方法。如果您需要该功能,它是开源的,您可以添加它。或者花钱请人替你做。为什么有人应该自愿利用自己的空闲时间来解决您的具体问题?让源与你同在。 @PeterMasar 难以置信,因为通常人们会发现 python 中的东西已经经过深思熟虑和充分地实现。我们已经被这种对细节的关注所宠坏了,所以当我们偶然发现一些“unpythonic”语言的东西时,我们会把我们的玩具扔出婴儿车,就像我现在要做的那样。哇哇哇哇哇 :-( strptime() in Python 3.7 现在支持此答案中描述为不可能的所有内容(时区偏移中的'Z'文字和':')。不幸的是,还有另一个极端情况使 RFC 3339 与 ISO 8601 根本不兼容,即前者允许负空时区偏移 -00:00,而后者则不允许。【参考方案5】:

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

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

【讨论】:

简单如iso8601.parse_date("2008-09-03T20:56:35.450686Z") 问题不是“如何解析 ISO 8601 日期”,而是“如何解析这种确切的日期格式。” @tiktak OP 询问“我需要解析像 X 这样的字符串”,我对这两个库的回答是使用另一个库,因为 iso8601 仍然存在重要问题。我参与或不参与这样的项目与答案完全无关。 iso8601,又名 pyiso8601,已于 2014 年 2 月更新。最新版本支持更广泛的 ISO 8601 字符串集。我在一些项目中使用效果很好。 遗憾的是,在 pypi 上名为“iso8601”的库是不完整的。它明确指出它不会仅举一个例子来根据周数处理日期。【参考方案6】:

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

编辑:

正如 Martijn 所指出的,如果你使用 isoformat() 创建了 datetime 对象,你可以简单地使用 datetime.fromisoformat()

【讨论】:

但在 3.7 中,您拥有datetime.fromisoformat(),它会自动处理您输入的字符串:datetime.datetime.isoformat('2018-01-31T09:24:31.488670+00:00') 好点。我同意,我建议使用datetime.fromisoformat()datetime.isoformat() 这是唯一真正符合问题标准的答案。如果你必须使用 strptime 这是正确的答案 您的示例在 Python 3.6 上失败:ValueError: time data '2018-01-31T09:24:31.488670+00:00' does not match format '%Y-%m-%dT%H:%M:%S.%f%z' 这是由于 %z 不匹配 +00:00。但是 +0000 匹配 %z 参见 python 文档 docs.python.org/3.6/library/… @Eric 是的,这个答案只适用于 Python 3.7 或更高版本。【参考方案7】:

您得到的确切错误是什么?是不是像下面这样?

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

【讨论】:

你不能只去掉 .Z 因为它意味着时区并且可以不同。我需要将日期转换为 UTC 时区。 普通的 datetime 对象没有时区的概念。如果您所有的时间都以“Z”结尾,那么您获得的所有日期时间都是 UTC(祖鲁时间)。 如果时区不是"""Z",那么它必须是小时/分钟的偏移量,可以直接添加到日期时间对象中/从日期时间对象中减去。您可以创建一个 tzinfo 子类来处理它,但这可能不被推荐。 此外,"%f" 是微秒说明符,因此(时区天真)strptime 字符串看起来像:"%Y-%m-%dT%H:%M:%S.% f"。 如果给定的日期时间字符串的 UTC 偏移量不是“Z”,这将引发异常。它不支持整个 RFC 3339 格式,与其他正确处理 UTC 偏移量的方法相比,它的效果较差。【参考方案8】:
import re
import datetime
s = "2008-09-03T20:56:35.450686Z"
d = datetime.datetime(*map(int, re.split(r'[^\d]', s)[:-1]))

【讨论】:

我不同意,这实际上是不可读的,据我所知,没有考虑到 Zulu (Z),即使提供了时区数据,它也会使这个日期时间变得幼稚。 我觉得它的可读性很强。事实上,这可能是在不安装额外软件包的情况下进行转换的最简单、最高效的方法。 我想这相当于 d=datetime.datetime(*map(int, re.split('\D', s)[:-1]))。 变体:datetime.datetime(*map(int, re.findall('\d+', s)) 这会导致一个没有时区的天真日期时间对象,对吧?所以 UTC 位在翻译中丢失了?【参考方案9】:

其中一个 cmets 的简单选项:将 'Z' 替换为 '+00:00' - 并使用 Python 3.7+ 的 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 可以将'Z' 字符解析为UTC,但fromisoformat 的速度快了~ x40(另见:A faster strptime):

%timeit datetime.fromisoformat(s.replace('Z', '+00:00'))
346 ns ± 22.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit datetime.strptime(s, '%Y-%m-%dT%H:%M:%S.%f%z')
14.2 µs ± 452 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit dateutil.parser.parse(s)
80.1 µs ± 3.32 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

(Windows 10 上的 Python 3.8.7 x64)

【讨论】:

@mikerodent:关键是fromisoformat+00:00 解析为Z 而不是aware datetime,其中tzinfo 为UTC。如果您的输入例如以Z+00:00 结尾,您可以在将其输入fromisoformat 之前删除Z。其他 UTC 偏移量,例如+05:30 然后将被解析为静态 UTC 偏移量(不是实际时区)。【参考方案10】:

现在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())

【讨论】:

只使用 python-dateutil - 箭头需要 python-dateutil。 Arrow 现在支持 ISO8601。引用的问题现已关闭。【参考方案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())

Documentation

【讨论】:

这不正是上面@Flimms 的回答吗? 你在哪里看到他在几秒钟内解析?我通过尝试获取纪元时间找到了这篇文章,所以我认为其他人也会这样做。 我的系统上的 不是 UTC。相反,以秒为单位的输出是 unix 纪元时间,就好像日期在我的本地时区一样。 这个答案有问题,不应该被接受。可能整个问题应该标记为***.com/questions/11743019/…的重复 @tripleee 实际上我刚刚检查了代码,它似乎确实返回了正确答案:455051100(检查于epochconverter.com),,除非我遗漏了什么?【参考方案12】:

我发现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 的维护者。现在比以往任何时候都快!

【讨论】:

这看起来像一个很棒的图书馆!对于那些想要在 Google App Engine 上优化 ISO8601 解析的人,遗憾的是,我们不能使用它,因为它是一个 C 库,但您的基准测试很有见地表明原生 datetime.strptime() 是下一个最快的解决方案。感谢您将所有这些信息放在一起! @hamx0r,请注意datetime.strptime() 不是完整的 ISO 8601 解析库。如果您使用的是 Python 3.7,则可以使用 datetime.fromisoformat() 方法,该方法更灵活一些。你可能是interested in this more complete list of parsers,它应该很快被合并到 ciso8601 README 中。 ciso8601 工作得很好,但是必须先执行“pip install pytz”,因为如果没有 pytz 依赖,就无法解析带有时区信息的时间戳。示例如下所示: dob = ciso8601.parse_datetime(result['dob']['date']) @Dirk,only in Python 2。但即使是 should be removed 在下一个版本中。【参考方案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)

【讨论】:

这个答案依赖于将特定的 UTC 偏移量(即“Z”,表示 +00:00)硬编码到传递给strptime 的格式字符串中。这是一个坏主意,因为它将无法解析具有不同 UTC 偏移量的任何日期时间并引发异常。请参阅my answer,它描述了如何使用 strptime 解析 RFC 3339 实际上是不可能的。 它是硬编码的,但它足以满足您只需要解析 zulu 的情况。 @alexander 是的 - 例如,如果您知道您的日期字符串是使用 javascripttoISOString 方法生成的,则可能是这种情况。但是在这个答案中没有提到对祖鲁时间日期的限制,问题也没有表明这就是所需要的,并且仅使用dateutil 通常同样方便并且可以解析的范围更小。【参考方案14】:

如果您使用 Django,它提供了dateparse module,它接受一堆类似于 ISO 格式的格式,包括时区。

如果您不使用 Django 并且不想使用此处提到的其他库之一,您可以将 the Django source code for dateparse 调整为您的项目。

【讨论】:

Django 的DateTimeField 在您设置字符串值时使用它。【参考方案15】:

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

包括测试! :P

【讨论】:

一般来说,链接到工具或库should be accompanied by usage notes, a specific explanation of how the linked resource is applicable to the problem, or some sample code,或者如果可能的话,以上所有。【参考方案16】:

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

【讨论】:

这个答案依赖于将特定的 UTC 偏移量(即“Z”,表示 +00:00)硬编码到传递给strptime 的格式字符串中。这是一个坏主意,因为它将无法解析具有不同 UTC 偏移量的任何日期时间并引发异常。请参阅my answer,它描述了如何使用 strptime 解析 RFC 3339 实际上是不可能的。 理论上,是的,这失败了。在实践中,我从来没有遇到过不是祖鲁时间的 ISO 8601 格式日期。对于我偶尔的需要,这很好用,并且不依赖于某些外部库。 您可以使用timezone.utc 代替timezone(timedelta(0))。此外,如果您supply utc tzinfo object,代码可以在 Python 2.6+ 中运行(至少) 遇到过没关系,不符合规范。 在最新版本的 Python 中,您可以使用%Z 作为时区。【参考方案17】:

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

【讨论】:

【参考方案18】:

在不安装第三方模块的情况下,在所有受支持的 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

【讨论】:

谢谢。这太恶心了。我喜欢它。 多么令人难以置信、真棒、漂亮的 hack!谢谢! 欢迎来到坏与丑部分。【参考方案19】:

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

【讨论】:

【参考方案20】:

因为 ISO 8601 允许存在许多可选的冒号和破折号,基本上是CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]。如果要使用 strptime,则需要先去除这些变化。目标是生成一个 utc 日期时间对象。


如果您只想要一个适用于带有 Z 后缀(如 2016-06-29T19:36:29.3453Z)的 UTC 的基本案例:
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,使其更一致/更易于解析。
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"[:]|([-](?!((\d2[:]\d2)|(\d4))$))", '', 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 版本 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"[:]|([-](?!((\d2[:]\d2)|(\d4))$))", '', 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

【讨论】:

【参考方案21】:

另一种方法是使用专门的 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 中可用。

【讨论】:

【参考方案22】:

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

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

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

【讨论】:

这只是忽略了时区 '2013-01-28T14:01:01.335612-08:00' --> 解析为 UTC,而不是 PDT【参考方案23】:

如果解析无效的日期字符串,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

【讨论】:

我认为它有时会抛出异常,如果它可以尽力猜测日期时间是什么,则不保证会抛出异常。 错误隐藏是反模式的前三名:不要。【参考方案24】:

现在有Maya: Datetimes for Humans™,来自流行的 Requests: 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>)

【讨论】:

【参考方案25】:

感谢伟大的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'(.*?[+-]\d2):(\d2)'
    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))

【讨论】:

【参考方案26】:

如果还是使用pandas,我可以从pandas 推荐Timestamp。在那里你可以

ts_1 = pd.Timestamp('2020-02-18T04:27:58.000Z')    
ts_2 = pd.Timestamp('2020-02-18T04:27:58.000')

咆哮:令人难以置信的是,我们仍然需要担心 2021 年的日期字符串解析之类的事情。

【讨论】:

pandas 对于这种简单的情况强烈不鼓励:它依赖于 pytz,这违反了 python 标准,并且 pd.Timestamp 巧妙地不是兼容的日期时间对象。 感谢您的评论。你对我有一些指示吗?我找不到 pytz:github.com/pandas-dev/pandas/blob/…,我不确定您指的是什么 Python 标准及其违规。 见rant by Paul Ganssle。至于不兼容,datetime.fromisoformat('2021-01-01T00:00:00+01:00').tzinfo.utcpandas.Timestamp('2021-01-01T00:00:00+01:00').tzinfo.utc都执行:根本不一样。 感谢您对这项正在进行的工作的指点。我不知道这个问题,但我真的希望他们能尽快解决它!但再说一遍:我不敢相信时间解析仍然是一个问题。 :-)【参考方案27】:

最初我尝试过:

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(('+', '-'))?像('+', '-') 这样的普通元组不应该能够完成同样的事情吗? 当然,但这不是线性扫描而不是完美的散列查找吗?【参考方案28】:
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 进行解析。

【讨论】:

90% 死代码,10% 错误:不要使用!

以上是关于如何解析 ISO 8601 格式的日期?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Swift 中解析/创建格式为小数秒 UTC 时区(ISO 8601、RFC 3339)的日期时间戳?

在django tables2中解析ISO 8601日期格式

在 Excel 中解析 ISO8601 日期/时间(包括 TimeZone)

Tabulator 5.0 - 解析日期时间 luxon - 日期时间 ISO 8601

如何将 Excel 中的日期转换为 ISO 8601 格式

如何在php中将ISO8601转换为日期格式