ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

需要明确的是:ISO 8601 是主要标准。 RFC 3339 是自称为 ISO 8601 的“配置文件”,它制定了一些 ISO 8601 规则的unwise overrides
不要错过下面用于反转 isoformat() 的 python3.7+ 解决方案
这个问题不应该因为链接的帖子而被关闭。由于这个要求解析 ISO 8601 时间字符串(python pre 到 3.7 不支持本机),另一个是使用过时的方法将日期时间对象格式化为纪元字符串。

J
Josh Correia

来自 python-dateutil 的 isoparse 函数

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 比较

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


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

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

classmethod datetime.fromisoformat(date_string):返回与 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 >>> datetime.fromisoformat('2011-11-04') datetime.datetime(2011, 11, 4, 0, 0) ...

请务必阅读文档中的警告!


这很奇怪。因为 datetime 可能包含 tzinfo,因此输出时区,但 datetime.fromisoformat() 不解析 tzinfo ?似乎是一个错误..
不要错过文档中的注释,它不接受 所有 有效的 ISO 8601 字符串,只接受由 isoformat 生成的字符串。由于尾随 Z,它不接受问题 "2008-09-03T20:56:35.450686Z" 中的示例,但它接受 "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,它实际上会失败。
n
nofinator

请注意,在 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 的旧名称。
C
Community

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 或 { 6}。

因此,这些都是有效的 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 does not implement the %z directive at all in Python 2。)

这里推荐 strptime 的多个答案都通过在其格式字符串中包含一个文字 Z 来解决这个问题,它与提问者的示例日期时间字符串中的 Z 匹配(并丢弃它,产生一个没有时区):

>>> 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。请改用此处答案中描述的许多其他方法之一。


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

试试 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”的库是不完整的。它明确指出,它不会仅举一个例子来根据周数处理日期。
F
FObersteiner

从 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 或更高版本。
P
Peter Mortensen

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

>>> 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 偏移量的其他人来说是次要的答案。
F
FObersteiner

来自评论之一的简单选项:将 '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'))
388 ns ± 48.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit dateutil.parser.isoparse(s)
11 µs ± 1.05 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)

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

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

(Windows 10 上的 Python 3.9.12 x64)


@mikerodent:关键是 fromisoformat+00:00 而不是 Z 解析为 aware datetime,其中 tzinfo 为 UTC。如果您的输入例如以 Z+00:00 结尾,您可以在将其输入 fromisoformat 之前删除 Z。其他 UTC 偏移量(例如 +05:30)将被解析为静态 UTC 偏移量(不是实际时区)。
F
Flimm
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位在翻译中丢失了吗?
P
Peter Mortensen

目前,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。引用的问题现已关闭。
B
Blairg23

只需使用 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 纪元时间,就好像日期在我的本地时区一样。
这个答案是错误的,不应该被接受。可能整个问题都应标记为 stackoverflow.com/questions/11743019/… 的重复项
@tripleee 实际上我只是检查了代码,它似乎确实返回了正确的答案:455051100(在 epochconverter.com 处检查),,除非我遗漏了什么?
m
movermeyer

我发现 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
e
enchanter

如果不想使用 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 是的 - 例如,如果您知道您的日期字符串是使用 JavaScript 的 toISOString 方法生成的,则可能是这种情况。但是在这个答案中没有提到对祖鲁时间日期的限制,问题也没有表明这就是所需要的,并且仅使用 dateutil 通常同样方便,并且可以解析的范围更小。
D
Don Kirkby

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

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


当您设置字符串值时,Django 的 DateTimeField 会使用它。
P
Peter Mortensen

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

包括测试! :P


P
Peter Mortensen

这适用于 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 中使用 %Z 作为时区。
M
Matthew Moisen

我是iso8601utils的作者。它可以在 on GitHubPyPI 上找到。以下是解析示例的方法:

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

D
Damian Yerrick

在所有受支持的 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

谢谢。这很恶心。我喜欢它。
多么令人难以置信,真棒,美丽的黑客!谢谢!
欢迎来到坏与丑部分。
P
Peter Mortensen

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

z
zawuza

另一种方法是使用 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 中也提到了这个函数:

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


M
Michael Dorner

如果仍然使用 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 年的日期字符串解析之类的事情。


对于这种简单的情况,强烈建议不要使用 pandas:它依赖于 pytz,这违反了 python 标准,并且 pd.Timestamp 巧妙地不是兼容的 datetime 对象。
谢谢你的评论。你对我有一些指示吗?我找不到 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 :根本不一样。
感谢您对这项正在进行的工作的指点。我不知道这个问题,但我真的希望他们能尽快解决它!但再说一遍:我不敢相信时间解析仍然是一个问题。 :-)
t
theannouncer

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

目标是生成一个 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" )

%z

ValueError:“z”是格式“%Y%m%dT%H%M%S.%f%z”的错误指令

Z

%z

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

G
Gordon Wrigley

对于适用于 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
u
user2646026

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

我认为它有时会抛出异常,如果它可以尽最大努力猜测日期时间是什么,则不能保证抛出异常。
错误隐藏是反模式的前三名:不要。
j
jrc

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

C
Community

感谢伟大的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))

A
A T

最初我尝试过:

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(('+', '-'))?像 ('+', '-') 这样的普通元组不应该能够完成同样的事情吗?
当然,但这不是线性扫描而不是完美的散列查找吗?
D
Denny Weinberg
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% 错误:不要使用!