from six import PY3
from six . moves import _thread
from datetime import datetime , timedelta , tzinfo
import copy
ZERO = timedelta ( 0 )
__all__ = [ ' tzname_in_python2 ' , ' enfold ' ]
def tzname_in_python2 ( namefunc ) :
""" Change unicode output into bytestrings in Python 2
tzname ( ) API changed in Python 3. It used to return bytes , but was changed
to unicode strings
"""
def adjust_encoding ( * args , * * kwargs ) :
name = namefunc ( * args , * * kwargs )
if name is not None and not PY3 :
name = name . encode ( )
return name
return adjust_encoding
# The following is adapted from Alexander Belopolsky's tz library
# https://github.com/abalkin/tz
if hasattr ( datetime , ' fold ' ) :
# This is the pre-python 3.6 fold situation
def enfold ( dt , fold = 1 ) :
"""
Provides a unified interface for assigning the ` ` fold ` ` attribute to
datetimes both before and after the implementation of PEP - 495.
: param fold :
The value for the ` ` fold ` ` attribute in the returned datetime . This
should be either 0 or 1.
: return :
Returns an object for which ` ` getattr ( dt , ' fold ' , 0 ) ` ` returns
` ` fold ` ` for all versions of Python . In versions prior to
Python 3.6 , this is a ` ` _DatetimeWithFold ` ` object , which is a
subclass of : py : class : ` datetime . datetime ` with the ` ` fold ` `
attribute added , if ` ` fold ` ` is 1.
. . versionadded : : 2.6 .0
"""
return dt . replace ( fold = fold )
else :
class _DatetimeWithFold ( datetime ) :
"""
This is a class designed to provide a PEP 495 - compliant interface for
Python versions before 3.6 . It is used only for dates in a fold , so
the ` ` fold ` ` attribute is fixed at ` ` 1 ` ` .
. . versionadded : : 2.6 .0
"""
__slots__ = ( )
@property
def fold ( self ) :
return 1
def enfold ( dt , fold = 1 ) :
"""
Provides a unified interface for assigning the ` ` fold ` ` attribute to
datetimes both before and after the implementation of PEP - 495.
: param fold :
The value for the ` ` fold ` ` attribute in the returned datetime . This
should be either 0 or 1.
: return :
Returns an object for which ` ` getattr ( dt , ' fold ' , 0 ) ` ` returns
` ` fold ` ` for all versions of Python . In versions prior to
Python 3.6 , this is a ` ` _DatetimeWithFold ` ` object , which is a
subclass of : py : class : ` datetime . datetime ` with the ` ` fold ` `
attribute added , if ` ` fold ` ` is 1.
. . versionadded : : 2.6 .0
"""
if getattr ( dt , ' fold ' , 0 ) == fold :
return dt
args = dt . timetuple ( ) [ : 6 ]
args + = ( dt . microsecond , dt . tzinfo )
if fold :
return _DatetimeWithFold ( * args )
else :
return datetime ( * args )
class _tzinfo ( tzinfo ) :
"""
Base class for all ` ` dateutil ` ` ` ` tzinfo ` ` objects .
"""
def is_ambiguous ( self , dt ) :
"""
Whether or not the " wall time " of a given datetime is ambiguous in this
zone .
: param dt :
A : py : class : ` datetime . datetime ` , naive or time zone aware .
: return :
Returns ` ` True ` ` if ambiguous , ` ` False ` ` otherwise .
. . versionadded : : 2.6 .0
"""
dt = dt . replace ( tzinfo = self )
wall_0 = enfold ( dt , fold = 0 )
wall_1 = enfold ( dt , fold = 1 )
same_offset = wall_0 . utcoffset ( ) == wall_1 . utcoffset ( )
same_dt = wall_0 . replace ( tzinfo = None ) == wall_1 . replace ( tzinfo = None )
return same_dt and not same_offset
def _fold_status ( self , dt_utc , dt_wall ) :
"""
Determine the fold status of a " wall " datetime , given a representation
of the same datetime as a ( naive ) UTC datetime . This is calculated based
on the assumption that ` ` dt . utcoffset ( ) - dt . dst ( ) ` ` is constant for all
datetimes , and that this offset is the actual number of hours separating
` ` dt_utc ` ` and ` ` dt_wall ` ` .
: param dt_utc :
Representation of the datetime as UTC
: param dt_wall :
Representation of the datetime as " wall time " . This parameter must
either have a ` fold ` attribute or have a fold - naive
: class : ` datetime . tzinfo ` attached , otherwise the calculation may
fail .
"""
if self . is_ambiguous ( dt_wall ) :
delta_wall = dt_wall - dt_utc
_fold = int ( delta_wall == ( dt_utc . utcoffset ( ) - dt_utc . dst ( ) ) )
else :
_fold = 0
return _fold
def _fold ( self , dt ) :
return getattr ( dt , ' fold ' , 0 )
def _fromutc ( self , dt ) :
"""
Given a timezone - aware datetime in a given timezone , calculates a
timezone - aware datetime in a new timezone .
Since this is the one time that we * know * we have an unambiguous
datetime object , we take this opportunity to determine whether the
datetime is ambiguous and in a " fold " state ( e . g . if it ' s the first
occurence , chronologically , of the ambiguous datetime ) .
: param dt :
A timezone - aware : class : ` datetime . dateime ` object .
"""
# Re-implement the algorithm from Python's datetime.py
if not isinstance ( dt , datetime ) :
raise TypeError ( " fromutc() requires a datetime argument " )
if dt . tzinfo is not self :
raise ValueError ( " dt.tzinfo is not self " )
dtoff = dt . utcoffset ( )
if dtoff is None :
raise ValueError ( " fromutc() requires a non-None utcoffset() "
" result " )
# The original datetime.py code assumes that `dst()` defaults to
# zero during ambiguous times. PEP 495 inverts this presumption, so
# for pre-PEP 495 versions of python, we need to tweak the algorithm.
dtdst = dt . dst ( )
if dtdst is None :
raise ValueError ( " fromutc() requires a non-None dst() result " )
delta = dtoff - dtdst
if delta :
dt + = delta
# Set fold=1 so we can default to being in the fold for
# ambiguous dates.
dtdst = enfold ( dt , fold = 1 ) . dst ( )
if dtdst is None :
raise ValueError ( " fromutc(): dt.dst gave inconsistent "
" results; cannot convert " )
return dt + dtdst
def fromutc ( self , dt ) :
"""
Given a timezone - aware datetime in a given timezone , calculates a
timezone - aware datetime in a new timezone .
Since this is the one time that we * know * we have an unambiguous
datetime object , we take this opportunity to determine whether the
datetime is ambiguous and in a " fold " state ( e . g . if it ' s the first
occurance , chronologically , of the ambiguous datetime ) .
: param dt :
A timezone - aware : class : ` datetime . dateime ` object .
"""
dt_wall = self . _fromutc ( dt )
# Calculate the fold status given the two datetimes.
_fold = self . _fold_status ( dt , dt_wall )
# Set the default fold value for ambiguous dates
return enfold ( dt_wall , fold = _fold )
class tzrangebase ( _tzinfo ) :
"""
This is an abstract base class for time zones represented by an annual
transition into and out of DST . Child classes should implement the following
methods :
* ` ` __init__ ( self , * args , * * kwargs ) ` `
* ` ` transitions ( self , year ) ` ` - this is expected to return a tuple of
datetimes representing the DST on and off transitions in standard
time .
A fully initialized ` ` tzrangebase ` ` subclass should also provide the
following attributes :
* ` ` hasdst ` ` : Boolean whether or not the zone uses DST .
* ` ` _dst_offset ` ` / ` ` _std_offset ` ` : : class : ` datetime . timedelta ` objects
representing the respective UTC offsets .
* ` ` _dst_abbr ` ` / ` ` _std_abbr ` ` : Strings representing the timezone short
abbreviations in DST and STD , respectively .
* ` ` _hasdst ` ` : Whether or not the zone has DST .
. . versionadded : : 2.6 .0
"""
def __init__ ( self ) :
raise NotImplementedError ( ' tzrangebase is an abstract base class ' )
def utcoffset ( self , dt ) :
isdst = self . _isdst ( dt )
if isdst is None :
return None
elif isdst :
return self . _dst_offset
else :
return self . _std_offset
def dst ( self , dt ) :
isdst = self . _isdst ( dt )
if isdst is None :
return None
elif isdst :
return self . _dst_base_offset
else :
return ZERO
@tzname_in_python2
def tzname ( self , dt ) :
if self . _isdst ( dt ) :
return self . _dst_abbr
else :
return self . _std_abbr
def fromutc ( self , dt ) :
""" Given a datetime in UTC, return local time """
if not isinstance ( dt , datetime ) :
raise TypeError ( " fromutc() requires a datetime argument " )
if dt . tzinfo is not self :
raise ValueError ( " dt.tzinfo is not self " )
# Get transitions - if there are none, fixed offset
transitions = self . transitions ( dt . year )
if transitions is None :
return dt + self . utcoffset ( dt )
# Get the transition times in UTC
dston , dstoff = transitions
dston - = self . _std_offset
dstoff - = self . _std_offset
utc_transitions = ( dston , dstoff )
dt_utc = dt . replace ( tzinfo = None )
isdst = self . _naive_isdst ( dt_utc , utc_transitions )
if isdst :
dt_wall = dt + self . _dst_offset
else :
dt_wall = dt + self . _std_offset
_fold = int ( not isdst and self . is_ambiguous ( dt_wall ) )
return enfold ( dt_wall , fold = _fold )
def is_ambiguous ( self , dt ) :
"""
Whether or not the " wall time " of a given datetime is ambiguous in this
zone .
: param dt :
A : py : class : ` datetime . datetime ` , naive or time zone aware .
: return :
Returns ` ` True ` ` if ambiguous , ` ` False ` ` otherwise .
. . versionadded : : 2.6 .0
"""
if not self . hasdst :
return False
start , end = self . transitions ( dt . year )
dt = dt . replace ( tzinfo = None )
return ( end < = dt < end + self . _dst_base_offset )
def _isdst ( self , dt ) :
if not self . hasdst :
return False
elif dt is None :
return None
transitions = self . transitions ( dt . year )
if transitions is None :
return False
dt = dt . replace ( tzinfo = None )
isdst = self . _naive_isdst ( dt , transitions )
# Handle ambiguous dates
if not isdst and self . is_ambiguous ( dt ) :
return not self . _fold ( dt )
else :
return isdst
def _naive_isdst ( self , dt , transitions ) :
dston , dstoff = transitions
dt = dt . replace ( tzinfo = None )
if dston < dstoff :
isdst = dston < = dt < dstoff
else :
isdst = not dstoff < = dt < dston
return isdst
@property
def _dst_base_offset ( self ) :
return self . _dst_offset - self . _std_offset
__hash__ = None
def __ne__ ( self , other ) :
return not ( self == other )
def __repr__ ( self ) :
return " %s (...) " % self . __class__ . __name__
__reduce__ = object . __reduce__
def _total_seconds ( td ) :
# Python 2.6 doesn't have a total_seconds() method on timedelta objects
return ( ( td . seconds + td . days * 86400 ) * 1000000 +
td . microseconds ) / / 1000000
_total_seconds = getattr ( timedelta , ' total_seconds ' , _total_seconds )