Pertanyaan Konversi datetimes ke cap waktu dan kembali lagi


Saya mengalami masalah dengan datetime dengan Python. Saya mencoba mengubah datetime menjadi stempel waktu dan kemudian kembali lagi dan tidak peduli bagaimana saya mencoba hasil akhirnya tidak sama. Saya selalu berakhir dengan 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)

4
2017-09-24 17:24


asal


Jawaban:


Itu diketahui Python 3.4 masalah:

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

Catatan: mikrodetik hilang. Itu .timestamp() sudah mengembalikan hasil yang sedikit kurang dari 1999 mikrodetik:

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

Pembulatan tetap dalam 3,4, 3,5, 3,6 rilis berikutnya:

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

Untuk mengatasi masalah ini, Anda bisa menggunakan rumus eksplisit:

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

Catatan: input dalam semua contoh adalah waktu lokal tetapi hasilnya adalah waktu UTC dalam yang terakhir.


3
2017-09-24 21:44



Angka terakhir itu mikrodetik ... apakah internal itu akurat? Mari kita cari tahu.

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

Saya yakin Anda telah menemukan kesalahan off-by-one-microsecond di pustaka datetime, kecuali ada sesuatu yang buruk terkubur dalam spesifikasi.

(Saya mengharapkan penyebaran sekitar nol, yang mencerminkan kesalahan pembulatan dari beberapa macam)


3
2017-09-24 18:29



Sangat sedikit angka floating point dengan pecahan desimal dapat persis diwakili sebagai angka floating point biner; biasanya akan ada kesalahan yang sangat kecil. Kadang-kadang akan lebih kecil dari jumlah yang diinginkan, dan kadang-kadang akan lebih besar, tetapi harus selalu sangat dekat. Nilai persis dari contoh Anda adalah 1391147980.0019989013671875 yang berada dalam 0,1 mikrodetik dari apa yang Anda tentukan.

Konversi dari floating point timestamp kembali ke a datetime harus menggunakan pembulatan untuk memastikan konversi round-trip memberikan nilai yang sama seperti aslinya. Seperti dicatat oleh J.F. Sebastian, ini dimasukkan sebagai bug terhadap Python 3.4; itu diklaim diperbaiki dalam rilis nanti, tetapi masih ada di Python 3.5.0 menggunakan nilai yang sama seperti yang diberikan dalam pertanyaan. Menjalankan tes serupa dengan nigel222 menunjukkan perpisahan hampir 50/50 antara pencocokan tepat dan hasil yang rendah dengan 1 mikrodetik.

Karena Anda tahu bahwa nilai asli adalah jumlah integral mikrodetik, Anda dapat menambahkan offset yang memastikan nilai poin mengambang biner selalu lebih tinggi daripada nilai desimal sementara masih cukup kecil sehingga tidak mempengaruhi hasil ketika dibulatkan dengan benar. Karena pembulatan harus terjadi pada 0,5 mikrodetik offset ideal akan menjadi setengah dari itu, atau 0,25 mikrodetik.

Berikut hasilnya dalam 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}

1
2017-09-25 04:19