By default all datetime
objects are naive. To make them timezone-aware, you must attach a tzinfo
object, which provides the UTC offset and timezone abbreviation as a function of date and time.
Fixed Offset Time Zones
For time zones that are a fixed offset from UTC, in Python 3.2+, the datetime
module provides the timezone
class, a concrete implementation of tzinfo
, which takes a timedelta
and an (optional) name parameter:
from datetime import datetime, timedelta, timezone
JST = timezone(timedelta(hours=+9))
dt = datetime(2015, 1, 1, 12, 0, 0, tzinfo=JST)
print(dt)
# 2015-01-01 12:00:00+09:00
print(dt.tzname())
# UTC+09:00
dt = datetime(2015, 1, 1, 12, 0, 0, tzinfo=timezone(timedelta(hours=9), 'JST'))
print(dt.tzname)
# 'JST'
For Python versions before 3.2, it is necessary to use a third party library, such as dateutil
. dateutil
provides an equivalent class, tzoffset
, which (as of version 2.5.3) takes arguments of the form dateutil.tz.tzoffset(tzname, offset)
, where offset
is specified in seconds:
from datetime import datetime, timedelta
from dateutil import tz
JST = tz.tzoffset('JST', 9 * 3600) # 3600 seconds per hour
dt = datetime(2015, 1, 1, 12, 0, tzinfo=JST)
print(dt)
# 2015-01-01 12:00:00+09:00
print(dt.tzname)
# 'JST'
Zones with daylight savings time
For zones with daylight savings time, python standard libraries do not provide a standard class, so it is necessary to use a third party library. pytz
and dateutil
are popular libraries providing time zone classes.
In addition to static time zones, dateutil
provides time zone classes that use daylight savings time (see the documentation for the tz
module). You can use the tz.gettz()
method to get a time zone object, which can then be passed directly to the datetime
constructor:
from datetime import datetime
from dateutil import tz
local = tz.gettz() # Local time
PT = tz.gettz('US/Pacific') # Pacific time
dt_l = datetime(2015, 1, 1, 12, tzinfo=local) # I am in EST
dt_pst = datetime(2015, 1, 1, 12, tzinfo=PT)
dt_pdt = datetime(2015, 7, 1, 12, tzinfo=PT) # DST is handled automatically
print(dt_l)
# 2015-01-01 12:00:00-05:00
print(dt_pst)
# 2015-01-01 12:00:00-08:00
print(dt_pdt)
# 2015-07-01 12:00:00-07:00
CAUTION: As of version 2.5.3, dateutil
does not handle ambiguous datetimes correctly, and will always default to the later date. There is no way to construct an object with a dateutil
timezone representing, for example 2015-11-01 1:30 EDT-4
, since this is during a daylight savings time transition.
All edge cases are handled properly when using pytz
, but pytz
time zones should not be directly attached to time zones through the constructor. Instead, a pytz
time zone should be attached using the time zone's localize
method:
from datetime import datetime, timedelta
import pytz
PT = pytz.timezone('US/Pacific')
dt_pst = PT.localize(datetime(2015, 1, 1, 12))
dt_pdt = PT.localize(datetime(2015, 11, 1, 0, 30))
print(dt_pst)
# 2015-01-01 12:00:00-08:00
print(dt_pdt)
# 2015-11-01 00:30:00-07:00
Be aware that if you perform datetime arithmetic on a pytz
-aware time zone, you must either perform the calculations in UTC (if you want absolute elapsed time), or you must call normalize()
on the result:
dt_new = dt_pdt + timedelta(hours=3) # This should be 2:30 AM PST
print(dt_new)
# 2015-11-01 03:30:00-07:00
dt_corrected = PT.normalize(dt_new)
print(dt_corrected)
# 2015-11-01 02:30:00-08:00