Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting datetimes to timestamps and back again

I'm having some trouble with datetime in Python. I tried converting a datetime to a timestamp and then back again and no matter how I try the end result is not the same. I always end up with a datetime of 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)
like image 281
kjonsson Avatar asked Mar 15 '23 16:03

kjonsson


2 Answers

It is a known 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)

Note: the microsecond is gone. The .timestamp() already returns result that is slightly less than 1999 microseconds:

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

To workaround the issue, you could use the explicit formula:

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

Note: the input in all examples is the local time but the result is UTC time in the last one.

like image 133
jfs Avatar answered Mar 19 '23 23:03

jfs


That last number is microseconds ... are the internals that accurate? Let's find out.

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

I do believe you have found an off-by-one-microsecond error in the datetime library, unless there is something perverse buried in the specifications.

(I was expecting a spread around zero, reflecting rounding errors of some sort)

like image 39
nigel222 Avatar answered Mar 20 '23 00:03

nigel222