Python 将日期时间转换为时间戳并再次转换

Python 将日期时间转换为时间戳并再次转换,python,datetime,python-3.x,Python,Datetime,Python 3.x,我在Python中的datetime方面遇到了一些问题。我尝试将日期时间转换为时间戳,然后再次转换,无论我如何尝试,最终结果都不一样。我总是以datetime的datetime结束(2014、1、30、23、59、401998) 最后一个数字是微秒。。。内部结构是否准确?让我们看看 counter={} for i in range(0,1000000,43): # fuzz up some random-ish dates d = datetime.datetime( 1990+

我在Python中的datetime方面遇到了一些问题。我尝试将日期时间转换为时间戳,然后再次转换,无论我如何尝试,最终结果都不一样。我总是以datetime的datetime结束(2014、1、30、23、59、401998)


最后一个数字是微秒。。。内部结构是否准确?让我们看看

counter={}
for i in range(0,1000000,43): 
   # fuzz up some random-ish dates
   d = datetime.datetime( 1990+(i%20), 1+(i%12), 1+(i%28), i%24, i%60, i%60, i)
   ts=datetime.datetime.timestamp( d)
   b = b=datetime.datetime.fromtimestamp(ts)
   msdif=d.microsecond-b.microsecond 
   if msdif in counter:
     counter[msdif] += 1
   else:
     counter[msdif]=1
   assert b.day==d.day and b.hour==d.hour and b.minute==d.minute and b.second=d.second

  >>>
  >>> counter
  {1: 23256}
  >>> 
我相信您已经在datetime库中发现了一个差一微秒的错误,除非规范中隐藏了一些反常的东西

(我原以为价差在零附近,反映出某种舍入误差)

这是一个已知的:

注意:微秒已经过去了。
.timestamp()
已返回略小于
1999
微秒的结果:

>>> from decimal import Decimal
>>> local.timestamp()
1391126380.001999
>>> Decimal(local.timestamp())
Decimal('1391126380.0019989013671875')
:

要解决此问题,可以使用显式公式:

>>> from datetime import datetime, timedelta
>>> local = datetime(2014, 1, 30, 23, 59, 40, 1999)
>>> datetime.utcfromtimestamp(local.timestamp())
datetime.datetime(2014, 1, 30, 23, 59, 40, 1998) # UTC time
>>> datetime(1970, 1, 1) + timedelta(seconds=local.timestamp())
datetime.datetime(2014, 1, 30, 23, 59, 40, 1999) # UTC time

注意:所有示例中的输入都是本地时间,但最后一个示例中的结果是UTC时间。

很少有小数点的浮点数可以精确表示为二进制浮点数;通常会有一些非常小的误差。有时它会小于所需的数字,有时它会更大,但它应该总是非常接近。您的示例的精确值为
1391147980.0019989013671875
,与您指定的值相差0.1微秒

从浮点
时间戳
日期时间
的转换应采用舍入,以确保往返转换的值与原始值相同。如所述,这是作为针对Python 3.4的bug输入的;它声称在以后的版本中已修复,但在Python 3.5.0中仍然存在,使用的值与问题中给出的值相同。运行类似于的测试显示,精确匹配和低1微秒的结果几乎各占一半

因为您知道原始值是微秒的整数,所以可以添加一个偏移量,以确保二进制浮点值始终高于十进制值,同时仍然足够小,在正确舍入时不会影响结果。由于四舍五入应在0.5微秒发生,因此理想的偏移量应为该偏移量的一半,即0.25微秒

以下是Python 3.5.0中的结果:

>>> a = datetime.datetime.timestamp(datetime.datetime(2014, 1, 30, 23, 59, 40, 1999))
>>> b = datetime.datetime.fromtimestamp(a)
>>> a
1391147980.001999
>>> b
datetime.datetime(2014, 1, 30, 23, 59, 40, 1998)

>>> b = datetime.datetime.fromtimestamp(a + 0.00000025)
>>> b
datetime.datetime(2014, 1, 30, 23, 59, 40, 1999)

>>> counter={}
>>> for i in range(0,1000000):
   # fuzz up some random-ish dates
   d = datetime.datetime(1990+(i%20), 1+(i%12), 1+(i%28), i%24, i%60, i%60, i)
   ts = datetime.datetime.timestamp(d)
   b = datetime.datetime.fromtimestamp(ts + 0.00000025)
   msdif = d.microsecond - b.microsecond 
   if msdif in counter:
     counter[msdif] += 1
   else:
     counter[msdif]=1
   assert b.day==d.day and b.hour==d.hour and b.minute==d.minute and b.second==d.second

>>> counter
{0: 1000000}

我认为这是因为浮点运算,如果打印a,您会发现值为
1391144380.001999
。当转换回来时,它会丢失最后一位。如果它不太接近下一个十进制值,
(2014,1,30,23,59,40,1996)
为什么
计数器不具有
0
的计数?因为与时间戳之间的转换总是关闭一微秒。错误不是四舍五入错误!您也可以将
0.0000004
添加到
timestamp
@MarkRansom中:取整是一个微妙的问题。显而易见的解决方案往往会失败。我的常数是经过仔细选择的,无论使用何种舍入模式,它都能工作。虽然
0.00000025
可能会更好。@markransem:如果有效;它应该有自己的答案:解释、证明和测试感谢这个建议。我已经发布了一个带有解释和测试的答案,尽管可能缺乏证据。它在Python3.5(当前正在开发的3.5版本,future
3.5.1
)中得到了修复。我的答案明确地说是“下一个3…发行版”注意:Python 3.4+中的固定版本使用
ROUND\u HALF\u偶数
模式(默认为浮点数和
ROUND()
方法)<代码>+0.00000025
将其打断:
datetime.utcfromtimestamp(-1.5/1000000+0.00000025)!=datetime.utcfromtimestamp(-1.5/1000000)
在没有bug的Python版本(和Python 2.7)上。@J.F.Sebastian今晚我刚刚下载了Python 3的最新二进制版本,所以这就是我测试的对象。我从来没有考虑过负时间戳,谢谢这个例子。Python2.7没有
timestamp
方法,因此没有问题。3.5.0是几天前发布的,因此不能将其视为“下一个”版本。更改适用于3.4、3.5和未来的3.6分支。
>>> from datetime import datetime
>>> local = datetime(2014, 1, 30, 23, 59, 40, 1999)
>>> datetime.fromtimestamp(local.timestamp())
datetime.datetime(2014, 1, 30, 23, 59, 40, 1999)
>>> from datetime import datetime, timedelta
>>> local = datetime(2014, 1, 30, 23, 59, 40, 1999)
>>> datetime.utcfromtimestamp(local.timestamp())
datetime.datetime(2014, 1, 30, 23, 59, 40, 1998) # UTC time
>>> datetime(1970, 1, 1) + timedelta(seconds=local.timestamp())
datetime.datetime(2014, 1, 30, 23, 59, 40, 1999) # UTC time
>>> a = datetime.datetime.timestamp(datetime.datetime(2014, 1, 30, 23, 59, 40, 1999))
>>> b = datetime.datetime.fromtimestamp(a)
>>> a
1391147980.001999
>>> b
datetime.datetime(2014, 1, 30, 23, 59, 40, 1998)

>>> b = datetime.datetime.fromtimestamp(a + 0.00000025)
>>> b
datetime.datetime(2014, 1, 30, 23, 59, 40, 1999)

>>> counter={}
>>> for i in range(0,1000000):
   # fuzz up some random-ish dates
   d = datetime.datetime(1990+(i%20), 1+(i%12), 1+(i%28), i%24, i%60, i%60, i)
   ts = datetime.datetime.timestamp(d)
   b = datetime.datetime.fromtimestamp(ts + 0.00000025)
   msdif = d.microsecond - b.microsecond 
   if msdif in counter:
     counter[msdif] += 1
   else:
     counter[msdif]=1
   assert b.day==d.day and b.hour==d.hour and b.minute==d.minute and b.second==d.second

>>> counter
{0: 1000000}