| Index: third_party/recipe_engine/third_party/dateutil/tz.py
|
| diff --git a/third_party/recipe_engine/third_party/dateutil/tz.py b/third_party/recipe_engine/third_party/dateutil/tz.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0e28d6b3320953a71aace88d4043c80e2b71a4b8
|
| --- /dev/null
|
| +++ b/third_party/recipe_engine/third_party/dateutil/tz.py
|
| @@ -0,0 +1,951 @@
|
| +"""
|
| +Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
| +
|
| +This module offers extensions to the standard python 2.3+
|
| +datetime module.
|
| +"""
|
| +__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
| +__license__ = "PSF License"
|
| +
|
| +import datetime
|
| +import struct
|
| +import time
|
| +import sys
|
| +import os
|
| +
|
| +relativedelta = None
|
| +parser = None
|
| +rrule = None
|
| +
|
| +__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
|
| + "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz"]
|
| +
|
| +try:
|
| + from dateutil.tzwin import tzwin, tzwinlocal
|
| +except (ImportError, OSError):
|
| + tzwin, tzwinlocal = None, None
|
| +
|
| +ZERO = datetime.timedelta(0)
|
| +EPOCHORDINAL = datetime.datetime.utcfromtimestamp(0).toordinal()
|
| +
|
| +class tzutc(datetime.tzinfo):
|
| +
|
| + def utcoffset(self, dt):
|
| + return ZERO
|
| +
|
| + def dst(self, dt):
|
| + return ZERO
|
| +
|
| + def tzname(self, dt):
|
| + return "UTC"
|
| +
|
| + def __eq__(self, other):
|
| + return (isinstance(other, tzutc) or
|
| + (isinstance(other, tzoffset) and other._offset == ZERO))
|
| +
|
| + def __ne__(self, other):
|
| + return not self.__eq__(other)
|
| +
|
| + def __repr__(self):
|
| + return "%s()" % self.__class__.__name__
|
| +
|
| + __reduce__ = object.__reduce__
|
| +
|
| +class tzoffset(datetime.tzinfo):
|
| +
|
| + def __init__(self, name, offset):
|
| + self._name = name
|
| + self._offset = datetime.timedelta(seconds=offset)
|
| +
|
| + def utcoffset(self, dt):
|
| + return self._offset
|
| +
|
| + def dst(self, dt):
|
| + return ZERO
|
| +
|
| + def tzname(self, dt):
|
| + return self._name
|
| +
|
| + def __eq__(self, other):
|
| + return (isinstance(other, tzoffset) and
|
| + self._offset == other._offset)
|
| +
|
| + def __ne__(self, other):
|
| + return not self.__eq__(other)
|
| +
|
| + def __repr__(self):
|
| + return "%s(%s, %s)" % (self.__class__.__name__,
|
| + `self._name`,
|
| + self._offset.days*86400+self._offset.seconds)
|
| +
|
| + __reduce__ = object.__reduce__
|
| +
|
| +class tzlocal(datetime.tzinfo):
|
| +
|
| + _std_offset = datetime.timedelta(seconds=-time.timezone)
|
| + if time.daylight:
|
| + _dst_offset = datetime.timedelta(seconds=-time.altzone)
|
| + else:
|
| + _dst_offset = _std_offset
|
| +
|
| + def utcoffset(self, dt):
|
| + if self._isdst(dt):
|
| + return self._dst_offset
|
| + else:
|
| + return self._std_offset
|
| +
|
| + def dst(self, dt):
|
| + if self._isdst(dt):
|
| + return self._dst_offset-self._std_offset
|
| + else:
|
| + return ZERO
|
| +
|
| + def tzname(self, dt):
|
| + return time.tzname[self._isdst(dt)]
|
| +
|
| + def _isdst(self, dt):
|
| + # We can't use mktime here. It is unstable when deciding if
|
| + # the hour near to a change is DST or not.
|
| + #
|
| + # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,
|
| + # dt.minute, dt.second, dt.weekday(), 0, -1))
|
| + # return time.localtime(timestamp).tm_isdst
|
| + #
|
| + # The code above yields the following result:
|
| + #
|
| + #>>> import tz, datetime
|
| + #>>> t = tz.tzlocal()
|
| + #>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
| + #'BRDT'
|
| + #>>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
|
| + #'BRST'
|
| + #>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
| + #'BRST'
|
| + #>>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
|
| + #'BRDT'
|
| + #>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
| + #'BRDT'
|
| + #
|
| + # Here is a more stable implementation:
|
| + #
|
| + timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400
|
| + + dt.hour * 3600
|
| + + dt.minute * 60
|
| + + dt.second)
|
| + return time.localtime(timestamp+time.timezone).tm_isdst
|
| +
|
| + def __eq__(self, other):
|
| + if not isinstance(other, tzlocal):
|
| + return False
|
| + return (self._std_offset == other._std_offset and
|
| + self._dst_offset == other._dst_offset)
|
| + return True
|
| +
|
| + def __ne__(self, other):
|
| + return not self.__eq__(other)
|
| +
|
| + def __repr__(self):
|
| + return "%s()" % self.__class__.__name__
|
| +
|
| + __reduce__ = object.__reduce__
|
| +
|
| +class _ttinfo(object):
|
| + __slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt"]
|
| +
|
| + def __init__(self):
|
| + for attr in self.__slots__:
|
| + setattr(self, attr, None)
|
| +
|
| + def __repr__(self):
|
| + l = []
|
| + for attr in self.__slots__:
|
| + value = getattr(self, attr)
|
| + if value is not None:
|
| + l.append("%s=%s" % (attr, `value`))
|
| + return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
|
| +
|
| + def __eq__(self, other):
|
| + if not isinstance(other, _ttinfo):
|
| + return False
|
| + return (self.offset == other.offset and
|
| + self.delta == other.delta and
|
| + self.isdst == other.isdst and
|
| + self.abbr == other.abbr and
|
| + self.isstd == other.isstd and
|
| + self.isgmt == other.isgmt)
|
| +
|
| + def __ne__(self, other):
|
| + return not self.__eq__(other)
|
| +
|
| + def __getstate__(self):
|
| + state = {}
|
| + for name in self.__slots__:
|
| + state[name] = getattr(self, name, None)
|
| + return state
|
| +
|
| + def __setstate__(self, state):
|
| + for name in self.__slots__:
|
| + if name in state:
|
| + setattr(self, name, state[name])
|
| +
|
| +class tzfile(datetime.tzinfo):
|
| +
|
| + # http://www.twinsun.com/tz/tz-link.htm
|
| + # ftp://elsie.nci.nih.gov/pub/tz*.tar.gz
|
| +
|
| + def __init__(self, fileobj):
|
| + if isinstance(fileobj, basestring):
|
| + self._filename = fileobj
|
| + fileobj = open(fileobj)
|
| + elif hasattr(fileobj, "name"):
|
| + self._filename = fileobj.name
|
| + else:
|
| + self._filename = `fileobj`
|
| +
|
| + # From tzfile(5):
|
| + #
|
| + # The time zone information files used by tzset(3)
|
| + # begin with the magic characters "TZif" to identify
|
| + # them as time zone information files, followed by
|
| + # sixteen bytes reserved for future use, followed by
|
| + # six four-byte values of type long, written in a
|
| + # ``standard'' byte order (the high-order byte
|
| + # of the value is written first).
|
| +
|
| + if fileobj.read(4) != "TZif":
|
| + raise ValueError, "magic not found"
|
| +
|
| + fileobj.read(16)
|
| +
|
| + (
|
| + # The number of UTC/local indicators stored in the file.
|
| + ttisgmtcnt,
|
| +
|
| + # The number of standard/wall indicators stored in the file.
|
| + ttisstdcnt,
|
| +
|
| + # The number of leap seconds for which data is
|
| + # stored in the file.
|
| + leapcnt,
|
| +
|
| + # The number of "transition times" for which data
|
| + # is stored in the file.
|
| + timecnt,
|
| +
|
| + # The number of "local time types" for which data
|
| + # is stored in the file (must not be zero).
|
| + typecnt,
|
| +
|
| + # The number of characters of "time zone
|
| + # abbreviation strings" stored in the file.
|
| + charcnt,
|
| +
|
| + ) = struct.unpack(">6l", fileobj.read(24))
|
| +
|
| + # The above header is followed by tzh_timecnt four-byte
|
| + # values of type long, sorted in ascending order.
|
| + # These values are written in ``standard'' byte order.
|
| + # Each is used as a transition time (as returned by
|
| + # time(2)) at which the rules for computing local time
|
| + # change.
|
| +
|
| + if timecnt:
|
| + self._trans_list = struct.unpack(">%dl" % timecnt,
|
| + fileobj.read(timecnt*4))
|
| + else:
|
| + self._trans_list = []
|
| +
|
| + # Next come tzh_timecnt one-byte values of type unsigned
|
| + # char; each one tells which of the different types of
|
| + # ``local time'' types described in the file is associated
|
| + # with the same-indexed transition time. These values
|
| + # serve as indices into an array of ttinfo structures that
|
| + # appears next in the file.
|
| +
|
| + if timecnt:
|
| + self._trans_idx = struct.unpack(">%dB" % timecnt,
|
| + fileobj.read(timecnt))
|
| + else:
|
| + self._trans_idx = []
|
| +
|
| + # Each ttinfo structure is written as a four-byte value
|
| + # for tt_gmtoff of type long, in a standard byte
|
| + # order, followed by a one-byte value for tt_isdst
|
| + # and a one-byte value for tt_abbrind. In each
|
| + # structure, tt_gmtoff gives the number of
|
| + # seconds to be added to UTC, tt_isdst tells whether
|
| + # tm_isdst should be set by localtime(3), and
|
| + # tt_abbrind serves as an index into the array of
|
| + # time zone abbreviation characters that follow the
|
| + # ttinfo structure(s) in the file.
|
| +
|
| + ttinfo = []
|
| +
|
| + for i in range(typecnt):
|
| + ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))
|
| +
|
| + abbr = fileobj.read(charcnt)
|
| +
|
| + # Then there are tzh_leapcnt pairs of four-byte
|
| + # values, written in standard byte order; the
|
| + # first value of each pair gives the time (as
|
| + # returned by time(2)) at which a leap second
|
| + # occurs; the second gives the total number of
|
| + # leap seconds to be applied after the given time.
|
| + # The pairs of values are sorted in ascending order
|
| + # by time.
|
| +
|
| + # Not used, for now
|
| + if leapcnt:
|
| + leap = struct.unpack(">%dl" % (leapcnt*2),
|
| + fileobj.read(leapcnt*8))
|
| +
|
| + # Then there are tzh_ttisstdcnt standard/wall
|
| + # indicators, each stored as a one-byte value;
|
| + # they tell whether the transition times associated
|
| + # with local time types were specified as standard
|
| + # time or wall clock time, and are used when
|
| + # a time zone file is used in handling POSIX-style
|
| + # time zone environment variables.
|
| +
|
| + if ttisstdcnt:
|
| + isstd = struct.unpack(">%db" % ttisstdcnt,
|
| + fileobj.read(ttisstdcnt))
|
| +
|
| + # Finally, there are tzh_ttisgmtcnt UTC/local
|
| + # indicators, each stored as a one-byte value;
|
| + # they tell whether the transition times associated
|
| + # with local time types were specified as UTC or
|
| + # local time, and are used when a time zone file
|
| + # is used in handling POSIX-style time zone envi-
|
| + # ronment variables.
|
| +
|
| + if ttisgmtcnt:
|
| + isgmt = struct.unpack(">%db" % ttisgmtcnt,
|
| + fileobj.read(ttisgmtcnt))
|
| +
|
| + # ** Everything has been read **
|
| +
|
| + # Build ttinfo list
|
| + self._ttinfo_list = []
|
| + for i in range(typecnt):
|
| + gmtoff, isdst, abbrind = ttinfo[i]
|
| + # Round to full-minutes if that's not the case. Python's
|
| + # datetime doesn't accept sub-minute timezones. Check
|
| + # http://python.org/sf/1447945 for some information.
|
| + gmtoff = (gmtoff+30)//60*60
|
| + tti = _ttinfo()
|
| + tti.offset = gmtoff
|
| + tti.delta = datetime.timedelta(seconds=gmtoff)
|
| + tti.isdst = isdst
|
| + tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)]
|
| + tti.isstd = (ttisstdcnt > i and isstd[i] != 0)
|
| + tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0)
|
| + self._ttinfo_list.append(tti)
|
| +
|
| + # Replace ttinfo indexes for ttinfo objects.
|
| + trans_idx = []
|
| + for idx in self._trans_idx:
|
| + trans_idx.append(self._ttinfo_list[idx])
|
| + self._trans_idx = tuple(trans_idx)
|
| +
|
| + # Set standard, dst, and before ttinfos. before will be
|
| + # used when a given time is before any transitions,
|
| + # and will be set to the first non-dst ttinfo, or to
|
| + # the first dst, if all of them are dst.
|
| + self._ttinfo_std = None
|
| + self._ttinfo_dst = None
|
| + self._ttinfo_before = None
|
| + if self._ttinfo_list:
|
| + if not self._trans_list:
|
| + self._ttinfo_std = self._ttinfo_first = self._ttinfo_list[0]
|
| + else:
|
| + for i in range(timecnt-1,-1,-1):
|
| + tti = self._trans_idx[i]
|
| + if not self._ttinfo_std and not tti.isdst:
|
| + self._ttinfo_std = tti
|
| + elif not self._ttinfo_dst and tti.isdst:
|
| + self._ttinfo_dst = tti
|
| + if self._ttinfo_std and self._ttinfo_dst:
|
| + break
|
| + else:
|
| + if self._ttinfo_dst and not self._ttinfo_std:
|
| + self._ttinfo_std = self._ttinfo_dst
|
| +
|
| + for tti in self._ttinfo_list:
|
| + if not tti.isdst:
|
| + self._ttinfo_before = tti
|
| + break
|
| + else:
|
| + self._ttinfo_before = self._ttinfo_list[0]
|
| +
|
| + # Now fix transition times to become relative to wall time.
|
| + #
|
| + # I'm not sure about this. In my tests, the tz source file
|
| + # is setup to wall time, and in the binary file isstd and
|
| + # isgmt are off, so it should be in wall time. OTOH, it's
|
| + # always in gmt time. Let me know if you have comments
|
| + # about this.
|
| + laststdoffset = 0
|
| + self._trans_list = list(self._trans_list)
|
| + for i in range(len(self._trans_list)):
|
| + tti = self._trans_idx[i]
|
| + if not tti.isdst:
|
| + # This is std time.
|
| + self._trans_list[i] += tti.offset
|
| + laststdoffset = tti.offset
|
| + else:
|
| + # This is dst time. Convert to std.
|
| + self._trans_list[i] += laststdoffset
|
| + self._trans_list = tuple(self._trans_list)
|
| +
|
| + def _find_ttinfo(self, dt, laststd=0):
|
| + timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400
|
| + + dt.hour * 3600
|
| + + dt.minute * 60
|
| + + dt.second)
|
| + idx = 0
|
| + for trans in self._trans_list:
|
| + if timestamp < trans:
|
| + break
|
| + idx += 1
|
| + else:
|
| + return self._ttinfo_std
|
| + if idx == 0:
|
| + return self._ttinfo_before
|
| + if laststd:
|
| + while idx > 0:
|
| + tti = self._trans_idx[idx-1]
|
| + if not tti.isdst:
|
| + return tti
|
| + idx -= 1
|
| + else:
|
| + return self._ttinfo_std
|
| + else:
|
| + return self._trans_idx[idx-1]
|
| +
|
| + def utcoffset(self, dt):
|
| + if not self._ttinfo_std:
|
| + return ZERO
|
| + return self._find_ttinfo(dt).delta
|
| +
|
| + def dst(self, dt):
|
| + if not self._ttinfo_dst:
|
| + return ZERO
|
| + tti = self._find_ttinfo(dt)
|
| + if not tti.isdst:
|
| + return ZERO
|
| +
|
| + # The documentation says that utcoffset()-dst() must
|
| + # be constant for every dt.
|
| + return tti.delta-self._find_ttinfo(dt, laststd=1).delta
|
| +
|
| + # An alternative for that would be:
|
| + #
|
| + # return self._ttinfo_dst.offset-self._ttinfo_std.offset
|
| + #
|
| + # However, this class stores historical changes in the
|
| + # dst offset, so I belive that this wouldn't be the right
|
| + # way to implement this.
|
| +
|
| + def tzname(self, dt):
|
| + if not self._ttinfo_std:
|
| + return None
|
| + return self._find_ttinfo(dt).abbr
|
| +
|
| + def __eq__(self, other):
|
| + if not isinstance(other, tzfile):
|
| + return False
|
| + return (self._trans_list == other._trans_list and
|
| + self._trans_idx == other._trans_idx and
|
| + self._ttinfo_list == other._ttinfo_list)
|
| +
|
| + def __ne__(self, other):
|
| + return not self.__eq__(other)
|
| +
|
| +
|
| + def __repr__(self):
|
| + return "%s(%s)" % (self.__class__.__name__, `self._filename`)
|
| +
|
| + def __reduce__(self):
|
| + if not os.path.isfile(self._filename):
|
| + raise ValueError, "Unpickable %s class" % self.__class__.__name__
|
| + return (self.__class__, (self._filename,))
|
| +
|
| +class tzrange(datetime.tzinfo):
|
| +
|
| + def __init__(self, stdabbr, stdoffset=None,
|
| + dstabbr=None, dstoffset=None,
|
| + start=None, end=None):
|
| + global relativedelta
|
| + if not relativedelta:
|
| + from dateutil import relativedelta
|
| + self._std_abbr = stdabbr
|
| + self._dst_abbr = dstabbr
|
| + if stdoffset is not None:
|
| + self._std_offset = datetime.timedelta(seconds=stdoffset)
|
| + else:
|
| + self._std_offset = ZERO
|
| + if dstoffset is not None:
|
| + self._dst_offset = datetime.timedelta(seconds=dstoffset)
|
| + elif dstabbr and stdoffset is not None:
|
| + self._dst_offset = self._std_offset+datetime.timedelta(hours=+1)
|
| + else:
|
| + self._dst_offset = ZERO
|
| + if dstabbr and start is None:
|
| + self._start_delta = relativedelta.relativedelta(
|
| + hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))
|
| + else:
|
| + self._start_delta = start
|
| + if dstabbr and end is None:
|
| + self._end_delta = relativedelta.relativedelta(
|
| + hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))
|
| + else:
|
| + self._end_delta = end
|
| +
|
| + def utcoffset(self, dt):
|
| + if self._isdst(dt):
|
| + return self._dst_offset
|
| + else:
|
| + return self._std_offset
|
| +
|
| + def dst(self, dt):
|
| + if self._isdst(dt):
|
| + return self._dst_offset-self._std_offset
|
| + else:
|
| + return ZERO
|
| +
|
| + def tzname(self, dt):
|
| + if self._isdst(dt):
|
| + return self._dst_abbr
|
| + else:
|
| + return self._std_abbr
|
| +
|
| + def _isdst(self, dt):
|
| + if not self._start_delta:
|
| + return False
|
| + year = datetime.datetime(dt.year,1,1)
|
| + start = year+self._start_delta
|
| + end = year+self._end_delta
|
| + dt = dt.replace(tzinfo=None)
|
| + if start < end:
|
| + return dt >= start and dt < end
|
| + else:
|
| + return dt >= start or dt < end
|
| +
|
| + def __eq__(self, other):
|
| + if not isinstance(other, tzrange):
|
| + return False
|
| + return (self._std_abbr == other._std_abbr and
|
| + self._dst_abbr == other._dst_abbr and
|
| + self._std_offset == other._std_offset and
|
| + self._dst_offset == other._dst_offset and
|
| + self._start_delta == other._start_delta and
|
| + self._end_delta == other._end_delta)
|
| +
|
| + def __ne__(self, other):
|
| + return not self.__eq__(other)
|
| +
|
| + def __repr__(self):
|
| + return "%s(...)" % self.__class__.__name__
|
| +
|
| + __reduce__ = object.__reduce__
|
| +
|
| +class tzstr(tzrange):
|
| +
|
| + def __init__(self, s):
|
| + global parser
|
| + if not parser:
|
| + from dateutil import parser
|
| + self._s = s
|
| +
|
| + res = parser._parsetz(s)
|
| + if res is None:
|
| + raise ValueError, "unknown string format"
|
| +
|
| + # Here we break the compatibility with the TZ variable handling.
|
| + # GMT-3 actually *means* the timezone -3.
|
| + if res.stdabbr in ("GMT", "UTC"):
|
| + res.stdoffset *= -1
|
| +
|
| + # We must initialize it first, since _delta() needs
|
| + # _std_offset and _dst_offset set. Use False in start/end
|
| + # to avoid building it two times.
|
| + tzrange.__init__(self, res.stdabbr, res.stdoffset,
|
| + res.dstabbr, res.dstoffset,
|
| + start=False, end=False)
|
| +
|
| + if not res.dstabbr:
|
| + self._start_delta = None
|
| + self._end_delta = None
|
| + else:
|
| + self._start_delta = self._delta(res.start)
|
| + if self._start_delta:
|
| + self._end_delta = self._delta(res.end, isend=1)
|
| +
|
| + def _delta(self, x, isend=0):
|
| + kwargs = {}
|
| + if x.month is not None:
|
| + kwargs["month"] = x.month
|
| + if x.weekday is not None:
|
| + kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week)
|
| + if x.week > 0:
|
| + kwargs["day"] = 1
|
| + else:
|
| + kwargs["day"] = 31
|
| + elif x.day:
|
| + kwargs["day"] = x.day
|
| + elif x.yday is not None:
|
| + kwargs["yearday"] = x.yday
|
| + elif x.jyday is not None:
|
| + kwargs["nlyearday"] = x.jyday
|
| + if not kwargs:
|
| + # Default is to start on first sunday of april, and end
|
| + # on last sunday of october.
|
| + if not isend:
|
| + kwargs["month"] = 4
|
| + kwargs["day"] = 1
|
| + kwargs["weekday"] = relativedelta.SU(+1)
|
| + else:
|
| + kwargs["month"] = 10
|
| + kwargs["day"] = 31
|
| + kwargs["weekday"] = relativedelta.SU(-1)
|
| + if x.time is not None:
|
| + kwargs["seconds"] = x.time
|
| + else:
|
| + # Default is 2AM.
|
| + kwargs["seconds"] = 7200
|
| + if isend:
|
| + # Convert to standard time, to follow the documented way
|
| + # of working with the extra hour. See the documentation
|
| + # of the tzinfo class.
|
| + delta = self._dst_offset-self._std_offset
|
| + kwargs["seconds"] -= delta.seconds+delta.days*86400
|
| + return relativedelta.relativedelta(**kwargs)
|
| +
|
| + def __repr__(self):
|
| + return "%s(%s)" % (self.__class__.__name__, `self._s`)
|
| +
|
| +class _tzicalvtzcomp:
|
| + def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
|
| + tzname=None, rrule=None):
|
| + self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)
|
| + self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)
|
| + self.tzoffsetdiff = self.tzoffsetto-self.tzoffsetfrom
|
| + self.isdst = isdst
|
| + self.tzname = tzname
|
| + self.rrule = rrule
|
| +
|
| +class _tzicalvtz(datetime.tzinfo):
|
| + def __init__(self, tzid, comps=[]):
|
| + self._tzid = tzid
|
| + self._comps = comps
|
| + self._cachedate = []
|
| + self._cachecomp = []
|
| +
|
| + def _find_comp(self, dt):
|
| + if len(self._comps) == 1:
|
| + return self._comps[0]
|
| + dt = dt.replace(tzinfo=None)
|
| + try:
|
| + return self._cachecomp[self._cachedate.index(dt)]
|
| + except ValueError:
|
| + pass
|
| + lastcomp = None
|
| + lastcompdt = None
|
| + for comp in self._comps:
|
| + if not comp.isdst:
|
| + # Handle the extra hour in DST -> STD
|
| + compdt = comp.rrule.before(dt-comp.tzoffsetdiff, inc=True)
|
| + else:
|
| + compdt = comp.rrule.before(dt, inc=True)
|
| + if compdt and (not lastcompdt or lastcompdt < compdt):
|
| + lastcompdt = compdt
|
| + lastcomp = comp
|
| + if not lastcomp:
|
| + # RFC says nothing about what to do when a given
|
| + # time is before the first onset date. We'll look for the
|
| + # first standard component, or the first component, if
|
| + # none is found.
|
| + for comp in self._comps:
|
| + if not comp.isdst:
|
| + lastcomp = comp
|
| + break
|
| + else:
|
| + lastcomp = comp[0]
|
| + self._cachedate.insert(0, dt)
|
| + self._cachecomp.insert(0, lastcomp)
|
| + if len(self._cachedate) > 10:
|
| + self._cachedate.pop()
|
| + self._cachecomp.pop()
|
| + return lastcomp
|
| +
|
| + def utcoffset(self, dt):
|
| + return self._find_comp(dt).tzoffsetto
|
| +
|
| + def dst(self, dt):
|
| + comp = self._find_comp(dt)
|
| + if comp.isdst:
|
| + return comp.tzoffsetdiff
|
| + else:
|
| + return ZERO
|
| +
|
| + def tzname(self, dt):
|
| + return self._find_comp(dt).tzname
|
| +
|
| + def __repr__(self):
|
| + return "<tzicalvtz %s>" % `self._tzid`
|
| +
|
| + __reduce__ = object.__reduce__
|
| +
|
| +class tzical:
|
| + def __init__(self, fileobj):
|
| + global rrule
|
| + if not rrule:
|
| + from dateutil import rrule
|
| +
|
| + if isinstance(fileobj, basestring):
|
| + self._s = fileobj
|
| + fileobj = open(fileobj)
|
| + elif hasattr(fileobj, "name"):
|
| + self._s = fileobj.name
|
| + else:
|
| + self._s = `fileobj`
|
| +
|
| + self._vtz = {}
|
| +
|
| + self._parse_rfc(fileobj.read())
|
| +
|
| + def keys(self):
|
| + return self._vtz.keys()
|
| +
|
| + def get(self, tzid=None):
|
| + if tzid is None:
|
| + keys = self._vtz.keys()
|
| + if len(keys) == 0:
|
| + raise ValueError, "no timezones defined"
|
| + elif len(keys) > 1:
|
| + raise ValueError, "more than one timezone available"
|
| + tzid = keys[0]
|
| + return self._vtz.get(tzid)
|
| +
|
| + def _parse_offset(self, s):
|
| + s = s.strip()
|
| + if not s:
|
| + raise ValueError, "empty offset"
|
| + if s[0] in ('+', '-'):
|
| + signal = (-1,+1)[s[0]=='+']
|
| + s = s[1:]
|
| + else:
|
| + signal = +1
|
| + if len(s) == 4:
|
| + return (int(s[:2])*3600+int(s[2:])*60)*signal
|
| + elif len(s) == 6:
|
| + return (int(s[:2])*3600+int(s[2:4])*60+int(s[4:]))*signal
|
| + else:
|
| + raise ValueError, "invalid offset: "+s
|
| +
|
| + def _parse_rfc(self, s):
|
| + lines = s.splitlines()
|
| + if not lines:
|
| + raise ValueError, "empty string"
|
| +
|
| + # Unfold
|
| + i = 0
|
| + while i < len(lines):
|
| + line = lines[i].rstrip()
|
| + if not line:
|
| + del lines[i]
|
| + elif i > 0 and line[0] == " ":
|
| + lines[i-1] += line[1:]
|
| + del lines[i]
|
| + else:
|
| + i += 1
|
| +
|
| + tzid = None
|
| + comps = []
|
| + invtz = False
|
| + comptype = None
|
| + for line in lines:
|
| + if not line:
|
| + continue
|
| + name, value = line.split(':', 1)
|
| + parms = name.split(';')
|
| + if not parms:
|
| + raise ValueError, "empty property name"
|
| + name = parms[0].upper()
|
| + parms = parms[1:]
|
| + if invtz:
|
| + if name == "BEGIN":
|
| + if value in ("STANDARD", "DAYLIGHT"):
|
| + # Process component
|
| + pass
|
| + else:
|
| + raise ValueError, "unknown component: "+value
|
| + comptype = value
|
| + founddtstart = False
|
| + tzoffsetfrom = None
|
| + tzoffsetto = None
|
| + rrulelines = []
|
| + tzname = None
|
| + elif name == "END":
|
| + if value == "VTIMEZONE":
|
| + if comptype:
|
| + raise ValueError, \
|
| + "component not closed: "+comptype
|
| + if not tzid:
|
| + raise ValueError, \
|
| + "mandatory TZID not found"
|
| + if not comps:
|
| + raise ValueError, \
|
| + "at least one component is needed"
|
| + # Process vtimezone
|
| + self._vtz[tzid] = _tzicalvtz(tzid, comps)
|
| + invtz = False
|
| + elif value == comptype:
|
| + if not founddtstart:
|
| + raise ValueError, \
|
| + "mandatory DTSTART not found"
|
| + if tzoffsetfrom is None:
|
| + raise ValueError, \
|
| + "mandatory TZOFFSETFROM not found"
|
| + if tzoffsetto is None:
|
| + raise ValueError, \
|
| + "mandatory TZOFFSETFROM not found"
|
| + # Process component
|
| + rr = None
|
| + if rrulelines:
|
| + rr = rrule.rrulestr("\n".join(rrulelines),
|
| + compatible=True,
|
| + ignoretz=True,
|
| + cache=True)
|
| + comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto,
|
| + (comptype == "DAYLIGHT"),
|
| + tzname, rr)
|
| + comps.append(comp)
|
| + comptype = None
|
| + else:
|
| + raise ValueError, \
|
| + "invalid component end: "+value
|
| + elif comptype:
|
| + if name == "DTSTART":
|
| + rrulelines.append(line)
|
| + founddtstart = True
|
| + elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"):
|
| + rrulelines.append(line)
|
| + elif name == "TZOFFSETFROM":
|
| + if parms:
|
| + raise ValueError, \
|
| + "unsupported %s parm: %s "%(name, parms[0])
|
| + tzoffsetfrom = self._parse_offset(value)
|
| + elif name == "TZOFFSETTO":
|
| + if parms:
|
| + raise ValueError, \
|
| + "unsupported TZOFFSETTO parm: "+parms[0]
|
| + tzoffsetto = self._parse_offset(value)
|
| + elif name == "TZNAME":
|
| + if parms:
|
| + raise ValueError, \
|
| + "unsupported TZNAME parm: "+parms[0]
|
| + tzname = value
|
| + elif name == "COMMENT":
|
| + pass
|
| + else:
|
| + raise ValueError, "unsupported property: "+name
|
| + else:
|
| + if name == "TZID":
|
| + if parms:
|
| + raise ValueError, \
|
| + "unsupported TZID parm: "+parms[0]
|
| + tzid = value
|
| + elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
|
| + pass
|
| + else:
|
| + raise ValueError, "unsupported property: "+name
|
| + elif name == "BEGIN" and value == "VTIMEZONE":
|
| + tzid = None
|
| + comps = []
|
| + invtz = True
|
| +
|
| + def __repr__(self):
|
| + return "%s(%s)" % (self.__class__.__name__, `self._s`)
|
| +
|
| +if sys.platform != "win32":
|
| + TZFILES = ["/etc/localtime", "localtime"]
|
| + TZPATHS = ["/usr/share/zoneinfo", "/usr/lib/zoneinfo", "/etc/zoneinfo"]
|
| +else:
|
| + TZFILES = []
|
| + TZPATHS = []
|
| +
|
| +def gettz(name=None):
|
| + tz = None
|
| + if not name:
|
| + try:
|
| + name = os.environ["TZ"]
|
| + except KeyError:
|
| + pass
|
| + if name is None or name == ":":
|
| + for filepath in TZFILES:
|
| + if not os.path.isabs(filepath):
|
| + filename = filepath
|
| + for path in TZPATHS:
|
| + filepath = os.path.join(path, filename)
|
| + if os.path.isfile(filepath):
|
| + break
|
| + else:
|
| + continue
|
| + if os.path.isfile(filepath):
|
| + try:
|
| + tz = tzfile(filepath)
|
| + break
|
| + except (IOError, OSError, ValueError):
|
| + pass
|
| + else:
|
| + tz = tzlocal()
|
| + else:
|
| + if name.startswith(":"):
|
| + name = name[:-1]
|
| + if os.path.isabs(name):
|
| + if os.path.isfile(name):
|
| + tz = tzfile(name)
|
| + else:
|
| + tz = None
|
| + else:
|
| + for path in TZPATHS:
|
| + filepath = os.path.join(path, name)
|
| + if not os.path.isfile(filepath):
|
| + filepath = filepath.replace(' ','_')
|
| + if not os.path.isfile(filepath):
|
| + continue
|
| + try:
|
| + tz = tzfile(filepath)
|
| + break
|
| + except (IOError, OSError, ValueError):
|
| + pass
|
| + else:
|
| + tz = None
|
| + if tzwin:
|
| + try:
|
| + tz = tzwin(name)
|
| + except OSError:
|
| + pass
|
| + if not tz:
|
| + from dateutil.zoneinfo import gettz
|
| + tz = gettz(name)
|
| + if not tz:
|
| + for c in name:
|
| + # name must have at least one offset to be a tzstr
|
| + if c in "0123456789":
|
| + try:
|
| + tz = tzstr(name)
|
| + except ValueError:
|
| + pass
|
| + break
|
| + else:
|
| + if name in ("GMT", "UTC"):
|
| + tz = tzutc()
|
| + elif name in time.tzname:
|
| + tz = tzlocal()
|
| + return tz
|
| +
|
| +# vim:ts=4:sw=4:et
|
|
|