28

I'm trying to write a pair of functions, dt and ut, that convert back and forth between normal unix time (seconds since 1970-01-01 00:00:00 UTC) and a Python datetime object.

If dt and ut were proper inverses then this code would print the same timestamp twice:

import time, datetime

# Convert a unix time u to a datetime object d, and vice versa
def dt(u): return datetime.datetime.fromtimestamp(u)
def ut(d): return time.mktime(d.timetuple())

u = 1004260000
print u, "-->", ut(dt(u))

Alas, the second timestamp is 3600 seconds (an hour) less than the first. I think this only happens for very particular unixtimes, maybe during that hour that daylight savings time skips over. But is there a way to write dt and ut so they're true inverses of each other?

Related question: Making matplotlib's date2num and num2date perfect inverses

Community
  • 1
  • 1
dreeves
  • 26,430
  • 45
  • 154
  • 229
  • btw, if function does nothing but calls another function with the same arguments then you could just assign it: `dt = datetime.datetime.utcfromtimestamp`. Functions are first-class citizens in Python: you can pass them as parameters to another functions, return from functions, etc. – jfs Nov 07 '12 at 00:23
  • Ah, nice, great point! Is it more efficient to do so or just a matter of succinctness? What about `def f(x): return foo(x)` vs `f = lambda x: foo(x)`? (My recollection is that those are equivalent, both functionally and efficiency-wise.) – dreeves Nov 07 '12 at 19:51
  • `f = g` means that f,g are two names that refer to the same function. `def` and `lambda` create new function. – jfs Nov 07 '12 at 22:08

1 Answers1

41

You are correct that this behavior is related to daylight savings time. The easiest way to avoid this is to ensure you use a time zone without daylight savings, UTC makes the most sense here.

datetime.datetime.utcfromtimestamp() and calendar.timegm() deal with UTC times, and are exact inverses.

import calendar, datetime

# Convert a unix time u to a datetime object d, and vice versa
def dt(u): return datetime.datetime.utcfromtimestamp(u)
def ut(d): return calendar.timegm(d.timetuple())

Here is a bit of explanation behind why datetime.datetime.fromtimestamp() has an issue with daylight savings time, from the docs:

Return the local date and time corresponding to the POSIX timestamp, such as is returned by time.time(). If optional argument tz is None or not specified, the timestamp is converted to the platform’s local date and time, and the returned datetime object is naive.

The important part here is that you get a naive datetime.datetime object, which means there is no timezone (or daylight savings) information as a part of the object. This means that multiple distinct timestamps can map to the same datetime.datetime object when using fromtimestamp(), if you happen to pick times that fall during the daylight savings time roll back:

>>> datetime.datetime.fromtimestamp(1004260000) 
datetime.datetime(2001, 10, 28, 1, 6, 40)
>>> datetime.datetime.fromtimestamp(1004256400)
datetime.datetime(2001, 10, 28, 1, 6, 40)
Andrew Clark
  • 202,379
  • 35
  • 273
  • 306
  • [`ut = lambda naive_utc_dt: (naive_utc_dt - datetime(1970,1,1)).total_seconds()`](http://stackoverflow.com/a/8778548/4279) – jfs Nov 07 '12 at 00:17
  • @J.F.Sebastian, want to add that as an answer? It sounds like you're right (though I haven't verified your version) and yours should be the accepted answer here. I hate to lead posterity astray, with only a link in the comments pointing to a better way! – dreeves Nov 07 '12 at 19:52
  • @dreeves: follow the link to see more details. timegm()-based solution is ok if you don't need microseconds – jfs Nov 08 '12 at 06:19