Weird datetime.utcnow() bug



考虑这个简单的Python脚本:

$ cat test_utc.py
from datetime import datetime
for i in range(10_000_000):
first = datetime.utcnow()
second = datetime.utcnow()
assert first <= second, f"{first=} {second=} {i=}"

当我从像python test_utc.py这样的shell中运行它时,它完成了无错误,正如预期的那样。然而,当我在Docker容器中运行它时,断言失败:

$ docker run -it --rm -v "$PWD":/code -w /code python:3.10.4 python test_utc.py
Traceback (most recent call last):
File "/code/test_utc.py", line 7, in <module>
assert first <= second, f"{first=} {second=} {i=}"
AssertionError: first=datetime.datetime(2022, 5, 24, 19, 5, 1, 861308) second=datetime.datetime(2022, 5, 24, 19, 5, 1, 818270) i=1818860

这怎么可能?

注:一个同事报告说,将range参数增加到100_000_000也会使它在他们的mac上的shell中失败(但对我来说不是)。

utcnownowtodayfromtimestamptime,表示:

虽然此函数通常返回非递减值,但如果在两次调用之间设置了系统时钟,则它可以返回比前一次调用更低的值。

utcnow代码也显示了time的用法:

def utcnow(cls):
"Construct a UTC datetime from time.time()."
t = _time.time()
return cls.utcfromtimestamp(t)

这样的系统时钟更新也是monotonic存在的原因,它说:

返回单调时钟的值(以小数秒为单位),即不能倒退的时钟。时钟不受系统时钟更新的影响。

utcnow没有这样的保证。

你的电脑没有一个完美的时钟,它时不时地通过互联网与更精确的时钟同步,可能会把它调回来。请看这里的示例答案

看起来Docker让它变得更糟,参见Docker博客中的地址时间漂移。摘录:

macOS没有原生容器支持。辅助虚拟机有自己的内部时钟,独立于主机的时钟。当两个时钟偏离时,依赖于时间或文件时间戳的命令可能会突然开始表现不同

最后,当向后更新发生时,您可以增加捕获向后更新的机会。如果不是在获得firstsecond之间,而是在second和下一个first之间,那么您将错过它!下面的代码修复了这个问题,也进行了微优化(包括删除utcnow中间人),所以它检查得更快/更频繁:

import time
from itertools import repeat
def function():
n = 10_000_000
reps = repeat(1, n)
now = time.time
first = now()
for _ in reps:
second = now()
assert first <= second, f"{first=} {second=} i={n - sum(reps)}"
first = second
function()

最新更新