【问题标题】:Python 3 datetime.fromtimestamp fails by 1 microsecondPython 3 datetime.fromtimestamp 失败 1 微秒
【发布时间】:2014-08-23 10:31:49
【问题描述】:

我想将具有微秒分辨率的日期时间保存为时间戳。但似乎 Python 3 日期时间模块在加载它们时损失了一微秒。为了测试这一点,让我们创建一个脚本:

test_datetime.py

from random import randint
from datetime import datetime

now = datetime.now()

for n in range(1000):
    d = datetime(year=now.year, month=now.month, day=now.day,
            hour=now.hour, minute=now.minute, second=now.second,
            microsecond=randint(0,999999))

    ts = d.timestamp()
    d2 = datetime.fromtimestamp(ts)

    assert d == d2, 'failed in pass {}: {} != {}'.format(n, d, d2)

python3 test_datetime.py 总是失败一微秒:

Traceback (most recent call last):
  File "test_datetime.py", line 14, in <module>
    assert d == d2, 'failed in pass {}: {} != {}'.format(n, d, d2)
AssertionError: failed in pass 4: 2014-07-02 11:51:46.984716 != 2014-07-02 11:51:46.984715

这种行为可以接受吗?如果我们想要微秒级分辨率,我们不应该依赖 datetime.fromtimestamp 吗?

【问题讨论】:

  • 使用新微秒值生成新值的稍微简单的方法:d = now.replace(microsecond=randint(0,999999))
  • @MartijnPieters:是的,谢谢。我只是想在问题中非常明确。
  • 我会找到now.replace(microsend=randint(0,99999)) clearer,因为这样您就不必解析其他 6 个关键字参数来查看您在该行中所做的事情。

标签: python datetime python-3.x unix-timestamp python-3.4


【解决方案1】:

时间戳值是浮点值。浮点值是近似值,因此会产生舍入误差。

例如,1404313854.442585 的浮点值并不精确。真的是:

>>> dt = datetime(2014, 7, 2, 16, 10, 54, 442585)
>>> dt.timestamp()
1404313854.442585
>>> format(dt.timestamp(), '.20f')
'1404313854.44258499145507812500'

这非常接近 442585,但并不完全。它略低于 442585,因此当您取小数部分时,将其乘以 100 万,然后仅取整数部分,0.991455078125 余数将被忽略,最终得到与 442584。

因此,当您将浮点值转换回datetime 对象时,1 微秒的舍入误差是正常的

如果你需要精确,不要依赖float;也许将微秒值存储为单独的整数,然后使用dt.fromtimestamp(seconds).replace(microsecond=microseconds)

在这种情况下,您可能会发现 rejection noticePEP-410 (Use decimal.Decimal type for timestamps) 很有启发性。 PEP 谈到了时间戳表示为浮点数的精度问题。

【讨论】:

  • 这是否表明fromtimestamp 存在缺陷,即截断而不是舍入?鉴于时间戳预计会略微偏离。这建议使用fromtimestamp(ts + 0.0000005) 解决方法。
  • @MarkRansom:This patch引入了参数;在此之前,舍入隐含在其他操作中。根据 cmets 和 issue introducing it 的说法,这里保留了旧的舍入行为,并为其他地方的问题引入了显式舍入。
  • @MarkRansom:啊,找到了。见this issue;舍入已更改,因为这是其他地方更常见的行为,并避免将时间戳舍入到未来
  • @MarkRansom:例如而不是让一半的时间戳落后一微秒,而不是让另一半的时间戳落后一微秒。
  • 我不确定我是否同意该问题中的推理。我喜欢坚持一个重要原则:当有互补功能时,如果可能的话,往返应该总是产生相同的结果。在这种情况下,它可能的。即使只是添加nextafter 而不是四舍五入也可以解决问题。
【解决方案2】:

时间戳是一个 POSIX 时间,它本质上被概念化为自任意“纪元”以来的整数秒数。 datetime.fromtimestamp() 返回“与 POSIX 时间戳对应的本地日期和时间,例如由 time.time() 返回”,其 documentation 告诉我们“返回[s] 自纪元以来的时间(以秒为单位)作为浮点数。请注意,尽管时间始终以浮点数形式返回,但并非所有系统都提供比 1 秒更好的精度。"

当中间数据类型实际上不能保证亚秒级的精度时,期望通过时间戳的转换来保留六位小数的精度似乎有点不合理。浮点数无法准确表示所有十进制值。

编辑:以下代码在程序运行时测试对于任意日期时间哪些微秒值无效。

from datetime import datetime
baset = datetime.now()

dodgy = []
for i in range(1000000):
    d = baset.replace(microsecond=i)
    ts = d.timestamp()
    if d != datetime.fromtimestamp(ts):
        dodgy.append(i)
print(len(dodgy))

我得到了 499,968 次“狡猾”的时间,但我还没有检查过它们。

【讨论】:

  • datetime 实际上并不使用time.time(),但无论平台如何,都支持微秒。然而,这浮点数的问题。
  • 我同意这是一个浮点问题,并且没有意识到所有平台现在都提供微秒精度 - 只是他们将计时器报告为最佳分辨率作为浮动点数。
  • 499,968 次“狡猾”:几乎一半。这听起来是正确的,因为转换时C code rounds down(注意那里的_PyTime_ROUND_DOWN 常量)。给定 100 万个浮点值,有不到一半的概率会略低于原始微秒值,而另一半则略高于原始微秒值。余数是可以精确表示为二进制分数的值。
猜你喜欢
  • 2017-01-22
  • 1970-01-01
  • 2017-07-31
  • 2013-02-08
  • 1970-01-01
  • 1970-01-01
  • 2019-02-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多