2015-09-24 132 views
4

我在Python中遇到了datetime問題。我試圖將日期時間轉換爲時間戳,然後再回來,無論我如何嘗試最終結果都不相同。我總是以日期時間結束(2014年,1月30日,23日,59日,40日,1998年)。將日期時間轉換爲時間戳並再次返回

import datetime 

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

print(b) 
+0

我想這是因爲浮點運算,如果打印,你會發現是'1391144380.001999'值。在將它轉換回來時,它失去了最後一點。如果它不太接近下一個十進制值,也不會發生同樣的情況:'(2014,1,30,23,59,40,1996)' – roymustang86

回答

3

這是一個衆所周知Python 3.4 issue

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

注:微秒走了。該.timestamp()已經返回結果略小於1999微秒:

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

The rounding is fixed in the next 3.4, 3.5, 3.6 releases

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

注意:所有示例中的輸入都是當地時間,但結果是最後一次UTC時間。

+0

您可能也可以在'timestamp'中添加'0.0000004'。 –

+0

@MarkRansom:舍入是一個微妙的問題。明顯的解決方案經常會打破[閱讀討論](http://bugs.python.org/issue23517)。 – jfs

+0

無論使用什麼舍入模式,我的常數都經過精心挑選才能正常工作。雖然'0.00000025'可能會更好。 –

3

最後一個數字是微秒......內部是否準確?讓我們來找出答案。

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

我相信你已經找到了日期時間庫中的off-by-一個微秒的錯誤,除非有埋在規範不正當的東西。

(我期待一個流傳在零附近,反映了某種形式的舍入誤差)

+0

爲什麼'counter'沒有計算'0'? –

+0

因爲從時間戳到時間戳的轉換始終關閉一微秒。錯誤不捨入錯誤! – nigel222

1

很少有小數的浮點數可以精確地表示爲二進制浮點數;通常會有一些非常小的錯誤。有時它會比想要的數字小,有時它會更大,但它應該總是非常接近。你的例子的確切值是1391147980.0019989013671875,它在你指定的0.1微秒內。

從浮點數timestamp回到datetime的轉換應使用舍入來確保往返轉換提供與原始值相同的值。正如J.F. Sebastian所指出的那樣,這是針對Python 3.4的一個錯誤;它聲稱在以後的版本中得到修復,但它仍然存在於Python 3.5.0中,使用與問題中給出的值相同的值。運行類似於nigel222的測試顯示精確匹配和低1微秒的結果之間幾乎爲50/50的分割。

既然您知道原始值是微秒的整數,您可以添加一個偏移量,以確保二進制浮點值始終高於十進制值,同時仍然足夠小以至於不會影響結果當正確舍入時。由於舍入應該在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} 
+0

它*是*在Python 3.5(目前在開發版本3.5,未來的'3.5.1')中修復的。我的答案明確地說「**下一個** 3 ...發佈」 – jfs

+0

注意:Python 3.4+中的固定版本使用ROUND_HALF_EVEN模式(默認用於浮點數和round()方法)。 '+ 0.00000025'在Python版本中沒有bug(和Python 2.7)時會將它分開:'datetime.utcfromtimestamp(-1.5/1000000 + 0.00000025)!= datetime.utcfromtimestamp(-1.5/1000000)'。 – jfs

+0

@ J.F.Sebastian我剛剛推出了今晚Python 2的最新二進制版本,所以這就是我測試的目的。我從來沒有考慮負面時間戳,感謝這個例子。 Python 2.7沒有'timestamp'方法,所以這裏沒有問題。 –

相關問題