| """ |
| Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net> |
| |
| This module offers extensions to the standard Python |
| datetime module. |
| """ |
| __license__ = "Simplified BSD" |
| |
| from six import string_types, PY3 |
| |
| 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 |
| |
| def tzname_in_python2(myfunc): |
| """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 inner_func(*args, **kwargs): |
| if PY3: |
| return myfunc(*args, **kwargs) |
| else: |
| return myfunc(*args, **kwargs).encode() |
| return inner_func |
| |
| 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 |
| |
| @tzname_in_python2 |
| 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 |
| |
| @tzname_in_python2 |
| 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__, |
| repr(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 |
| |
| @tzname_in_python2 |
| 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, repr(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, string_types): |
| self._filename = fileobj |
| fileobj = open(fileobj, 'rb') |
| elif hasattr(fileobj, "name"): |
| self._filename = fileobj.name |
| else: |
| self._filename = repr(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).decode() != "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).decode() |
| |
| # 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. |
| |
| @tzname_in_python2 |
| 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__, repr(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 |
| |
| @tzname_in_python2 |
| 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__, repr(self._s)) |
| |
| class _tzicalvtzcomp(object): |
| 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 |
| |
| @tzname_in_python2 |
| def tzname(self, dt): |
| return self._find_comp(dt).tzname |
| |
| def __repr__(self): |
| return "<tzicalvtz %s>" % repr(self._tzid) |
| |
| __reduce__ = object.__reduce__ |
| |
| class tzical(object): |
| def __init__(self, fileobj): |
| global rrule |
| if not rrule: |
| from dateutil import rrule |
| |
| if isinstance(fileobj, string_types): |
| self._s = fileobj |
| fileobj = open(fileobj, 'r') # ical should be encoded in UTF-8 with CRLF |
| elif hasattr(fileobj, "name"): |
| self._s = fileobj.name |
| else: |
| self._s = repr(fileobj) |
| |
| self._vtz = {} |
| |
| self._parse_rfc(fileobj.read()) |
| |
| def keys(self): |
| return list(self._vtz.keys()) |
| |
| def get(self, tzid=None): |
| if tzid is None: |
| keys = list(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__, repr(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 |