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)

"""
Explanation: Introduction
UTC

Coordinated Universal Time / Temps Universel Coordonné
Also called Greenwich Mean Time (GMT)

Time zones vs. Offsets

UTC-6 is an offset

US/Central is a time zone


CST is a highly-context-dependent abbreviation:

Central Standard Time (UTC-6)
Cuba Standard Time (UTC-5)
China Standard Time (UTC+8)



Complicated time zones
Non-integer offsets
Examples:
- Australia/Adelaide (+09:30)
- Asia/Kathmandu (+05:45)
- Africa/Monrovia (+00:44:30) (Before 1979)
Change of DST status without offset change

Portugal, 1992
WET  (+0 STD) -&gt; WEST (+1 DST)  1992-03-29

WEST (+1 DST) -&gt; CET  (+1 STD)  1992-09-27


Portugal, 1996

CET  (+1 STD) -&gt; WEST (+1 DST)  1996-03-31
WEST (+1 DST) -&gt; WET  (+0 STD)  1996-10-27

Complicated time zones
Zone name change without offset change

Aleutian Islands, 1983:
BST (-11 STD) -> BDT (-10 DST), 1983-04-24
BDT (-10 DST) -> AHST (-10 STD), 1983-10-30
AHST (-10 STD) -> HST (-10 STD), 1983-11-30 (Zone renamed)

More than one DST transition per year

Morroco, 2012
WET  (+0 STD) -&gt; WEST (+1 DST)  2012-04-29
WEST (+1 DST) -&gt; WET  (+0 STD)  2012-07-20
WET  (+0 STD) -&gt; WEST (+1 DST)  2012-08-20
WEST (+1 DST) -&gt; WET  (+0 STD)  2012-09-30



... and Morocco in 2013-present, and Egypt in 2010 and 2014, and Palestine in 2011.
Complicated time zones
Missing days

Christmas Island (Kiritimati), January 2, 1995  (UTC-10 -> UTC+14)
End of explanation
"""


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)

"""
Explanation: Also Samoa on January 29, 2011.
Double days

Kwajalein Atoll, 1969
End of explanation
"""


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))

for dt in closing_times:
    print(dt.replace(tzinfo=NYC).astimezone(UTC))

"""
Explanation: Why do we need to work with time zones at all?
End of explanation
"""


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()))

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!

"""
Explanation: Python's Time Zone Model
tzinfo

Time zones are provided by subclassing tzinfo.

Information provided is a function of the datetime:

tzname: The (usually abbreviated) name of the time zone at the given datetime
utcoffset: The offset from UTC at the given datetime
dst: The size of the datetime's DST offset (usually 0 or 1 hour)



An example tzinfo implementation
End of explanation
"""


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'))

"""
Explanation: Ambiguous times
Ambiguous times are times where the same "wall time" occurs twice, such as during a DST to STD transition.
End of explanation
"""


print_tzinfo(datetime(2004, 10, 31, 1, 30, tzinfo=NYC))          # fold=0
print_tzinfo(datetime(2004, 10, 31, 1, 30, fold=1, tzinfo=NYC))

"""
Explanation: PEP-495: Local Time Disambiguation

First introduced in Python 3.6
Introduces the fold attribute of datetime
Changes to aware datetime comparison around ambiguous times

Whether you are on the fold side is a property of the datetime:
End of explanation
"""


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

"""
Explanation: Note: fold=1 represents the second instance of an ambiguous datetime
Comparing timezone-aware datetimes
End of explanation
"""


print_dt_eq(dt1.replace(tzinfo=NYC), dt2.replace(tzinfo=NYC))   # Unambiguous
print_dt_eq(dt1.replace(tzinfo=NYC), dt3.replace(tzinfo=NYC))

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))

"""
Explanation: Same Zone: Wall clock times are used, offset ignored
End of explanation
"""


print_dt_eq(dt1.replace(tzinfo=NYC), dt2.replace(tzinfo=CHI))    # Unambiguous
print_dt_eq(dt1.replace(tzinfo=NYC), dt3.replace(tzinfo=CHI))

"""
Explanation: Comparing timezone-aware datetimes

Different zones: If both datetimes are unambiguous, the absolute times are compared:
End of explanation
"""


print_dt_eq(dt1a.replace(fold=1, tzinfo=NYC), dt3a.replace(tzinfo=CHI), bold=True)

"""
Explanation: If either datetime is ambiguous, the result is always False:
End of explanation
"""


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'))


x == y


x == z

y == z

"""
Explanation: A curious case...
End of explanation
"""


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()))

print(datetime(2007, 3, 25, 1, 0, tzinfo=LON))

print(datetime(2007, 3, 25, 0, 0, tzinfo=UTC).astimezone(LON))
print(datetime(2007, 3, 25, 1, 0, tzinfo=UTC).astimezone(LON))

"""
Explanation: Imaginary Times
Imaginary times are wall times that don't exist in a given time zone, such as during an STD to DST transition.
End of explanation
"""


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'))

print('x (LON):              {}'.format(x))
print('x (UTC):              {}'.format(x.astimezone(UTC)))
print('x (LON->UTC->LON):    {}'.format(x.astimezone(UTC).astimezone(LON)))


print('y: {}'.format(y))
print('z: {}'.format(z))

print('x: {}'.format(x))
print('y: {}'.format(y))
print('z: {}'.format(z))

x.tzinfo is y.tzinfo

x.tzinfo is z.tzinfo

"""
Explanation: Why it was non-transitive
End of explanation
"""


dt = datetime(2017, 8, 11, 14, tzinfo=tz.gettz('US/Pacific'))
print_tzinfo(dt)

"""
Explanation: Working with time zones
dateutil
In dateutil's suite of tzinfo objects, you can attach time zones in the constructor if you have a wall time:
End of explanation
"""


print_tzinfo(dt.replace(tzinfo=tz.gettz('US/Eastern')))

"""
Explanation: 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:
End of explanation
"""


print_tzinfo(dt.astimezone(tz.gettz('US/Eastern')))

"""
Explanation: If you have an absolute time, in UTC or otherwise, use datetime.astimezone():
End of explanation
"""


print_tzinfo(dt.astimezone(pytz.timezone('US/Eastern')))

"""
Explanation: pytz
In pytz, datetime.astimezone() still works exactly as expected:
End of explanation
"""


print_tzinfo(dt.replace(tzinfo=pytz.timezone('US/Eastern')))

"""
Explanation: But the constructor or .replace methods fail horribly:
End of explanation
"""


LOS_p = pytz.timezone('America/Los_Angeles')
dt = LOS_p.localize(datetime(2017, 8, 11, 14, 0))
print_tzinfo(dt)

"""
Explanation: pytz's time zone model

tzinfos are all static offsets
tzinfo is attached by the time zone object itself:
End of explanation
"""


dt_add = dt + timedelta(days=180)
print_tzinfo(dt_add)

print_tzinfo(LOS_p.normalize(dt_add))

"""
Explanation: You must normalize() datetimes after you've done some arithmetic on them:
End of explanation
"""


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)

print(repr(dt_pytz))    # Note that pytz doesn't set fold
print_tzinfo(dt_pytz)

"""
Explanation: Handling ambiguous times
Overview
Both dateutil and pytz will automatically give you the right absolute time if converting from an absolute time.
End of explanation
"""


dt = datetime(2004, 10, 31, 1, 30, tzinfo=tz.gettz('US/Eastern'))
tz.enfold(dt)

"""
Explanation: dateutil
For backwards compatibility, dateutil provides a tz.enfold method to add a fold attribute if necessary:
End of explanation
"""


tz.datetime_ambiguous(datetime(2004, 10, 31, 1, 30, tzinfo=NYC))

tz.datetime_ambiguous(datetime(2004, 10, 31, 1, 30), NYC)

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))

"""
Explanation: ```python
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'
```



dateutil
To detect ambiguous times, dateutil provides tz.datetime_ambiguous
End of explanation
"""


for i in range(3):
    dt_i = tz.enfold(dt_0 + timedelta(hours=i), fold=1)
    print('{} (fold={})'.format(dt_i, dt_i.fold))

"""
Explanation: Note: fold is ignored when the datetime is not ambiguous:
End of explanation
"""


NYC_pytz = pytz.timezone('America/New_York')

dt_pytz = NYC_pytz.localize(datetime(2004, 10, 31, 1, 30))
print_tzinfo(dt_pytz)

"""
Explanation: pytz
When localizing times, pytz defaults to standard time:
End of explanation
"""


dt_pytz = NYC_pytz.localize(datetime(2004, 10, 31, 1, 30), is_dst=True)
print_tzinfo(dt_pytz)

"""
Explanation: To get a time zone in daylight time, pass is_dst=True to localize:
End of explanation
"""


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"))


"""
Explanation: If is_dst=None is passed to localize, pytz raises an AmbiguousTimeError:
End of explanation
"""


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'))


"""
Explanation: Handling imaginary times
dateutil
dateutil provides a tz.datetime_exists() function to tell you whether you've constructed an imaginary datetime:
End of explanation
"""


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)))

"""
Explanation: Generally for imaginary datetimes, you either want to skip over them or "slide forward":
End of explanation
"""


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))

"""
Explanation: pytz
When using localize on an imaginary datetime, pytz will create an imaginary time and use is_dst to decide what offset to assign it:
End of explanation
"""


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))

"""
Explanation: 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):
End of explanation
"""


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'))

"""
Explanation: pytz
If you pass is_dst=None, pytz will throw a NonExistentTimeError:
End of explanation
"""


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)))

"""
Explanation: pytz
dateutil.tz.datetime_exists() works with pytz zones, too
End of explanation
"""


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'))

"""
Explanation: And will detect non-normalized datetimes:
End of explanation
"""


# tz.tzutc() is equivalent to pytz.UTC or timezone.utc
dt = datetime(2014, 12, 19, 22, 30, tzinfo=tz.tzutc())
print_tzinfo(dt)

"""
Explanation: dateutil's tzinfo implementations
UTC and Static time zones
End of explanation
"""


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))

"""
Explanation: Static offsets represent zones with a fixed offset from UTC, and takes a tzname or either number of seconds or a timedelta:
End of explanation
"""


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)

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))

"""
Explanation: UTC and Static time zones
In Python 3.2, timezone objects were introduced to provide ready-made tzinfo subclasses for the simple case of static offsets from UTC.
End of explanation
"""


# Temporarily changes the TZ file on *nix systems.
from helper_functions import TZEnvContext

print_tzinfo(dt.astimezone(tz.tzlocal()))

with TZEnvContext('UTC'):
    print_tzinfo(dt.astimezone(tz.tzlocal()))

with TZEnvContext('PST8PDT'):
    print_tzinfo((dt + timedelta(days=180)).astimezone(tz.tzlocal()))

"""
Explanation: Local time
The tz.tzlocal() class is a tzinfo implementation that uses the OS hooks in Python's time  module to get the local system time.
End of explanation
"""


NYC = tz.gettz('America/New_York')
NYC

"""
Explanation: Local time: Windows
tz.win.tzwinlocal() directly queries the Windows registry for its time zone data and uses that to construct a tzinfo.
Fixes this bug:
```python



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
```
IANA (Olson) database
The dateutil.tz.tzfile class provides support for IANA zoneinfo binaries (shipped with *nix systems).
<br/><br/>
DO NOT USE tz.tzfile directly - use tz.gettz()
End of explanation
"""


print_tzinfo(datetime(2017, 8, 12, 14, tzinfo=NYC))      # Eastern Daylight Time

print_tzinfo(datetime(1944, 1, 6, 12, 15, tzinfo=NYC))    # Eastern War Time

print_tzinfo(datetime(1901, 9, 6, 16, 7, tzinfo=NYC))     # Local solar mean

"""
Explanation: The IANA database contains historical time zone transitions:
End of explanation
"""


tz.gettz()      # Passing nothing gives you local time

# If your TZSTR is an an Olson file, it is prioritized over the /etc/localtime tzfile.
with TZEnvContext('CST6CDT'):
    print(gettz())

# 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'))

# Retrieve IANA zone:
print(gettz('Pacific/Kiritimati'))

# Directly parse a TZ variable:
print(gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3'))

"""
Explanation: tz.gettz()
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.
End of explanation
"""


