2015-09-24 2 views
4

У меня возникли проблемы с datetime в Python. Я попытался преобразовать дату и время в метку времени, а затем вернуться назад, и как бы я ни старался, конечный результат - это не то же самое. Я всегда получаю datetime 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

Возможно, вы также можете добавить '0.0000004' в' timestamp'. –

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

Я верю, что вы нашли ошибку от за одним-микросекунд в библиотеке даты и времени, если есть что-то извращенное похоронен в спецификациях.

(я ожидал распространение около нуля, отражая ошибки округления какие-то)

+0

Почему 'counter' не имеет счетчика для' 0'? –

+0

Поскольку преобразование в метку времени и от нее постоянно отключается на одну микросекунду. Ошибка не округления! – nigel222

1

Очень мало чисел с плавающей запятой с десятичной дробью может быть точно представлено как двоичное число с плавающей запятой; обычно будет небольшая ошибка. Иногда это будет меньше желаемого числа, а иногда оно будет больше, но оно всегда должно быть очень близко. Точное значение вашего примера - 1391147980.0019989013671875, которое находится в пределах 0,1 микросекунды того, что вы указали.

Преобразование из плавающей запятой timestamp назад в datetime следует использовать округление, чтобы убедиться, что преобразование в обратном направлении дает то же значение, что и оригинал. Как отмечено J.F. Sebastian, это было введено как ошибка с Python 3.4; он, как утверждается, фиксируется в последующих выпусках, но он все еще существует в Python 3.5.0, используя те же значения, что указаны в вопросе. Выполнение теста, аналогичного nigel222, показывает почти 50/50 разделение между точными совпадениями и результатами, которые являются низкими на 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} 
+0

это * исправлено в Python 3.5 (текущий в версии для разработки 3.5, будущий '3.5.1'). В моем ответе прямо сказано «** следующее ** 3 ... релизы» – jfs

+0

примечание: исправленная версия в Python 3.4+ использует режим ROUND_HALF_EVEN' (по умолчанию для чисел с плавающей запятой и метод «round()»). '+ 0.00000025' ломает его:' datetime.utcfromtimestamp (-1.5/1000000 + 0.00000025)! = Datetime.utcfromtimestamp (-1.5/1000000) 'в версиях Python без ошибки (и Python 2.7). – jfs

+0

@ J.F.Sebastian Я только что снял последнюю бинарную сборку Python 3 сегодня, так что это то, что я тестировал. Спасибо за этот пример. Python 2.7 не имеет метода «timestamp», поэтому проблем там нет. –

Смежные вопросы