In [1]:
%load_ext autoreload
%autoreload 2
In [2]:
import pytz
In [3]:
from dateutil.tz import gettz, tzutc
from dateutil import tz
from datetime import datetime, timedelta, tzinfo
In [4]:
from dateutil.tz import gettz
from dateutil.tz import tzutc, tzoffset, tzlocal
from dateutil.tz import tzstr, tzrange, tzfile
from dateutil import relativedelta as rd
In [5]:
from helper_functions import print_tzinfo
from helper_functions import print_dt_eq
In [6]:
def add_absolute(dt, td):
if dt.tzinfo is None:
return dt + td
dtu = dt.astimezone(tz.tzutc())
return (dtu + td).astimezone(dt.tzinfo)
In [7]:
NYC = gettz('America/New_York')
CHI = gettz('America/Chicago')
UTC = tzutc()
CST
is a highly-context-dependent abbreviation:UTC-6
)UTC-5
)UTC+8
)
In [8]:
dt_before = datetime(1995, 1, 1, 23, 59, tzinfo=tz.gettz('Pacific/Kiritimati'))
dt_after = add_absolute(dt_before, timedelta(minutes=2))
print(dt_before)
print(dt_after)
Also Samoa on January 29, 2011.
In [9]:
dt_before = datetime(1969, 9, 30, 11, 59, tzinfo=tz.gettz('Pacific/Kwajalein'))
dt_after = add_absolute(dt_before, timedelta(minutes=2))
print(dt_before)
print(dt_after)
In [10]:
from dateutil import rrule as rr
# Close of business in New York on weekdays
closing_times = rr.rrule(freq=rr.DAILY, byweekday=(rr.MO, rr.TU, rr.WE, rr.TH, rr.FR),
byhour=17, dtstart=datetime(2017, 3, 9, 17), count=5)
for dt in closing_times:
print(dt.replace(tzinfo=NYC))
In [11]:
for dt in closing_times:
print(dt.replace(tzinfo=NYC).astimezone(UTC))
tzinfo
tzinfo
.Information provided is a function of the datetime:
tzname
: The (usually abbreviated) name of the time zone at the given datetimeutcoffset
: The offset from UTC at the given datetimedst
: The size of the datetime's DST offset (usually 0 or 1 hour)
In [12]:
class ET(tzinfo):
def utcoffset(self, dt):
if self.isdaylight(dt):
return timedelta(hours=-4)
else:
return timedelta(hours=-5)
def dst(self, dt):
if self.isdaylight(dt):
return timedelta(hours=1)
else:
return timedelta(hours=0)
def tzname(self, dt):
return "EDT" if self.isdaylight(dt) else "EST"
def isdaylight(self, dt):
dst_start = datetime(dt.year, 1, 1) + rd.relativedelta(month=3, weekday=rd.SU(+2), hour=2)
dst_end = datetime(dt.year, 1, 1) + rd.relativedelta(month=11, weekday=rd.SU, hour=2)
return dst_start <= dt.replace(tzinfo=None) < dst_end
print(datetime(2017, 11, 4, 12, 0, tzinfo=ET()))
print(datetime(2017, 11, 5, 12, 0, tzinfo=ET()))
In [13]:
dt_before_utc = datetime(2017, 11, 5, 0, 30, tzinfo=ET()).astimezone(tz.tzutc())
dt_during = (dt_before_utc + timedelta(hours=1)).astimezone(ET()) # 1:30 EST
dt_after = (dt_before_utc + timedelta(hours=2)).astimezone(ET()) # 1:30 EDT
print(dt_during) # Lookin good!
print(dt_after) # OH NO!
In [14]:
dt1 = datetime(2004, 10, 31, 4, 30, tzinfo=UTC)
for i in range(4):
dt = (dt1 + timedelta(hours=i)).astimezone(NYC)
print('{} | {} | {}'.format(dt, dt.tzname(),
'Ambiguous' if tz.datetime_ambiguous(dt) else 'Unambiguous'))
Whether you are on the fold side is a property of the datetime:
In [15]:
print_tzinfo(datetime(2004, 10, 31, 1, 30, tzinfo=NYC)) # fold=0
print_tzinfo(datetime(2004, 10, 31, 1, 30, fold=1, tzinfo=NYC))
Note: fold=1
represents the second instance of an ambiguous datetime
In [16]:
dt1 = datetime(2004, 10, 30, 12, 0); dt1a = datetime(2004, 10, 31, 1, 30)
dt2 = datetime(2004, 10, 30, 12, 0); dt2a = datetime(2004, 10, 31, 1, 30)
dt3 = datetime(2004, 10, 30, 11, 0); dt3a = datetime(2004, 10, 31, 2, 30) # Unambiguous
In [17]:
print_dt_eq(dt1.replace(tzinfo=NYC), dt2.replace(tzinfo=NYC)) # Unambiguous
print_dt_eq(dt1.replace(tzinfo=NYC), dt3.replace(tzinfo=NYC))
In [18]:
print_dt_eq(dt1a.replace(tzinfo=NYC), dt2a.replace(tzinfo=NYC)) # Ambiguous
print_dt_eq(dt1a.replace(tzinfo=NYC), dt2a.replace(fold=1, tzinfo=NYC), bold=True)
print_dt_eq(dt1a.replace(tzinfo=NYC), dt3a.replace(tzinfo=NYC))
In [19]:
print_dt_eq(dt1.replace(tzinfo=NYC), dt2.replace(tzinfo=CHI)) # Unambiguous
print_dt_eq(dt1.replace(tzinfo=NYC), dt3.replace(tzinfo=CHI))
If either datetime is ambiguous, the result is always False
:
In [20]:
print_dt_eq(dt1a.replace(fold=1, tzinfo=NYC), dt3a.replace(tzinfo=CHI), bold=True)
In [21]:
LON = gettz('Europe/London')
x = datetime(2007, 3, 25, 1, 0, tzinfo=LON)
ts = x.timestamp()
y = datetime.fromtimestamp(ts, LON)
z = datetime.fromtimestamp(ts, gettz('Europe/London'))
In [22]:
x == y
Out[22]:
In [23]:
x == z
Out[23]:
In [24]:
y == z
Out[24]:
In [25]:
dt1 = datetime(2004, 4, 4, 6, 30, tzinfo=UTC)
for i in range(3):
dt = (dt1 + timedelta(hours=i)).astimezone(NYC)
print('{} | {} '.format(dt, dt.tzname()))
In [26]:
print(datetime(2007, 3, 25, 1, 0, tzinfo=LON))
In [27]:
print(datetime(2007, 3, 25, 0, 0, tzinfo=UTC).astimezone(LON))
print(datetime(2007, 3, 25, 1, 0, tzinfo=UTC).astimezone(LON))
In [28]:
LON = gettz('Europe/London')
x = datetime(2007, 3, 25, 1, 0, tzinfo=LON)
ts = x.timestamp()
y = datetime.fromtimestamp(ts, LON)
z = datetime.fromtimestamp(ts, gettz('Europe/London'))
In [29]:
print('x (LON): {}'.format(x))
print('x (UTC): {}'.format(x.astimezone(UTC)))
print('x (LON->UTC->LON): {}'.format(x.astimezone(UTC).astimezone(LON)))
In [30]:
print('y: {}'.format(y))
print('z: {}'.format(z))
In [31]:
print('x: {}'.format(x))
print('y: {}'.format(y))
print('z: {}'.format(z))
In [32]:
x.tzinfo is y.tzinfo
Out[32]:
In [33]:
x.tzinfo is z.tzinfo
Out[33]:
In [34]:
dt = datetime(2017, 8, 11, 14, tzinfo=tz.gettz('US/Pacific'))
print_tzinfo(dt)
If you have a naive wall time, or a wall time in another zone that you want to translate without shifting the offset, use datetime.replace
:
In [35]:
print_tzinfo(dt.replace(tzinfo=tz.gettz('US/Eastern')))
If you have an absolute time, in UTC or otherwise, use datetime.astimezone()
:
In [36]:
print_tzinfo(dt.astimezone(tz.gettz('US/Eastern')))
In [37]:
print_tzinfo(dt.astimezone(pytz.timezone('US/Eastern')))
But the constructor or .replace
methods fail horribly:
In [38]:
print_tzinfo(dt.replace(tzinfo=pytz.timezone('US/Eastern')))
In [39]:
LOS_p = pytz.timezone('America/Los_Angeles')
dt = LOS_p.localize(datetime(2017, 8, 11, 14, 0))
print_tzinfo(dt)
normalize()
datetimes after you've done some arithmetic on them:
In [40]:
dt_add = dt + timedelta(days=180)
print_tzinfo(dt_add)
In [41]:
print_tzinfo(LOS_p.normalize(dt_add))
In [42]:
dt1 = datetime(2004, 10, 31, 6, 30, tzinfo=UTC) # This is in the fold in EST
dt_dateutil = dt1.astimezone(tz.gettz('US/Eastern'))
dt_pytz = dt1.astimezone(pytz.timezone('US/Eastern'))
print(repr(dt_dateutil))
print_tzinfo(dt_dateutil)
In [43]:
print(repr(dt_pytz)) # Note that pytz doesn't set fold
print_tzinfo(dt_pytz)
For backwards compatibility, dateutil
provides a tz.enfold
method to add a fold
attribute if necessary:
In [44]:
dt = datetime(2004, 10, 31, 1, 30, tzinfo=tz.gettz('US/Eastern'))
tz.enfold(dt)
Out[44]:
Python 2.7.12
Type "help", "copyright", "credits" or "license" for more information.
>>> from datetime import datetime
>>> from dateutil import tz
>>> dt = datetime(2004, 10, 31, 1, 30, tzinfo=tz.gettz('US/Eastern'))
>>> tz.enfold(dt)
_DatetimeWithFold(2004, 10, 31, 1, 30, tzinfo=tzfile('/usr/share/zoneinfo/US/Eastern'))
>>> tz.enfold(dt).tzname()
'EST'
>>> dt.tzname()
'EDT'
In [45]:
tz.datetime_ambiguous(datetime(2004, 10, 31, 1, 30, tzinfo=NYC))
Out[45]:
In [46]:
tz.datetime_ambiguous(datetime(2004, 10, 31, 1, 30), NYC)
Out[46]:
In [47]:
dt_0 = datetime(2004, 10, 31, 0, 30, tzinfo=NYC)
for i in range(3):
dt_i = dt_0 + timedelta(hours=i)
dt_i = tz.enfold(dt_i, tz.datetime_ambiguous(dt_i))
print('{} (fold={})'.format(dt_i, dt_i.fold))
Note: fold
is ignored when the datetime
is not ambiguous:
In [48]:
for i in range(3):
dt_i = tz.enfold(dt_0 + timedelta(hours=i), fold=1)
print('{} (fold={})'.format(dt_i, dt_i.fold))
In [49]:
NYC_pytz = pytz.timezone('America/New_York')
In [50]:
dt_pytz = NYC_pytz.localize(datetime(2004, 10, 31, 1, 30))
print_tzinfo(dt_pytz)
To get a time zone in daylight time, pass is_dst=True
to localize
:
In [51]:
dt_pytz = NYC_pytz.localize(datetime(2004, 10, 31, 1, 30), is_dst=True)
print_tzinfo(dt_pytz)
If is_dst=None
is passed to localize
, pytz
raises an AmbiguousTimeError
:
In [52]:
for hour in (0, 1):
dt = datetime(2004, 10, 31, hour, 30)
try:
NYC_pytz.localize(dt, is_dst=None)
print('{} | {}'.format(dt, "Unambiguous"))
except pytz.AmbiguousTimeError:
print('{} | {}'.format(dt, "Ambiguous"))
dateutil
provides a tz.datetime_exists()
function to tell you whether you've constructed an imaginary datetime
:
In [53]:
dt_0 = datetime(2004, 4, 4, 1, 30, tzinfo=NYC)
for i in range(3):
dt = dt_0 + timedelta(hours=i)
print('{} ({})'.format(dt, 'Exists' if tz.datetime_exists(dt) else 'Imaginary'))
Generally for imaginary datetimes, you either want to skip over them or "slide forward":
In [54]:
def resolve_imaginary(dt): # This is a planned feature in dateutil 2.7.0
if dt.tzinfo is not None and not tz.datetime_exists(dt):
curr_offset = dt.utcoffset()
old_offset = (dt - timedelta(hours=24)).utcoffset()
dt += curr_offset - old_offset
return dt
print(resolve_imaginary(datetime(2004, 4, 4, 2, 30, tzinfo=NYC)))
When using localize
on an imaginary datetime, pytz
will create an imaginary time and use is_dst
to decide what offset to assign it:
In [55]:
print(NYC_pytz.localize(datetime(2004, 4, 4, 2, 30), is_dst=True))
print(NYC_pytz.localize(datetime(2004, 4, 4, 2, 30), is_dst=False))
If you have a non-existent date, normalize()
will slide it forward or backwards, depending on the value passed to is_dst
(default is False
):
In [56]:
dt_imag_dst = NYC_pytz.localize(datetime(2004, 4, 4, 2, 30), is_dst=True)
dt_imag_std = NYC_pytz.localize(datetime(2004, 4, 4, 2, 30), is_dst=False)
print(NYC_pytz.normalize(dt_imag_dst))
print(NYC_pytz.normalize(dt_imag_std))
If you pass is_dst=None
, pytz
will throw a NonExistentTimeError
:
In [57]:
dt_0 = datetime(2004, 4, 4, 1, 30)
for i in range(3):
try:
dt = NYC_pytz.localize(dt_0 + timedelta(hours=i), is_dst=None)
exists = True
except pytz.NonExistentTimeError:
exists = False
print('{} ({})'.format(dt, 'Exists' if exists else 'Imaginary'))
dateutil.tz.datetime_exists()
works with pytz
zones, too
In [58]:
dt_pytz_real = NYC_pytz.localize(datetime(2004, 4, 4, 1, 30))
dt_pytz_imag = NYC_pytz.localize(datetime(2004, 4, 4, 2, 30))
print('Real: {}'.format(tz.datetime_exists(dt_pytz_real)))
print('Imaginary: {}'.format(tz.datetime_exists(dt_pytz_imag)))
And will detect non-normalized datetimes
:
In [59]:
dt_nn = dt_pytz_real + timedelta(hours=3) # Needs to be normalized to DST
print('{}: {}'.format(dt_nn, 'Exists' if tz.datetime_exists(dt_nn) else 'Imaginary'))
In [60]:
# tz.tzutc() is equivalent to pytz.UTC or timezone.utc
dt = datetime(2014, 12, 19, 22, 30, tzinfo=tz.tzutc())
print_tzinfo(dt)
Static offsets represent zones with a fixed offset from UTC, and takes a tzname or either number of seconds or a timedelta
:
In [61]:
JST = tzoffset('JST', 32400) # Japan Standard Time is year round
IST = tzoffset('IST', # India Standard Time is year round
timedelta(hours=5, minutes=30))
EST = tzoffset(None, timedelta(hours=-5)) # Can use None as a name
dt = datetime(2016, 7, 17, 12, 15, tzinfo=tzutc())
print_tzinfo(dt.astimezone(JST))
print_tzinfo(dt.astimezone(IST))
print_tzinfo(dt.astimezone(EST))
In Python 3.2, timezone
objects were introduced to provide ready-made tzinfo
subclasses for the simple case of static offsets from UTC.
In [62]:
from datetime import timezone
dt = datetime(2014, 12, 19, 22, 30, tzinfo=timezone.utc) # Equivalent to pytz.UTC or dateutil.tz.tzutc()
print_tzinfo(dt)
In [63]:
JST = timezone(timedelta(hours=9), 'JST') # Japan Standard Time is year round
IST = timezone(timedelta(hours=5, minutes=30), # India Standard Time is year round
'IST')
EST = timezone(timedelta(hours=-5)) # Without a name, it's UTC-hh:mm
dt = datetime(2016, 7, 17, 12, 15, tzinfo=tzutc())
print_tzinfo(dt.astimezone(JST)); print()
print_tzinfo(dt.astimezone(IST)); print()
print_tzinfo(dt.astimezone(EST))
The tz.tzlocal()
class is a tzinfo
implementation that uses the OS hooks in Python's time
module to get the local system time.
In [64]:
# Temporarily changes the TZ file on *nix systems.
from helper_functions import TZEnvContext
print_tzinfo(dt.astimezone(tz.tzlocal()))
In [65]:
with TZEnvContext('UTC'):
print_tzinfo(dt.astimezone(tz.tzlocal()))
In [66]:
with TZEnvContext('PST8PDT'):
print_tzinfo((dt + timedelta(days=180)).astimezone(tz.tzlocal()))
tz.win.tzwinlocal()
directly queries the Windows registry for its time zone data and uses that to construct a tzinfo
.
Fixes this bug:
>>> dt = datetime(2014, 2, 11, 17, 0)
>>> print(dt.replace(tzinfo=tz.tzlocal()).tzname())
Eastern Standard Time
>>> print(dt.replace(tzinfo=tz.win.tzwinlocal()).tzname())
Eastern Standard Time
>>> with TZWinContext('Pacific Standard Time'):
... print(dt.replace(tzinfo=tz.tzlocal()).tzname())
... print(dt.replace(tzinfo=tz.win.tzwinlocal()).tzname())
Eastern Standard Time
Pacific Standard Time
The dateutil.tz.tzfile
class provides support for IANA zoneinfo
binaries (shipped with *nix systems).
DO NOT USE tz.tzfile
directly - use tz.gettz()
In [67]:
NYC = tz.gettz('America/New_York')
NYC
Out[67]:
The IANA database contains historical time zone transitions:
In [68]:
print_tzinfo(datetime(2017, 8, 12, 14, tzinfo=NYC)) # Eastern Daylight Time
In [69]:
print_tzinfo(datetime(1944, 1, 6, 12, 15, tzinfo=NYC)) # Eastern War Time
In [70]:
print_tzinfo(datetime(1901, 9, 6, 16, 7, tzinfo=NYC)) # Local solar mean
The most general way to get a time zone is to pass the relevant timezone string to the gettz() function, which will try parsing it a number of different ways until it finds a relevant string.
In [71]:
tz.gettz() # Passing nothing gives you local time
Out[71]:
In [72]:
# If your TZSTR is an an Olson file, it is prioritized over the /etc/localtime tzfile.
with TZEnvContext('CST6CDT'):
print(gettz())
In [73]:
# If it doesn't find a tzfile, but it finds a valid abbreviation for the local zone,
# it returns tzlocal()
with TZEnvContext('LMT4'):
print(gettz('LMT'))
In [74]:
# Retrieve IANA zone:
print(gettz('Pacific/Kiritimati'))
In [75]:
# Directly parse a TZ variable:
print(gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3'))