Python Language Date and Time Constructing timezone-aware datetimes

Help us to keep this website almost Ad Free! It takes only 10 seconds of your time:
> Step 1: Go view our video on YouTube: EF Core Bulk Extensions
> Step 2: And Like the video. BONUS: You can also share it!

Example

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:

Python 3.x3.2
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:

Python 3.x3.2
Python 2.x2.7
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


Got any Python Language Question?