I always get confused with datetimes
and timezone conversion in Python, but now I'm experiencing a rather odd behavior. I suspect (strongly) that this is related to Daylight Saving Times, but I don't know for sure, and I definitely don't know how to handle it correctly.
Here's what happens: If I make a datetime
instance aware of its timezone, I create an epoch
UTC timestamp from it, and I re-create a datetime
instance back from that timestamp, I seem to be gaining one hour:
>>> import pytz
>>> import datetime
>>> import time
>>>
>>> naive = datetime.datetime.now()
>>> print "Naive 'now' %s" % naive
Naive 'now' 2014-08-21 11:19:13.019046
>>> eastern_tz = pytz.timezone('US/Eastern')
>>> now_eastern = eastern_tz.localize(naive)
>>> print "Now (eastern) is %s" % now_eastern
Now (eastern) is 2014-08-21 11:19:13.019046-04:00
>>> print "Now (utc) is %s" % now_eastern.astimezone(pytz.utc)
Now (utc) is 2014-08-21 15:19:13.019046+00:00
# This one is correct
>>> now_eastern_utc_timetuple = now_eastern.utctimetuple()
>>> print "Now (eastern) as timetuple %s" % now_eastern_utc_timetuple
Now (eastern) as timetuple time.struct_time(tm_year=2014, tm_mon=8, tm_mday=21, \
tm_hour=15, tm_min=19, tm_sec=13, tm_wday=3, \
tm_yday=233, tm_isdst=0)
# Shouldn't this be isdst=1 ? ----------^^^
>>> now_epoch = time.mktime(now_eastern_utc_timetuple)
>>> print "now epoch (UTC) %s" % now_epoch
now epoch (UTC) 1408652353.0
# I'm pretty sure this is +3600 in advance
>>> print "Converted back: %s" % datetime.datetime.fromtimestamp(now_epoch)
Converted back: 2014-08-21 16:19:13
I've verified the times using epochconverter.com, and I'm pretty sure the timestamp generated from the utctimetuple
is adding one hour. As I mentioned, I'm almost certain that this is related to Daylight Time Saving unawareness, because if I try with a date when the daylight time savings is not in use (for instance, December), it works fine.
>>> naive = datetime.datetime.strptime('2012/12/12 10:00', '%Y/%m/%d %H:%M')
>>> print "Naive 'now' %s" % naive
Naive 'now' 2012-12-12 10:00:00
>>> eastern_tz = pytz.timezone('US/Eastern')
>>> now_eastern = eastern_tz.localize(naive)
>>> print "Now (eastern) is %s" % now_eastern
Now (eastern) is 2012-12-12 10:00:00-05:00
>>> print "Now (utc) is %s" % now_eastern.astimezone(pytz.utc)
Now (utc) is 2012-12-12 15:00:00+00:00
>>> now_eastern_utc_timetuple = now_eastern.utctimetuple()
>>> print "Now (eastern) as timetuple %s" % now_eastern_utc_timetuple
Now (eastern) as timetuple time.struct_time(tm_year=2012, tm_mon=12, tm_mday=12,\
tm_hour=15, tm_min=0, tm_sec=0, tm_wday=2, \
tm_yday=347, tm_isdst=0)
>>> now_epoch = time.mktime(now_eastern_utc_timetuple)
>>> print "now epoch (UTC) %s" % now_epoch
now epoch (UTC) 1355342400.0
>>> print "Converted back: %s" % datetime.datetime.fromtimestamp(now_epoch)
Converted back: 2012-12-12 15:00:00
I'm using:
So the question is: How can I handle this kind of conversion correctly? Is this issue related to DST?
Thank you in advance.
Shouldn't this be isdst=1 ?
No. You ask an aware datetime object to give you a UTC time tuple. UTC has no DST therefore tm_isdst
is always zero.
now_eastern.astimezone(pytz.utc)
and now_eastern.utctimetuple()
produce the same time. They are correct if now_eastern
is correct.
time.mktime(now_eastern_utc_timetuple)
is incorrect because mktime()
expects local time but now_eastern_utc_timetuple
is in UTC. You could use calendar.timegm(now_eastern_utc_timetuple)
instead or better now_eastern.timestamp()
, see Converting datetime.date to UTC timestamp in Python.
I would use: now_eastern = eastern_tz.localize(naive, is_dst=None)
(assuming naive
represents time in 'US/Eastern'
i.e., assuming your local timezone is 'US/Eastern'
, you could use tzlocal.get_localzone()
to get the local timezone automatically) <- raise an exception instead of returning a wrong answer or better: now_eastern = datetime.now(eastern)
.