Datetime hell. Timezone aware to UNIX timestamp.
Rating +1 for datetime in the category crapiest module in the Python 2.x Standard Library. The fact that datetimes in Python are time zone unaware by default, even if you parse a datetime from a string with time zone in it, sucks.
What I tried to achieve today was, I wanted to parse a ISO date like 2013-06-05T15:19:10Z and convert it to UNIX timestamp. You say simple? I thought so, too! FYI my local time zone is CEST (+0200).
First try
I skipped the first try, because I already knew this will fail, but it could be the first try for someone who does not.
from datetime import datetime
if __name__ == '__main__':
iso_string = '2013-06-05T15:19:10Z'
datetime_obj = datetime.strptime(
iso_string, '%Y-%m-%dT%H:%M:%S%Z'
)
print datetime_obj.strftime('%s')
Result
ValueError: time data '2013-06-05T15:19:10Z' does not match format '%Y-%m-%dT%H:%M:%S%Z'
The datetime doc says:
%Z Time zone name (empty string if the object is naive).
But Z as in Zulu is not supported. Even if it would be, parsing the time zone from a string, never ever results in a time zone aware datetime object, because:
datetime.strptime(date_string, format)
is equivalent to datetime(*(time.strptime(date_string, format)[0:6]))
.
Noticed the [0:6]
? Yeah …
Second try
from datetime import datetime
if __name__ == '__main__':
iso_string = '2013-06-05T15:19:10Z'
datetime_obj = datetime.strptime(
iso_string, '%Y-%m-%dT%H:%M:%SZ'
)
print datetime_obj.strftime('%s')
Result
1370438350
I thought, since datetime is time zone unaware, it maybe handles everything as UTC? So this should work. But …
>>> datetime.utcfromtimestamp(1370438350).isoformat()
'2013-06-05T13:19:10'
… it does not. 2 hours short! So a datetime object is aware of a time zone, my local system time zone, or at least something behind the scenes is.
Third try
I need to make the datetime object aware of the time zone. If the object already knows it’s in UTC, everything should work out fine. Btw the datetime module comes with a tzinfo base class for time zone classes, but not with a single implemented time zone. If you don’t like to implement them yourself use pytz.
from datetime import datetime, tzinfo, timedelta
class UTC(tzinfo):
"""UTC"""
def utcoffset(self, dt):
return timedelta(0)
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return timedelta(0)
if __name__ == '__main__':
iso_string = '2013-06-05T15:19:10Z'
datetime_obj = datetime.strptime(
iso_string, '%Y-%m-%dT%H:%M:%SZ'
)
datetime_obj = datetime_obj.replace(tzinfo=UTC())
timestamp = datetime_obj.strftime('%s')
print timestamp
print '-> %s' % datetime\
.utcfromtimestamp(int(timestamp)).isoformat()
Result
1370441950
-> 2013-06-05T14:19:10
Closer, but still 1 hour missing. Where is this hour gone? I don’t know! It’s gone!
Fourth try
I figure, since the strptime()
method is wack, his homey strftime()
maybe is too?! There is an other way to create a UNIX timestamp from a datetime object.
from datetime import datetime, tzinfo, timedelta
from time import mktime
class UTC(tzinfo):
"""UTC"""
def utcoffset(self, dt):
return timedelta(0)
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return timedelta(0)
if __name__ == '__main__':
iso_string = '2013-06-05T15:19:10Z'
datetime_obj = datetime.strptime(
iso_string, '%Y-%m-%dT%H:%M:%SZ'
)
datetime_obj = datetime_obj.replace(tzinfo=UTC())
timestamp = mktime(datetime_obj.timetuple())
print timestamp
print '-> %s' % datetime\
.utcfromtimestamp(timestamp).isoformat()
Result
1370441950.0
-> 2013-06-05T14:19:10
Nope. Tried again using utctimetuple() instead of timetuple(). But no …
1370441950.0
-> 2013-06-05T14:19:10
Fifth try
Alright then, I only got one way creating an UNIX timestamp from a datetime object left.
from datetime import datetime, tzinfo, timedelta
from time import mktime
from calendar import timegm
class UTC(tzinfo):
"""UTC"""
def utcoffset(self, dt):
return timedelta(0)
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return timedelta(0)
if __name__ == '__main__':
iso_string = '2013-06-05T15:19:10Z'
datetime_obj = datetime.strptime(
iso_string, '%Y-%m-%dT%H:%M:%SZ'
)
datetime_obj = datetime_obj.replace(tzinfo=UTC())
timestamp = timegm(datetime_obj.timetuple())
print timestamp
print '-> %s' % datetime\
.utcfromtimestamp(timestamp).isoformat()
Result
1370445550
-> 2013-06-05T15:19:10
Wohoooo! I did it! I successfully converted a datetime ISO string to a fully working, down to the last second correct, UNIX timestamp. So sad … let the flaming begin.
Suggestion by Jeff Epler
import time
from calendar import timegm
from datetime import datetime
if __name__ == '__main__':
iso_string = '2013-06-05T15:19:10Z'
timestamp = timegm(
time.strptime(
iso_string.replace('Z', 'GMT'),
'%Y-%m-%dT%H:%M:%S%Z'
)
)
print timestamp
print '-> %s' % datetime.utcfromtimestamp(timestamp).isoformat()
Result
1370445550
-> 2013-06-05T15:19:10
Thanks Jeff, this one is much better!