| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986 |
- # -*- coding: utf-8 -*-
- """
- This module offers timezone implementations subclassing the abstract
- :py:`datetime.tzinfo` type. There are classes to handle tzfile format files
- (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, etc), TZ
- environment string (in all known formats), given ranges (with help from
- relative deltas), local machine timezone, fixed offset timezone, and UTC
- timezone.
- """
- import datetime
- import struct
- import time
- import sys
- import os
- from six import string_types, PY3
- try:
- from dateutil.tzwin import tzwin, tzwinlocal
- except ImportError:
- tzwin = tzwinlocal = None
- relativedelta = None
- parser = None
- rrule = None
- __all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
- "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz"]
- 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://ftp.iana.org/tz/tz*.tar.gz
- def __init__(self, fileobj, filename=None):
- file_opened_here = False
- if isinstance(fileobj, string_types):
- self._filename = fileobj
- fileobj = open(fileobj, 'rb')
- file_opened_here = True
- elif filename is not None:
- self._filename = filename
- 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).
- try:
- 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 **
- finally:
- if file_opened_here:
- fileobj.close()
- # 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
- # ical should be encoded in UTF-8 with CRLF
- fileobj = open(fileobj, 'r')
- 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 is not None:
- try:
- tz = tzwin(name)
- except WindowsError:
- tz = None
- 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
|