python-dateutil
Advanced tools
| from .tz import * | ||
| from six import PY3 | ||
| __all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange", | ||
| "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz"] | ||
| 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 |
| # -*- 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 | ||
| from .__init__ import tzname_in_python2 | ||
| try: | ||
| from .win import tzwin, tzwinlocal | ||
| except ImportError: | ||
| tzwin = tzwinlocal = None | ||
| relativedelta = None | ||
| parser = None | ||
| rrule = 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 | ||
| @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): | ||
| def __init__(self): | ||
| self._std_offset = datetime.timedelta(seconds=-time.timezone) | ||
| if time.daylight: | ||
| self._dst_offset = datetime.timedelta(seconds=-time.altzone) | ||
| else: | ||
| self._dst_offset = self._std_offset | ||
| def utcoffset(self, dt): | ||
| if dt is None: | ||
| return 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): | ||
| return (isinstance(other, tzlocal) and | ||
| (self._std_offset == other._std_offset and | ||
| self._dst_offset == other._dst_offset)) | ||
| 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 dt is None: | ||
| return None | ||
| 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 dt is None: | ||
| return None | ||
| 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): | ||
| if dt is None: | ||
| return None | ||
| 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 |
| # This code was originally contributed by Jeffrey Harris. | ||
| import datetime | ||
| import struct | ||
| from six.moves import winreg | ||
| try: | ||
| import ctypes | ||
| from ctypes import wintypes | ||
| except ValueError: | ||
| # ValueError is raised on non-Windows systems for some horrible reason. | ||
| raise ImportError("Running tzwin on non-Windows system") | ||
| from .__init__ import tzname_in_python2 | ||
| __all__ = ["tzwin", "tzwinlocal", "tzres"] | ||
| ONEWEEK = datetime.timedelta(7) | ||
| TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" | ||
| TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones" | ||
| TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" | ||
| def _settzkeyname(): | ||
| handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) | ||
| try: | ||
| winreg.OpenKey(handle, TZKEYNAMENT).Close() | ||
| TZKEYNAME = TZKEYNAMENT | ||
| except WindowsError: | ||
| TZKEYNAME = TZKEYNAME9X | ||
| handle.Close() | ||
| return TZKEYNAME | ||
| TZKEYNAME = _settzkeyname() | ||
| class tzres(object): | ||
| """ | ||
| Class for accessing `tzres.dll`, which contains timezone name related | ||
| resources. | ||
| ..versionadded:: 2.5.0 | ||
| """ | ||
| p_wchar = ctypes.POINTER(wintypes.WCHAR) # Pointer to a wide char | ||
| def __init__(self, tzres_loc='tzres.dll'): | ||
| # Load the user32 DLL so we can load strings from tzres | ||
| user32 = ctypes.WinDLL('user32') | ||
| # Specify the LoadStringW function | ||
| user32.LoadStringW.argtypes = (wintypes.HINSTANCE, | ||
| wintypes.UINT, | ||
| wintypes.LPWSTR, | ||
| ctypes.c_int) | ||
| self.LoadStringW = user32.LoadStringW | ||
| self._tzres = ctypes.WinDLL(tzres_loc) | ||
| self.tzres_loc = tzres_loc | ||
| def load_name(self, offset): | ||
| """ | ||
| Load a timezone name from a DLL offset (integer). | ||
| >>> from dateutil.tzwin import tzres | ||
| >>> tzr = tzres() | ||
| >>> print(tzr.load_name(112)) | ||
| 'Eastern Standard Time' | ||
| :param offset: | ||
| A positive integer value referring to a string from the tzres dll. | ||
| ..note: | ||
| Offsets found in the registry are generally of the form | ||
| `@tzres.dll,-114`. The offset in this case if 114, not -114. | ||
| """ | ||
| resource = self.p_wchar() | ||
| lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR) | ||
| nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0) | ||
| return resource[:nchar] | ||
| def name_from_string(self, tzname_str): | ||
| """ | ||
| Parse strings as returned from the Windows registry into the time zone | ||
| name as defined in the registry. | ||
| >>> from dateutil.tzwin import tzres | ||
| >>> tzr = tzres() | ||
| >>> print(tzr.name_from_string('@tzres.dll,-251')) | ||
| 'Dateline Daylight Time' | ||
| >>> print(tzr.name_from_string('Eastern Standard Time')) | ||
| 'Eastern Standard Time' | ||
| :param tzname_str: | ||
| A timezone name string as returned from a Windows registry key. | ||
| :return: | ||
| Returns the localized timezone string from tzres.dll if the string | ||
| is of the form `@tzres.dll,-offset`, else returns the input string. | ||
| """ | ||
| if not tzname_str.startswith('@'): | ||
| return tzname_str | ||
| name_splt = tzname_str.split(',-') | ||
| try: | ||
| offset = int(name_splt[1]) | ||
| except: | ||
| raise ValueError("Malformed timezone string.") | ||
| return self.load_name(offset) | ||
| class tzwinbase(datetime.tzinfo): | ||
| """tzinfo class based on win32's timezones available in the registry.""" | ||
| def __eq__(self, other): | ||
| # Compare on all relevant dimensions, including name. | ||
| return (isinstance(other, tzwinbase) and | ||
| (self._stdoffset == other._stdoffset and | ||
| self._dstoffset == other._dstoffset and | ||
| self._stddayofweek == other._stddayofweek and | ||
| self._dstdayofweek == other._dstdayofweek and | ||
| self._stdweeknumber == other._stdweeknumber and | ||
| self._dstweeknumber == other._dstweeknumber and | ||
| self._stdhour == other._stdhour and | ||
| self._dsthour == other._dsthour and | ||
| self._stdminute == other._stdminute and | ||
| self._dstminute == other._dstminute and | ||
| self._stdname == other._stdname and | ||
| self._dstname == other._dstname)) | ||
| def __ne__(self, other): | ||
| return not self.__eq__(other) | ||
| def utcoffset(self, dt): | ||
| if self._isdst(dt): | ||
| return datetime.timedelta(minutes=self._dstoffset) | ||
| else: | ||
| return datetime.timedelta(minutes=self._stdoffset) | ||
| def dst(self, dt): | ||
| if self._isdst(dt): | ||
| minutes = self._dstoffset - self._stdoffset | ||
| return datetime.timedelta(minutes=minutes) | ||
| else: | ||
| return datetime.timedelta(0) | ||
| @tzname_in_python2 | ||
| def tzname(self, dt): | ||
| if self._isdst(dt): | ||
| return self._dstname | ||
| else: | ||
| return self._stdname | ||
| @staticmethod | ||
| def list(): | ||
| """Return a list of all time zones known to the system.""" | ||
| handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) | ||
| tzkey = winreg.OpenKey(handle, TZKEYNAME) | ||
| result = [winreg.EnumKey(tzkey, i) | ||
| for i in range(winreg.QueryInfoKey(tzkey)[0])] | ||
| tzkey.Close() | ||
| handle.Close() | ||
| return result | ||
| def display(self): | ||
| return self._display | ||
| def _isdst(self, dt): | ||
| if not self._dstmonth: | ||
| # dstmonth == 0 signals the zone has no daylight saving time | ||
| return False | ||
| dston = picknthweekday(dt.year, self._dstmonth, self._dstdayofweek, | ||
| self._dsthour, self._dstminute, | ||
| self._dstweeknumber) | ||
| dstoff = picknthweekday(dt.year, self._stdmonth, self._stddayofweek, | ||
| self._stdhour, self._stdminute, | ||
| self._stdweeknumber) | ||
| if dston < dstoff: | ||
| return dston <= dt.replace(tzinfo=None) < dstoff | ||
| else: | ||
| return not dstoff <= dt.replace(tzinfo=None) < dston | ||
| class tzwin(tzwinbase): | ||
| def __init__(self, name): | ||
| self._name = name | ||
| # multiple contexts only possible in 2.7 and 3.1, we still support 2.6 | ||
| with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: | ||
| with winreg.OpenKey(handle, | ||
| "%s\%s" % (TZKEYNAME, name)) as tzkey: | ||
| keydict = valuestodict(tzkey) | ||
| self._stdname = keydict["Std"] | ||
| self._dstname = keydict["Dlt"] | ||
| self._display = keydict["Display"] | ||
| # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm | ||
| tup = struct.unpack("=3l16h", keydict["TZI"]) | ||
| self._stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1 | ||
| self._dstoffset = self._stdoffset-tup[2] # + DaylightBias * -1 | ||
| # for the meaning see the win32 TIME_ZONE_INFORMATION structure docs | ||
| # http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx | ||
| (self._stdmonth, | ||
| self._stddayofweek, # Sunday = 0 | ||
| self._stdweeknumber, # Last = 5 | ||
| self._stdhour, | ||
| self._stdminute) = tup[4:9] | ||
| (self._dstmonth, | ||
| self._dstdayofweek, # Sunday = 0 | ||
| self._dstweeknumber, # Last = 5 | ||
| self._dsthour, | ||
| self._dstminute) = tup[12:17] | ||
| def __repr__(self): | ||
| return "tzwin(%s)" % repr(self._name) | ||
| def __reduce__(self): | ||
| return (self.__class__, (self._name,)) | ||
| class tzwinlocal(tzwinbase): | ||
| def __init__(self): | ||
| with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: | ||
| with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey: | ||
| keydict = valuestodict(tzlocalkey) | ||
| self._stdname = keydict["StandardName"] | ||
| self._dstname = keydict["DaylightName"] | ||
| try: | ||
| with winreg.OpenKey( | ||
| handle, "%s\%s" % (TZKEYNAME, self._stdname)) as tzkey: | ||
| _keydict = valuestodict(tzkey) | ||
| self._display = _keydict["Display"] | ||
| except OSError: | ||
| self._display = None | ||
| self._stdoffset = -keydict["Bias"]-keydict["StandardBias"] | ||
| self._dstoffset = self._stdoffset-keydict["DaylightBias"] | ||
| # For reasons unclear, in this particular key, the day of week has been | ||
| # moved to the END of the SYSTEMTIME structure. | ||
| tup = struct.unpack("=8h", keydict["StandardStart"]) | ||
| (self._stdmonth, | ||
| self._stdweeknumber, # Last = 5 | ||
| self._stdhour, | ||
| self._stdminute) = tup[1:5] | ||
| self._stddayofweek = tup[7] | ||
| tup = struct.unpack("=8h", keydict["DaylightStart"]) | ||
| (self._dstmonth, | ||
| self._dstweeknumber, # Last = 5 | ||
| self._dsthour, | ||
| self._dstminute) = tup[1:5] | ||
| self._dstdayofweek = tup[7] | ||
| def __repr__(self): | ||
| return "tzwinlocal()" | ||
| def __str__(self): | ||
| # str will return the standard name, not the daylight name. | ||
| return "tzwinlocal(%s)" % repr(self._stdname) | ||
| def __reduce__(self): | ||
| return (self.__class__, ()) | ||
| def picknthweekday(year, month, dayofweek, hour, minute, whichweek): | ||
| """ dayofweek == 0 means Sunday, whichweek 5 means last instance """ | ||
| first = datetime.datetime(year, month, 1, hour, minute) | ||
| # This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6), | ||
| # Because 7 % 7 = 0 | ||
| weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1) | ||
| wd = weekdayone + ((whichweek - 1) * ONEWEEK) | ||
| if (wd.month != month): | ||
| wd -= ONEWEEK | ||
| return wd | ||
| def valuestodict(key): | ||
| """Convert a registry key's values to a dictionary.""" | ||
| dout = {} | ||
| size = winreg.QueryInfoKey(key)[1] | ||
| tz_res = None | ||
| for i in range(size): | ||
| key_name, value, dtype = winreg.EnumValue(key, i) | ||
| if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN: | ||
| # If it's a DWORD (32-bit integer), it's stored as unsigned - convert | ||
| # that to a proper signed integer | ||
| if value & (1 << 31): | ||
| value = value - (1 << 32) | ||
| elif dtype == winreg.REG_SZ: | ||
| # If it's a reference to the tzres DLL, load the actual string | ||
| if value.startswith('@tzres'): | ||
| tz_res = tz_res or tzres() | ||
| value = tz_res.name_from_string(value) | ||
| value = value.rstrip('\x00') # Remove trailing nulls | ||
| dout[key_name] = value | ||
| return dout |
| import logging | ||
| import os | ||
| import tempfile | ||
| import shutil | ||
| import json | ||
| from subprocess import check_call | ||
| from dateutil.zoneinfo import tar_open, METADATA_FN, ZONEFILENAME | ||
| def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None): | ||
| """Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar* | ||
| filename is the timezone tarball from ftp.iana.org/tz. | ||
| """ | ||
| tmpdir = tempfile.mkdtemp() | ||
| zonedir = os.path.join(tmpdir, "zoneinfo") | ||
| moduledir = os.path.dirname(__file__) | ||
| try: | ||
| with tar_open(filename) as tf: | ||
| for name in zonegroups: | ||
| tf.extract(name, tmpdir) | ||
| filepaths = [os.path.join(tmpdir, n) for n in zonegroups] | ||
| try: | ||
| check_call(["zic", "-d", zonedir] + filepaths) | ||
| except OSError as e: | ||
| _print_on_nosuchfile(e) | ||
| raise | ||
| # write metadata file | ||
| with open(os.path.join(zonedir, METADATA_FN), 'w') as f: | ||
| json.dump(metadata, f, indent=4, sort_keys=True) | ||
| target = os.path.join(moduledir, ZONEFILENAME) | ||
| with tar_open(target, "w:%s" % format) as tf: | ||
| for entry in os.listdir(zonedir): | ||
| entrypath = os.path.join(zonedir, entry) | ||
| tf.add(entrypath, entry) | ||
| finally: | ||
| shutil.rmtree(tmpdir) | ||
| def _print_on_nosuchfile(e): | ||
| """Print helpful troubleshooting message | ||
| e is an exception raised by subprocess.check_call() | ||
| """ | ||
| if e.errno == 2: | ||
| logging.error( | ||
| "Could not find zic. Perhaps you need to install " | ||
| "libc-bin or some other package that provides it, " | ||
| "or it's not in your PATH?") |
| # -*- coding: utf-8 -*- | ||
| __version__ = "2.4.2" | ||
| __version__ = "2.5.0" |
+360
-212
@@ -6,2 +6,15 @@ # -*- coding:iso-8859-1 -*- | ||
| This module attempts to be forgiving with regards to unlikely input formats, | ||
| returning a datetime object even for dates which are ambiguous. If an element | ||
| of a date/time stamp is omitted, the following rules are applied: | ||
| - If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour | ||
| on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is | ||
| specified. | ||
| - If a time zone is omitted, a timezone-naive datetime is returned. | ||
| If any other elements are missing, they are taken from the | ||
| :class:`datetime.datetime` object passed to the parameter ``default``. If this | ||
| results in a day number exceeding the valid number of days per month, the | ||
| value falls back to the end of the month. | ||
| Additional resources about date/time string formats can be found below: | ||
@@ -24,3 +37,5 @@ | ||
| import collections | ||
| import re | ||
| from io import StringIO | ||
| from calendar import monthrange, isleap | ||
@@ -36,4 +51,9 @@ from six import text_type, binary_type, integer_types | ||
| class _timelex(object): | ||
| # Fractional seconds are sometimes split by a comma | ||
| _split_decimal = re.compile("([\.,])") | ||
| def __init__(self, instream): | ||
| if isinstance(instream, binary_type): | ||
| instream = instream.decode() | ||
| if isinstance(instream, text_type): | ||
@@ -43,8 +63,2 @@ instream = StringIO(instream) | ||
| self.instream = instream | ||
| self.wordchars = ('abcdfeghijklmnopqrstuvwxyz' | ||
| 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_' | ||
| '��������������������������������' | ||
| '������������������������������') | ||
| self.numchars = '0123456789' | ||
| self.whitespace = ' \t\r\n' | ||
| self.charstack = [] | ||
@@ -58,4 +72,4 @@ self.tokenstack = [] | ||
| can be parsed by the parser. Lexical units are demarcated by changes in | ||
| the character set, so any continuous string of letters is considered one | ||
| unit, any continuous string of numbers is considered one unit. | ||
| the character set, so any continuous string of letters is considered | ||
| one unit, any continuous string of numbers is considered one unit. | ||
@@ -75,10 +89,7 @@ The main complication arises from the fact that dots ('.') can be used | ||
| state = None | ||
| wordchars = self.wordchars | ||
| numchars = self.numchars | ||
| whitespace = self.whitespace | ||
| while not self.eof: | ||
| # We only realize that we've reached the end of a token when we find | ||
| # a character that's not part of the current token - since that | ||
| # character may be part of the next token, it's stored in the | ||
| # We only realize that we've reached the end of a token when we | ||
| # find a character that's not part of the current token - since | ||
| # that character may be part of the next token, it's stored in the | ||
| # charstack. | ||
@@ -99,7 +110,7 @@ if self.charstack: | ||
| token = nextchar | ||
| if nextchar in wordchars: | ||
| if self.isword(nextchar): | ||
| state = 'a' | ||
| elif nextchar in numchars: | ||
| elif self.isnum(nextchar): | ||
| state = '0' | ||
| elif nextchar in whitespace: | ||
| elif self.isspace(nextchar): | ||
| token = ' ' | ||
@@ -113,3 +124,3 @@ break # emit token | ||
| seenletters = True | ||
| if nextchar in wordchars: | ||
| if self.isword(nextchar): | ||
| token += nextchar | ||
@@ -125,5 +136,5 @@ elif nextchar == '.': | ||
| # numbers until we find something that doesn't fit. | ||
| if nextchar in numchars: | ||
| if self.isnum(nextchar): | ||
| token += nextchar | ||
| elif nextchar == '.': | ||
| elif nextchar == '.' or (nextchar == ',' and len(token) >= 2): | ||
| token += nextchar | ||
@@ -138,5 +149,5 @@ state = '0.' | ||
| seenletters = True | ||
| if nextchar == '.' or nextchar in wordchars: | ||
| if nextchar == '.' or self.isword(nextchar): | ||
| token += nextchar | ||
| elif nextchar in numchars and token[-1] == '.': | ||
| elif self.isnum(nextchar) and token[-1] == '.': | ||
| token += nextchar | ||
@@ -150,5 +161,5 @@ state = '0.' | ||
| # break up the tokens later. | ||
| if nextchar == '.' or nextchar in numchars: | ||
| if nextchar == '.' or self.isnum(nextchar): | ||
| token += nextchar | ||
| elif nextchar in wordchars and token[-1] == '.': | ||
| elif self.isword(nextchar) and token[-1] == '.': | ||
| token += nextchar | ||
@@ -161,10 +172,12 @@ state = 'a.' | ||
| if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or | ||
| token[-1] == '.')): | ||
| l = token.split('.') | ||
| token[-1] in '.,')): | ||
| l = self._split_decimal.split(token) | ||
| token = l[0] | ||
| for tok in l[1:]: | ||
| self.tokenstack.append('.') | ||
| if tok: | ||
| self.tokenstack.append(tok) | ||
| if state == '0.' and token.count('.') == 0: | ||
| token = token.replace(',', '.') | ||
| return token | ||
@@ -185,7 +198,22 @@ | ||
| @classmethod | ||
| def split(cls, s): | ||
| return list(cls(s)) | ||
| split = classmethod(split) | ||
| @classmethod | ||
| def isword(cls, nextchar): | ||
| """ Whether or not the next character is part of a word """ | ||
| return nextchar.isalpha() | ||
| @classmethod | ||
| def isnum(cls, nextchar): | ||
| """ Whether the next character is part of a number """ | ||
| return nextchar.isdigit() | ||
| @classmethod | ||
| def isspace(cls, nextchar): | ||
| """ Whether the next character is whitespace """ | ||
| return nextchar.isspace() | ||
| class _resultbase(object): | ||
@@ -205,2 +233,6 @@ | ||
| def __len__(self): | ||
| return (sum(getattr(self, attr) is not None | ||
| for attr in self.__slots__)) | ||
| def __repr__(self): | ||
@@ -212,16 +244,16 @@ return self._repr(self.__class__.__name__) | ||
| """ | ||
| Class which handles what inputs are accepted. Subclass this to customize the | ||
| language and acceptable values for each parameter. | ||
| Class which handles what inputs are accepted. Subclass this to customize | ||
| the language and acceptable values for each parameter. | ||
| :param dayfirst: | ||
| Whether to interpret the first value in an ambiguous 3-integer date | ||
| (e.g. 01/05/09) as the day (`True`) or month (`False`). If | ||
| `yearfirst` is set to `True`, this distinguishes between YDM and | ||
| YMD. Default is `False`. | ||
| (e.g. 01/05/09) as the day (``True``) or month (``False``). If | ||
| ``yearfirst`` is set to ``True``, this distinguishes between YDM | ||
| and YMD. Default is ``False``. | ||
| :param yearfirst: | ||
| Whether to interpret the first value in an ambiguous 3-integer date | ||
| (e.g. 01/05/09) as the year. If `True`, the first number is taken to | ||
| be the year, otherwise the last number is taken to be the year. | ||
| Default is `False`. | ||
| (e.g. 01/05/09) as the year. If ``True``, the first number is taken | ||
| to be the year, otherwise the last number is taken to be the year. | ||
| Default is ``False``. | ||
| """ | ||
@@ -275,3 +307,3 @@ | ||
| self._year = time.localtime().tm_year | ||
| self._century = self._year // 100*100 | ||
| self._century = self._year // 100 * 100 | ||
@@ -302,3 +334,3 @@ def _convert(self, lst): | ||
| try: | ||
| return self._months[name.lower()]+1 | ||
| return self._months[name.lower()] + 1 | ||
| except KeyError: | ||
@@ -332,6 +364,6 @@ pass | ||
| def convertyear(self, year): | ||
| if year < 100: | ||
| def convertyear(self, year, century_specified=False): | ||
| if year < 100 and not century_specified: | ||
| year += self._century | ||
| if abs(year-self._year) >= 50: | ||
| if abs(year - self._year) >= 50: | ||
| if year < self._year: | ||
@@ -346,3 +378,3 @@ year += 100 | ||
| if res.year is not None: | ||
| res.year = self.convertyear(res.year) | ||
| res.year = self.convertyear(res.year, res.century_specified) | ||
@@ -357,11 +389,117 @@ if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z': | ||
| class _ymd(list): | ||
| def __init__(self, tzstr, *args, **kwargs): | ||
| super(self.__class__, self).__init__(*args, **kwargs) | ||
| self.century_specified = False | ||
| self.tzstr = tzstr | ||
| @staticmethod | ||
| def token_could_be_year(token, year): | ||
| try: | ||
| return int(token) == year | ||
| except ValueError: | ||
| return False | ||
| @staticmethod | ||
| def find_potential_year_tokens(year, tokens): | ||
| return [token for token in tokens if _ymd.token_could_be_year(token, year)] | ||
| def find_probable_year_index(self, tokens): | ||
| """ | ||
| attempt to deduce if a pre 100 year was lost | ||
| due to padded zeros being taken off | ||
| """ | ||
| for index, token in enumerate(self): | ||
| potential_year_tokens = _ymd.find_potential_year_tokens(token, tokens) | ||
| if len(potential_year_tokens) == 1 and len(potential_year_tokens[0]) > 2: | ||
| return index | ||
| def append(self, val): | ||
| if hasattr(val, '__len__'): | ||
| if val.isdigit() and len(val) > 2: | ||
| self.century_specified = True | ||
| elif val > 100: | ||
| self.century_specified = True | ||
| super(self.__class__, self).append(int(val)) | ||
| def resolve_ymd(self, mstridx, yearfirst, dayfirst): | ||
| len_ymd = len(self) | ||
| year, month, day = (None, None, None) | ||
| if len_ymd > 3: | ||
| raise ValueError("More than three YMD values") | ||
| elif len_ymd == 1 or (mstridx != -1 and len_ymd == 2): | ||
| # One member, or two members with a month string | ||
| if mstridx != -1: | ||
| month = self[mstridx] | ||
| del self[mstridx] | ||
| if len_ymd > 1 or mstridx == -1: | ||
| if self[0] > 31: | ||
| year = self[0] | ||
| else: | ||
| day = self[0] | ||
| elif len_ymd == 2: | ||
| # Two members with numbers | ||
| if self[0] > 31: | ||
| # 99-01 | ||
| year, month = self | ||
| elif self[1] > 31: | ||
| # 01-99 | ||
| month, year = self | ||
| elif dayfirst and self[1] <= 12: | ||
| # 13-01 | ||
| day, month = self | ||
| else: | ||
| # 01-13 | ||
| month, day = self | ||
| elif len_ymd == 3: | ||
| # Three members | ||
| if mstridx == 0: | ||
| month, day, year = self | ||
| elif mstridx == 1: | ||
| if self[0] > 31 or (yearfirst and self[2] <= 31): | ||
| # 99-Jan-01 | ||
| year, month, day = self | ||
| else: | ||
| # 01-Jan-01 | ||
| # Give precendence to day-first, since | ||
| # two-digit years is usually hand-written. | ||
| day, month, year = self | ||
| elif mstridx == 2: | ||
| # WTF!? | ||
| if self[1] > 31: | ||
| # 01-99-Jan | ||
| day, year, month = self | ||
| else: | ||
| # 99-01-Jan | ||
| year, day, month = self | ||
| else: | ||
| if self[0] > 31 or \ | ||
| self.find_probable_year_index(_timelex.split(self.tzstr)) == 0 or \ | ||
| (yearfirst and self[1] <= 12 and self[2] <= 31): | ||
| # 99-01-01 | ||
| year, month, day = self | ||
| elif self[0] > 12 or (dayfirst and self[1] <= 12): | ||
| # 13-01-01 | ||
| day, month, year = self | ||
| else: | ||
| # 01-13-01 | ||
| month, day, year = self | ||
| return year, month, day | ||
| class parser(object): | ||
| def __init__(self, info=None): | ||
| self.info = info or parserinfo() | ||
| def parse(self, timestr, default=None, ignoretz=False, tzinfos=None, | ||
| **kwargs): | ||
| def parse(self, timestr, default=None, ignoretz=False, tzinfos=None, **kwargs): | ||
| """ | ||
| Parse the date/time string into a datetime object. | ||
| Parse the date/time string into a :class:`datetime.datetime` object. | ||
@@ -373,28 +511,49 @@ :param timestr: | ||
| The default datetime object, if this is a datetime object and not | ||
| `None`, elements specified in `timestr` replace elements in the | ||
| ``None``, elements specified in ``timestr`` replace elements in the | ||
| default object. | ||
| :param ignoretz: | ||
| Whether or not to ignore the time zone. | ||
| If set ``True``, time zones in parsed strings are ignored and a | ||
| naive :class:`datetime.datetime` object is returned. | ||
| :param tzinfos: | ||
| A time zone, to be applied to the date, if `ignoretz` is `True`. | ||
| This can be either a subclass of `tzinfo`, a time zone string or an | ||
| integer offset. | ||
| Additional time zone names / aliases which may be present in the | ||
| string. This argument maps time zone names (and optionally offsets | ||
| from those time zones) to time zones. This parameter can be a | ||
| dictionary with timezone aliases mapping time zone names to time | ||
| zones or a function taking two parameters (``tzname`` and | ||
| ``tzoffset``) and returning a time zone. | ||
| The timezones to which the names are mapped can be an integer | ||
| offset from UTC in minutes or a :class:`tzinfo` object. | ||
| .. doctest:: | ||
| :options: +NORMALIZE_WHITESPACE | ||
| >>> from dateutil.parser import parse | ||
| >>> from dateutil.tz import gettz | ||
| >>> tzinfos = {"BRST": -10800, "CST": gettz("America/Chicago")} | ||
| >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) | ||
| datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -10800)) | ||
| >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) | ||
| datetime.datetime(2012, 1, 19, 17, 21, | ||
| tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) | ||
| This parameter is ignored if ``ignoretz`` is set. | ||
| :param **kwargs: | ||
| Keyword arguments as passed to `_parse()`. | ||
| Keyword arguments as passed to ``_parse()``. | ||
| :return: | ||
| Returns a `datetime.datetime` object or, if the `fuzzy_with_tokens` | ||
| option is `True`, returns a tuple, the first element being a | ||
| `datetime.datetime` object, the second a tuple containing the | ||
| fuzzy tokens. | ||
| Returns a :class:`datetime.datetime` object or, if the | ||
| ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the | ||
| first element being a :class:`datetime.datetime` object, the second | ||
| a tuple containing the fuzzy tokens. | ||
| :raises ValueError: | ||
| Raised for invalid or unknown string format, if the provided | ||
| `tzinfo` is not in a valid format, or if an invalid date would | ||
| be created. | ||
| :class:`tzinfo` is not in a valid format, or if an invalid date | ||
| would be created. | ||
| :raises OverFlowError: | ||
| :raises OverflowError: | ||
| Raised if the parsed date exceeds the largest valid C integer on | ||
@@ -404,19 +563,20 @@ your system. | ||
| default_specified = default is not None | ||
| if not default_specified: | ||
| if default is None: | ||
| effective_dt = datetime.datetime.now() | ||
| default = datetime.datetime.now().replace(hour=0, minute=0, | ||
| second=0, microsecond=0) | ||
| if kwargs.get('fuzzy_with_tokens', False): | ||
| res, skipped_tokens = self._parse(timestr, **kwargs) | ||
| else: | ||
| res = self._parse(timestr, **kwargs) | ||
| effective_dt = default | ||
| res, skipped_tokens = self._parse(timestr, **kwargs) | ||
| if res is None: | ||
| raise ValueError("Unknown string format") | ||
| if len(res) == 0: | ||
| raise ValueError("String does not contain a date.") | ||
| repl = {} | ||
| for attr in ["year", "month", "day", "hour", | ||
| "minute", "second", "microsecond"]: | ||
| for attr in ("year", "month", "day", "hour", | ||
| "minute", "second", "microsecond"): | ||
| value = getattr(res, attr) | ||
@@ -426,2 +586,12 @@ if value is not None: | ||
| if 'day' not in repl: | ||
| # If the default day exceeds the last day of the month, fall back to | ||
| # the end of the month. | ||
| cyear = default.year if res.year is None else res.year | ||
| cmonth = default.month if res.month is None else res.month | ||
| cday = default.day if res.day is None else res.day | ||
| if cday > monthrange(cyear, cmonth)[1]: | ||
| repl['day'] = monthrange(cyear, cmonth)[1] | ||
| ret = default.replace(**repl) | ||
@@ -472,3 +642,3 @@ | ||
| Private method which performs the heavy lifting of parsing, called from | ||
| `parse()`, which passes on its `kwargs` to this function. | ||
| ``parse()``, which passes on its ``kwargs`` to this function. | ||
@@ -480,13 +650,14 @@ :param timestr: | ||
| Whether to interpret the first value in an ambiguous 3-integer date | ||
| (e.g. 01/05/09) as the day (`True`) or month (`False`). If | ||
| `yearfirst` is set to `True`, this distinguishes between YDM and | ||
| YMD. If set to `None`, this value is retrieved from the current | ||
| `parserinfo` object (which itself defaults to `False`). | ||
| (e.g. 01/05/09) as the day (``True``) or month (``False``). If | ||
| ``yearfirst`` is set to ``True``, this distinguishes between YDM | ||
| and YMD. If set to ``None``, this value is retrieved from the | ||
| current :class:`parserinfo` object (which itself defaults to | ||
| ``False``). | ||
| :param yearfirst: | ||
| Whether to interpret the first value in an ambiguous 3-integer date | ||
| (e.g. 01/05/09) as the year. If `True`, the first number is taken to | ||
| be the year, otherwise the last number is taken to be the year. If | ||
| this is set to `None`, the value is retrieved from the current | ||
| `parserinfo` object (which itself defaults to `False`). | ||
| (e.g. 01/05/09) as the year. If ``True``, the first number is taken | ||
| to be the year, otherwise the last number is taken to be the year. | ||
| If this is set to ``None``, the value is retrieved from the current | ||
| :class:`parserinfo` object (which itself defaults to ``False``). | ||
@@ -498,8 +669,13 @@ :param fuzzy: | ||
| :param fuzzy_with_tokens: | ||
| If `True`, `fuzzy` is automatically set to True, and the parser will | ||
| return a tuple where the first element is the parsed | ||
| `datetime.datetime` datetimestamp and the second element is a tuple | ||
| containing the portions of the string which were ignored, e.g. | ||
| "Today is January 1, 2047 at 8:21:00AM" should return | ||
| `(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))` | ||
| If ``True``, ``fuzzy`` is automatically set to True, and the parser | ||
| will return a tuple where the first element is the parsed | ||
| :class:`datetime.datetime` datetimestamp and the second element is | ||
| a tuple containing the portions of the string which were ignored: | ||
| .. doctest:: | ||
| >>> from dateutil.parser import parse | ||
| >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) | ||
| (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) | ||
| """ | ||
@@ -527,3 +703,3 @@ if fuzzy_with_tokens: | ||
| # year/month/day list | ||
| ymd = [] | ||
| ymd = _ymd(timestr) | ||
@@ -564,5 +740,7 @@ # Index of the month string in ymd | ||
| if not ymd and l[i-1].find('.') == -1: | ||
| ymd.append(info.convertyear(int(s[:2]))) | ||
| ymd.append(int(s[2:4])) | ||
| ymd.append(int(s[4:])) | ||
| #ymd.append(info.convertyear(int(s[:2]))) | ||
| ymd.append(s[:2]) | ||
| ymd.append(s[2:4]) | ||
| ymd.append(s[4:]) | ||
| else: | ||
@@ -574,20 +752,15 @@ # 19990101T235959[.59] | ||
| elif len_li == 8: | ||
| elif len_li in (8, 12, 14): | ||
| # YYYYMMDD | ||
| s = l[i-1] | ||
| ymd.append(int(s[:4])) | ||
| ymd.append(int(s[4:6])) | ||
| ymd.append(int(s[6:])) | ||
| ymd.append(s[:4]) | ||
| ymd.append(s[4:6]) | ||
| ymd.append(s[6:8]) | ||
| elif len_li in (12, 14): | ||
| # YYYYMMDDhhmm[ss] | ||
| s = l[i-1] | ||
| ymd.append(int(s[:4])) | ||
| ymd.append(int(s[4:6])) | ||
| ymd.append(int(s[6:8])) | ||
| res.hour = int(s[8:10]) | ||
| res.minute = int(s[10:12]) | ||
| if len_li > 8: | ||
| res.hour = int(s[8:10]) | ||
| res.minute = int(s[10:12]) | ||
| if len_li == 14: | ||
| res.second = int(s[12:]) | ||
| if len_li > 12: | ||
| res.second = int(s[12:]) | ||
@@ -675,3 +848,3 @@ elif ((i < len_l and info.hms(l[i]) is not None) or | ||
| sep = l[i] | ||
| ymd.append(int(value)) | ||
| ymd.append(value_repr) | ||
| i += 1 | ||
@@ -682,3 +855,3 @@ | ||
| # 01-01[-01] | ||
| ymd.append(int(l[i])) | ||
| ymd.append(l[i]) | ||
| except ValueError: | ||
@@ -693,3 +866,3 @@ # 01-Jan[-01] | ||
| else: | ||
| return None | ||
| return None, None | ||
@@ -708,3 +881,3 @@ i += 1 | ||
| else: | ||
| ymd.append(int(l[i])) | ||
| ymd.append(l[i]) | ||
@@ -725,3 +898,3 @@ i += 1 | ||
| # Year, month or day | ||
| ymd.append(int(value)) | ||
| ymd.append(value) | ||
| i += 1 | ||
@@ -740,3 +913,3 @@ elif info.ampm(l[i]) is not None: | ||
| elif not fuzzy: | ||
| return None | ||
| return None, None | ||
| else: | ||
@@ -766,3 +939,3 @@ i += 1 | ||
| i += 1 | ||
| ymd.append(int(l[i])) | ||
| ymd.append(l[i]) | ||
| i += 1 | ||
@@ -773,3 +946,3 @@ | ||
| i += 1 | ||
| ymd.append(int(l[i])) | ||
| ymd.append(l[i]) | ||
| i += 1 | ||
@@ -788,3 +961,3 @@ | ||
| # Convert it here to become unambiguous | ||
| ymd.append(info.convertyear(value)) | ||
| ymd.append(str(info.convertyear(value))) | ||
| i += 4 | ||
@@ -873,3 +1046,3 @@ continue | ||
| else: | ||
| return None | ||
| return None, None | ||
| i += 1 | ||
@@ -892,3 +1065,3 @@ | ||
| if not (info.jump(l[i]) or fuzzy): | ||
| return None | ||
| return None, None | ||
@@ -905,73 +1078,18 @@ if last_skipped_token_i == i - 1: | ||
| # Process year/month/day | ||
| len_ymd = len(ymd) | ||
| if len_ymd > 3: | ||
| # More than three members!? | ||
| return None | ||
| elif len_ymd == 1 or (mstridx != -1 and len_ymd == 2): | ||
| # One member, or two members with a month string | ||
| if mstridx != -1: | ||
| res.month = ymd[mstridx] | ||
| del ymd[mstridx] | ||
| year, month, day = ymd.resolve_ymd(mstridx, yearfirst, dayfirst) | ||
| if year is not None: | ||
| res.year = year | ||
| res.century_specified = ymd.century_specified | ||
| if len_ymd > 1 or mstridx == -1: | ||
| if ymd[0] > 31: | ||
| res.year = ymd[0] | ||
| else: | ||
| res.day = ymd[0] | ||
| if month is not None: | ||
| res.month = month | ||
| elif len_ymd == 2: | ||
| # Two members with numbers | ||
| if ymd[0] > 31: | ||
| # 99-01 | ||
| res.year, res.month = ymd | ||
| elif ymd[1] > 31: | ||
| # 01-99 | ||
| res.month, res.year = ymd | ||
| elif dayfirst and ymd[1] <= 12: | ||
| # 13-01 | ||
| res.day, res.month = ymd | ||
| else: | ||
| # 01-13 | ||
| res.month, res.day = ymd | ||
| if day is not None: | ||
| res.day = day | ||
| elif len_ymd == 3: | ||
| # Three members | ||
| if mstridx == 0: | ||
| res.month, res.day, res.year = ymd | ||
| elif mstridx == 1: | ||
| if ymd[0] > 31 or (yearfirst and ymd[2] <= 31): | ||
| # 99-Jan-01 | ||
| res.year, res.month, res.day = ymd | ||
| else: | ||
| # 01-Jan-01 | ||
| # Give precendence to day-first, since | ||
| # two-digit years is usually hand-written. | ||
| res.day, res.month, res.year = ymd | ||
| elif mstridx == 2: | ||
| # WTF!? | ||
| if ymd[1] > 31: | ||
| # 01-99-Jan | ||
| res.day, res.year, res.month = ymd | ||
| else: | ||
| # 99-01-Jan | ||
| res.year, res.day, res.month = ymd | ||
| else: | ||
| if ymd[0] > 31 or \ | ||
| (yearfirst and ymd[1] <= 12 and ymd[2] <= 31): | ||
| # 99-01-01 | ||
| res.year, res.month, res.day = ymd | ||
| elif ymd[0] > 12 or (dayfirst and ymd[1] <= 12): | ||
| # 13-01-01 | ||
| res.day, res.month, res.year = ymd | ||
| else: | ||
| # 01-13-01 | ||
| res.month, res.day, res.year = ymd | ||
| except (IndexError, ValueError, AssertionError): | ||
| return None | ||
| return None, None | ||
| if not info.validate(res): | ||
| return None | ||
| return None, None | ||
@@ -981,3 +1099,3 @@ if fuzzy_with_tokens: | ||
| else: | ||
| return res | ||
| return res, None | ||
@@ -989,5 +1107,6 @@ DEFAULTPARSER = parser() | ||
| """ | ||
| Parse a string in one of the supported formats, using the `parserinfo` | ||
| parameters. | ||
| Parse a string in one of the supported formats, using the | ||
| ``parserinfo`` parameters. | ||
| :param timestr: | ||
@@ -998,33 +1117,54 @@ A string containing a date/time stamp. | ||
| A :class:`parserinfo` object containing parameters for the parser. | ||
| If `None`, the default arguments to the `parserinfo` constructor are | ||
| used. | ||
| If ``None``, the default arguments to the :class:`parserinfo` | ||
| constructor are used. | ||
| The `**kwargs` parameter takes the following keyword arguments: | ||
| The ``**kwargs`` parameter takes the following keyword arguments: | ||
| :param default: | ||
| The default datetime object, if this is a datetime object and not | ||
| `None`, elements specified in `timestr` replace elements in the | ||
| ``None``, elements specified in ``timestr`` replace elements in the | ||
| default object. | ||
| :param ignoretz: | ||
| Whether or not to ignore the time zone (boolean). | ||
| If set ``True``, time zones in parsed strings are ignored and a naive | ||
| :class:`datetime` object is returned. | ||
| :param tzinfos: | ||
| A time zone, to be applied to the date, if `ignoretz` is `True`. | ||
| This can be either a subclass of `tzinfo`, a time zone string or an | ||
| integer offset. | ||
| Additional time zone names / aliases which may be present in the | ||
| string. This argument maps time zone names (and optionally offsets | ||
| from those time zones) to time zones. This parameter can be a | ||
| dictionary with timezone aliases mapping time zone names to time | ||
| zones or a function taking two parameters (``tzname`` and | ||
| ``tzoffset``) and returning a time zone. | ||
| The timezones to which the names are mapped can be an integer | ||
| offset from UTC in minutes or a :class:`tzinfo` object. | ||
| .. doctest:: | ||
| :options: +NORMALIZE_WHITESPACE | ||
| >>> from dateutil.parser import parse | ||
| >>> from dateutil.tz import gettz | ||
| >>> tzinfos = {"BRST": -10800, "CST": gettz("America/Chicago")} | ||
| >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) | ||
| datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -10800)) | ||
| >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) | ||
| datetime.datetime(2012, 1, 19, 17, 21, | ||
| tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) | ||
| This parameter is ignored if ``ignoretz`` is set. | ||
| :param dayfirst: | ||
| Whether to interpret the first value in an ambiguous 3-integer date | ||
| (e.g. 01/05/09) as the day (`True`) or month (`False`). If | ||
| `yearfirst` is set to `True`, this distinguishes between YDM and | ||
| YMD. If set to `None`, this value is retrieved from the current | ||
| :class:`parserinfo` object (which itself defaults to `False`). | ||
| (e.g. 01/05/09) as the day (``True``) or month (``False``). If | ||
| ``yearfirst`` is set to ``True``, this distinguishes between YDM and | ||
| YMD. If set to ``None``, this value is retrieved from the current | ||
| :class:`parserinfo` object (which itself defaults to ``False``). | ||
| :param yearfirst: | ||
| Whether to interpret the first value in an ambiguous 3-integer date | ||
| (e.g. 01/05/09) as the year. If `True`, the first number is taken to | ||
| (e.g. 01/05/09) as the year. If ``True``, the first number is taken to | ||
| be the year, otherwise the last number is taken to be the year. If | ||
| this is set to `None`, the value is retrieved from the current | ||
| :class:`parserinfo` object (which itself defaults to `False`). | ||
| this is set to ``None``, the value is retrieved from the current | ||
| :class:`parserinfo` object (which itself defaults to ``False``). | ||
@@ -1036,15 +1176,28 @@ :param fuzzy: | ||
| :param fuzzy_with_tokens: | ||
| If `True`, `fuzzy` is automatically set to True, and the parser will | ||
| return a tuple where the first element is the parsed | ||
| `datetime.datetime` datetimestamp and the second element is a tuple | ||
| containing the portions of the string which were ignored, e.g. | ||
| "Today is January 1, 2047 at 8:21:00AM" should return | ||
| `(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))` | ||
| If ``True``, ``fuzzy`` is automatically set to True, and the parser | ||
| will return a tuple where the first element is the parsed | ||
| :class:`datetime.datetime` datetimestamp and the second element is | ||
| a tuple containing the portions of the string which were ignored: | ||
| .. doctest:: | ||
| >>> from dateutil.parser import parse | ||
| >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) | ||
| (datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) | ||
| :return: | ||
| Returns a :class:`datetime.datetime` object or, if the | ||
| ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the | ||
| first element being a :class:`datetime.datetime` object, the second | ||
| a tuple containing the fuzzy tokens. | ||
| :raises ValueError: | ||
| Raised for invalid or unknown string format, if the provided | ||
| :class:`tzinfo` is not in a valid format, or if an invalid date | ||
| would be created. | ||
| :raises OverflowError: | ||
| Raised if the parsed date exceeds the largest valid C integer on | ||
| your system. | ||
| """ | ||
| # Python 2.x support: datetimes return their string presentation as | ||
| # bytes in 2.x and unicode in 3.x, so it's reasonable to expect that | ||
| # the parser will get both kinds. Internally we use unicode only. | ||
| if isinstance(timestr, binary_type): | ||
| timestr = timestr.decode() | ||
| if parserinfo: | ||
@@ -1076,7 +1229,2 @@ return parser(parserinfo).parse(timestr, **kwargs) | ||
| def parse(self, tzstr): | ||
| # Python 2.x compatibility: tzstr should be converted to unicode before | ||
| # being passed to _timelex. | ||
| if isinstance(tzstr, binary_type): | ||
| tzstr = tzstr.decode() | ||
| res = self._result() | ||
@@ -1083,0 +1231,0 @@ l = _timelex.split(tzstr) |
+166
-100
@@ -6,2 +6,3 @@ # -*- coding: utf-8 -*- | ||
| from six import integer_types | ||
| from warnings import warn | ||
@@ -44,71 +45,71 @@ __all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] | ||
| """ | ||
| The relativedelta type is based on the specification of the excellent | ||
| work done by M.-A. Lemburg in his | ||
| `mx.DateTime <http://www.egenix.com/files/python/mxDateTime.html>`_ extension. | ||
| However, notice that this type does *NOT* implement the same algorithm as | ||
| his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. | ||
| The relativedelta type is based on the specification of the excellent | ||
| work done by M.-A. Lemburg in his | ||
| `mx.DateTime <http://www.egenix.com/files/python/mxDateTime.html>`_ extension. | ||
| However, notice that this type does *NOT* implement the same algorithm as | ||
| his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. | ||
| There are two different ways to build a relativedelta instance. The | ||
| first one is passing it two date/datetime classes:: | ||
| There are two different ways to build a relativedelta instance. The | ||
| first one is passing it two date/datetime classes:: | ||
| relativedelta(datetime1, datetime2) | ||
| relativedelta(datetime1, datetime2) | ||
| The second one is passing it any number of the following keyword arguments:: | ||
| The second one is passing it any number of the following keyword arguments:: | ||
| relativedelta(arg1=x,arg2=y,arg3=z...) | ||
| relativedelta(arg1=x,arg2=y,arg3=z...) | ||
| year, month, day, hour, minute, second, microsecond: | ||
| Absolute information (argument is singular); adding or subtracting a | ||
| relativedelta with absolute information does not perform an aritmetic | ||
| operation, but rather REPLACES the corresponding value in the | ||
| original datetime with the value(s) in relativedelta. | ||
| year, month, day, hour, minute, second, microsecond: | ||
| Absolute information (argument is singular); adding or subtracting a | ||
| relativedelta with absolute information does not perform an aritmetic | ||
| operation, but rather REPLACES the corresponding value in the | ||
| original datetime with the value(s) in relativedelta. | ||
| years, months, weeks, days, hours, minutes, seconds, microseconds: | ||
| Relative information, may be negative (argument is plural); adding | ||
| or subtracting a relativedelta with relative information performs | ||
| the corresponding aritmetic operation on the original datetime value | ||
| with the information in the relativedelta. | ||
| years, months, weeks, days, hours, minutes, seconds, microseconds: | ||
| Relative information, may be negative (argument is plural); adding | ||
| or subtracting a relativedelta with relative information performs | ||
| the corresponding aritmetic operation on the original datetime value | ||
| with the information in the relativedelta. | ||
| weekday: | ||
| One of the weekday instances (MO, TU, etc). These instances may | ||
| receive a parameter N, specifying the Nth weekday, which could | ||
| be positive or negative (like MO(+1) or MO(-2). Not specifying | ||
| it is the same as specifying +1. You can also use an integer, | ||
| where 0=MO. | ||
| weekday: | ||
| One of the weekday instances (MO, TU, etc). These instances may | ||
| receive a parameter N, specifying the Nth weekday, which could | ||
| be positive or negative (like MO(+1) or MO(-2). Not specifying | ||
| it is the same as specifying +1. You can also use an integer, | ||
| where 0=MO. | ||
| leapdays: | ||
| Will add given days to the date found, if year is a leap | ||
| year, and the date found is post 28 of february. | ||
| leapdays: | ||
| Will add given days to the date found, if year is a leap | ||
| year, and the date found is post 28 of february. | ||
| yearday, nlyearday: | ||
| Set the yearday or the non-leap year day (jump leap days). | ||
| These are converted to day/month/leapdays information. | ||
| yearday, nlyearday: | ||
| Set the yearday or the non-leap year day (jump leap days). | ||
| These are converted to day/month/leapdays information. | ||
| Here is the behavior of operations with relativedelta: | ||
| Here is the behavior of operations with relativedelta: | ||
| 1. Calculate the absolute year, using the 'year' argument, or the | ||
| original datetime year, if the argument is not present. | ||
| 1. Calculate the absolute year, using the 'year' argument, or the | ||
| original datetime year, if the argument is not present. | ||
| 2. Add the relative 'years' argument to the absolute year. | ||
| 2. Add the relative 'years' argument to the absolute year. | ||
| 3. Do steps 1 and 2 for month/months. | ||
| 3. Do steps 1 and 2 for month/months. | ||
| 4. Calculate the absolute day, using the 'day' argument, or the | ||
| original datetime day, if the argument is not present. Then, | ||
| subtract from the day until it fits in the year and month | ||
| found after their operations. | ||
| 4. Calculate the absolute day, using the 'day' argument, or the | ||
| original datetime day, if the argument is not present. Then, | ||
| subtract from the day until it fits in the year and month | ||
| found after their operations. | ||
| 5. Add the relative 'days' argument to the absolute day. Notice | ||
| that the 'weeks' argument is multiplied by 7 and added to | ||
| 'days'. | ||
| 5. Add the relative 'days' argument to the absolute day. Notice | ||
| that the 'weeks' argument is multiplied by 7 and added to | ||
| 'days'. | ||
| 6. Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds, | ||
| microsecond/microseconds. | ||
| 6. Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds, | ||
| microsecond/microseconds. | ||
| 7. If the 'weekday' argument is present, calculate the weekday, | ||
| with the given (wday, nth) tuple. wday is the index of the | ||
| weekday (0-6, 0=Mon), and nth is the number of weeks to add | ||
| forward or backward, depending on its signal. Notice that if | ||
| the calculated date is already Monday, for example, using | ||
| (0, 1) or (0, -1) won't change the day. | ||
| 7. If the 'weekday' argument is present, calculate the weekday, | ||
| with the given (wday, nth) tuple. wday is the index of the | ||
| weekday (0-6, 0=Mon), and nth is the number of weeks to add | ||
| forward or backward, depending on its signal. Notice that if | ||
| the calculated date is already Monday, for example, using | ||
| (0, 1) or (0, -1) won't change the day. | ||
| """ | ||
@@ -122,2 +123,8 @@ | ||
| hour=None, minute=None, second=None, microsecond=None): | ||
| # Check for non-integer values in integer-only quantities | ||
| if any(x is not None and x != int(x) for x in (years, months)): | ||
| raise ValueError("Non-integer years and months are " | ||
| "ambiguous and not currently supported.") | ||
| if dt1 and dt2: | ||
@@ -128,2 +135,3 @@ # datetime is a subclass of date. So both must be date | ||
| raise TypeError("relativedelta only diffs datetime/date") | ||
| # We allow two dates, or two datetimes, so we coerce them to be | ||
@@ -137,2 +145,3 @@ # of the same type | ||
| dt2 = datetime.datetime.fromordinal(dt2.toordinal()) | ||
| self.years = 0 | ||
@@ -156,3 +165,3 @@ self.months = 0 | ||
| months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month) | ||
| months = (dt1.year * 12 + dt1.month) - (dt2.year * 12 + dt2.month) | ||
| self._set_months(months) | ||
@@ -171,8 +180,9 @@ dtm = self.__radd__(dt2) | ||
| delta = dt1 - dtm | ||
| self.seconds = delta.seconds+delta.days*86400 | ||
| self.seconds = delta.seconds + delta.days * 86400 | ||
| self.microseconds = delta.microseconds | ||
| else: | ||
| # Relative information | ||
| self.years = years | ||
| self.months = months | ||
| self.days = days+weeks*7 | ||
| self.days = days + weeks * 7 | ||
| self.leapdays = leapdays | ||
@@ -183,2 +193,4 @@ self.hours = hours | ||
| self.microseconds = microseconds | ||
| # Absolute information | ||
| self.year = year | ||
@@ -192,2 +204,12 @@ self.month = month | ||
| # Absolute information | ||
| if any(x is not None and int(x) != x | ||
| for x in (year, month, day, hour, | ||
| minute, second, microsecond)): | ||
| # For now we'll deprecate floats - later it'll be an error. | ||
| warn("Non-integer value passed as absolute information. " + | ||
| "This is not a well-defined condition and will raise " + | ||
| "errors in future versions.", DeprecationWarning) | ||
| if isinstance(weekday, integer_types): | ||
@@ -223,26 +245,26 @@ self.weekday = weekdays[weekday] | ||
| if abs(self.microseconds) > 999999: | ||
| s = self.microseconds//abs(self.microseconds) | ||
| div, mod = divmod(self.microseconds*s, 1000000) | ||
| self.microseconds = mod*s | ||
| self.seconds += div*s | ||
| s = self.microseconds // abs(self.microseconds) | ||
| div, mod = divmod(self.microseconds * s, 1000000) | ||
| self.microseconds = mod * s | ||
| self.seconds += div * s | ||
| if abs(self.seconds) > 59: | ||
| s = self.seconds//abs(self.seconds) | ||
| div, mod = divmod(self.seconds*s, 60) | ||
| self.seconds = mod*s | ||
| self.minutes += div*s | ||
| s = self.seconds // abs(self.seconds) | ||
| div, mod = divmod(self.seconds * s, 60) | ||
| self.seconds = mod * s | ||
| self.minutes += div * s | ||
| if abs(self.minutes) > 59: | ||
| s = self.minutes//abs(self.minutes) | ||
| div, mod = divmod(self.minutes*s, 60) | ||
| self.minutes = mod*s | ||
| self.hours += div*s | ||
| div, mod = divmod(self.minutes * s, 60) | ||
| self.minutes = mod * s | ||
| self.hours += div * s | ||
| if abs(self.hours) > 23: | ||
| s = self.hours//abs(self.hours) | ||
| div, mod = divmod(self.hours*s, 24) | ||
| self.hours = mod*s | ||
| self.days += div*s | ||
| div, mod = divmod(self.hours * s, 24) | ||
| self.hours = mod * s | ||
| self.days += div * s | ||
| if abs(self.months) > 11: | ||
| s = self.months//abs(self.months) | ||
| div, mod = divmod(self.months*s, 12) | ||
| self.months = mod*s | ||
| self.years += div*s | ||
| s = self.months // abs(self.months) | ||
| div, mod = divmod(self.months * s, 12) | ||
| self.months = mod * s | ||
| self.years += div * s | ||
| if (self.hours or self.minutes or self.seconds or self.microseconds | ||
@@ -255,2 +277,9 @@ or self.hour is not None or self.minute is not None or | ||
| @property | ||
| def weeks(self): | ||
| return self.days // 7 | ||
| @weeks.setter | ||
| def weeks(self, value): | ||
| self.days = self.days - (self.weeks * 7) + value * 7 | ||
| def _set_months(self, months): | ||
@@ -266,10 +295,45 @@ self.months = months | ||
| def normalized(self): | ||
| """ | ||
| Return a version of this object represented entirely using integer | ||
| values for the relative attributes. | ||
| >>> relativedelta(days=1.5, hours=2).normalized() | ||
| relativedelta(days=1, hours=14) | ||
| :return: | ||
| Returns a :class:`dateutil.relativedelta.relativedelta` object. | ||
| """ | ||
| # Cascade remainders down (rounding each to roughly nearest microsecond) | ||
| days = int(self.days) | ||
| hours_f = round(self.hours + 24 * (self.days - days), 11) | ||
| hours = int(hours_f) | ||
| minutes_f = round(self.minutes + 60 * (hours_f - hours), 10) | ||
| minutes = int(minutes_f) | ||
| seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8) | ||
| seconds = int(seconds_f) | ||
| microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds)) | ||
| # Constructor carries overflow back up with call to _fix() | ||
| return self.__class__(years=self.years, months=self.months, | ||
| days=days, hours=hours, minutes=minutes, | ||
| seconds=seconds, microseconds=microseconds, | ||
| leapdays=self.leapdays, year=self.year, | ||
| month=self.month, day=self.day, | ||
| weekday=self.weekday, hour=self.hour, | ||
| minute=self.minute, second=self.second, | ||
| microsecond=self.microsecond) | ||
| def __add__(self, other): | ||
| if isinstance(other, relativedelta): | ||
| return relativedelta(years=other.years+self.years, | ||
| months=other.months+self.months, | ||
| days=other.days+self.days, | ||
| hours=other.hours+self.hours, | ||
| minutes=other.minutes+self.minutes, | ||
| seconds=other.seconds+self.seconds, | ||
| return self.__class__(years=other.years + self.years, | ||
| months=other.months + self.months, | ||
| days=other.days + self.days, | ||
| hours=other.hours + self.hours, | ||
| minutes=other.minutes + self.minutes, | ||
| seconds=other.seconds + self.seconds, | ||
| microseconds=(other.microseconds + | ||
@@ -320,7 +384,7 @@ self.microseconds), | ||
| weekday, nth = self.weekday.weekday, self.weekday.n or 1 | ||
| jumpdays = (abs(nth)-1)*7 | ||
| jumpdays = (abs(nth) - 1) * 7 | ||
| if nth > 0: | ||
| jumpdays += (7-ret.weekday()+weekday) % 7 | ||
| jumpdays += (7 - ret.weekday() + weekday) % 7 | ||
| else: | ||
| jumpdays += (ret.weekday()-weekday) % 7 | ||
| jumpdays += (ret.weekday() - weekday) % 7 | ||
| jumpdays *= -1 | ||
@@ -339,9 +403,9 @@ ret += datetime.timedelta(days=jumpdays) | ||
| raise TypeError("unsupported type for sub operation") | ||
| return relativedelta(years=self.years-other.years, | ||
| months=self.months-other.months, | ||
| days=self.days-other.days, | ||
| hours=self.hours-other.hours, | ||
| minutes=self.minutes-other.minutes, | ||
| seconds=self.seconds-other.seconds, | ||
| microseconds=self.microseconds-other.microseconds, | ||
| return self.__class__(years=self.years - other.years, | ||
| months=self.months - other.months, | ||
| days=self.days - other.days, | ||
| hours=self.hours - other.hours, | ||
| minutes=self.minutes - other.minutes, | ||
| seconds=self.seconds - other.seconds, | ||
| microseconds=self.microseconds - other.microseconds, | ||
| leapdays=self.leapdays or other.leapdays, | ||
@@ -358,3 +422,3 @@ year=self.year or other.year, | ||
| def __neg__(self): | ||
| return relativedelta(years=-self.years, | ||
| return self.__class__(years=-self.years, | ||
| months=-self.months, | ||
@@ -398,9 +462,9 @@ days=-self.days, | ||
| f = float(other) | ||
| return relativedelta(years=int(self.years*f), | ||
| months=int(self.months*f), | ||
| days=int(self.days*f), | ||
| hours=int(self.hours*f), | ||
| minutes=int(self.minutes*f), | ||
| seconds=int(self.seconds*f), | ||
| microseconds=int(self.microseconds*f), | ||
| return self.__class__(years=int(self.years * f), | ||
| months=int(self.months * f), | ||
| days=int(self.days * f), | ||
| hours=int(self.hours * f), | ||
| minutes=int(self.minutes * f), | ||
| seconds=int(self.seconds * f), | ||
| microseconds=int(self.microseconds * f), | ||
| leapdays=self.leapdays, | ||
@@ -435,2 +499,3 @@ year=self.year, | ||
| self.seconds == other.seconds and | ||
| self.microseconds == other.microseconds and | ||
| self.leapdays == other.leapdays and | ||
@@ -459,3 +524,3 @@ self.year == other.year and | ||
| if value: | ||
| l.append("%s=%+d" % (attr, value)) | ||
| l.append("{attr}={value:+g}".format(attr=attr, value=value)) | ||
| for attr in ["year", "month", "day", "weekday", | ||
@@ -465,5 +530,6 @@ "hour", "minute", "second", "microsecond"]: | ||
| if value is not None: | ||
| l.append("%s=%s" % (attr, repr(value))) | ||
| return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) | ||
| l.append("{attr}={value}".format(attr=attr, value=repr(value))) | ||
| return "{classname}({attrs})".format(classname=self.__class__.__name__, | ||
| attrs=", ".join(l)) | ||
| # vim:ts=4:sw=4:et |
+208
-33
@@ -13,7 +13,14 @@ # -*- coding: utf-8 -*- | ||
| from fractions import gcd | ||
| try: | ||
| from math import gcd | ||
| except ImportError: | ||
| from fractions import gcd | ||
| from six import advance_iterator, integer_types | ||
| from six.moves import _thread | ||
| import heapq | ||
| # For warning about deprecation of until and count | ||
| from warnings import warn | ||
| __all__ = ["rrule", "rruleset", "rrulestr", | ||
@@ -41,2 +48,4 @@ "YEARLY", "MONTHLY", "WEEKLY", "DAILY", | ||
| FREQNAMES = ['YEARLY','MONTHLY','WEEKLY','DAILY','HOURLY','MINUTELY','SECONDLY'] | ||
| (YEARLY, | ||
@@ -214,3 +223,44 @@ MONTHLY, | ||
| def between(self, after, before, inc=False): | ||
| def xafter(self, dt, count=None, inc=False): | ||
| """ | ||
| Generator which yields up to `count` recurrences after the given | ||
| datetime instance, equivalent to `after`. | ||
| :param dt: | ||
| The datetime at which to start generating recurrences. | ||
| :param count: | ||
| The maximum number of recurrences to generate. If `None` (default), | ||
| dates are generated until the recurrence rule is exhausted. | ||
| :param inc: | ||
| If `dt` is an instance of the rule and `inc` is `True`, it is | ||
| included in the output. | ||
| :yields: Yields a sequence of `datetime` objects. | ||
| """ | ||
| if self._cache_complete: | ||
| gen = self._cache | ||
| else: | ||
| gen = self | ||
| # Select the comparison function | ||
| if inc: | ||
| comp = lambda dc, dtc: dc >= dtc | ||
| else: | ||
| comp = lambda dc, dtc: dc > dtc | ||
| # Generate dates | ||
| n = 0 | ||
| for d in gen: | ||
| if comp(d, dt): | ||
| yield d | ||
| if count is not None: | ||
| n += 1 | ||
| if n >= count: | ||
| break | ||
| def between(self, after, before, inc=False, count=1): | ||
| """ Returns all the occurrences of the rrule between after and before. | ||
@@ -355,2 +405,8 @@ The inc keyword defines what happens if after and/or before are | ||
| # Cache the original byxxx rules, if they are provided, as the _byxxx | ||
| # attributes do not necessarily map to the inputs, and this can be | ||
| # a problem in generating the strings. Only store things if they've | ||
| # been supplied (the string retrieval will just use .get()) | ||
| self._original_rule = {} | ||
| if until and not isinstance(until, datetime.datetime): | ||
@@ -360,2 +416,7 @@ until = datetime.datetime.fromordinal(until.toordinal()) | ||
| if count and until: | ||
| warn("Using both 'count' and 'until' is inconsistent with RFC 2445" | ||
| " and has been deprecated in dateutil. Future versions will " | ||
| "raise an error.", DeprecationWarning) | ||
| if wkst is None: | ||
@@ -382,2 +443,5 @@ self._wkst = calendar.firstweekday() | ||
| if self._bysetpos: | ||
| self._original_rule['bysetpos'] = self._bysetpos | ||
| if (byweekno is None and byyearday is None and bymonthday is None and | ||
@@ -388,7 +452,11 @@ byweekday is None and byeaster is None): | ||
| bymonth = dtstart.month | ||
| self._original_rule['bymonth'] = None | ||
| bymonthday = dtstart.day | ||
| self._original_rule['bymonthday'] = None | ||
| elif freq == MONTHLY: | ||
| bymonthday = dtstart.day | ||
| self._original_rule['bymonthday'] = None | ||
| elif freq == WEEKLY: | ||
| byweekday = dtstart.weekday() | ||
| self._original_rule['byweekday'] = None | ||
@@ -404,2 +472,5 @@ # bymonth | ||
| if 'bymonth' not in self._original_rule: | ||
| self._original_rule['bymonth'] = self._bymonth | ||
| # byyearday | ||
@@ -413,2 +484,3 @@ if byyearday is None: | ||
| self._byyearday = tuple(sorted(set(byyearday))) | ||
| self._original_rule['byyearday'] = self._byyearday | ||
@@ -423,6 +495,8 @@ # byeaster | ||
| self._byeaster = tuple(sorted(byeaster)) | ||
| self._original_rule['byeaster'] = self._byeaster | ||
| else: | ||
| self._byeaster = None | ||
| # bymonthay | ||
| # bymonthday | ||
| if bymonthday is None: | ||
@@ -435,5 +509,12 @@ self._bymonthday = () | ||
| self._bymonthday = tuple(sorted(set([x for x in bymonthday if x > 0]))) | ||
| self._bynmonthday = tuple(sorted(set([x for x in bymonthday if x < 0]))) | ||
| bymonthday = set(bymonthday) # Ensure it's unique | ||
| self._bymonthday = tuple(sorted([x for x in bymonthday if x > 0])) | ||
| self._bynmonthday = tuple(sorted([x for x in bymonthday if x < 0])) | ||
| # Storing positive numbers first, then negative numbers | ||
| if 'bymonthday' not in self._original_rule: | ||
| self._original_rule['bymonthday'] = tuple( | ||
| itertools.chain(self._bymonthday, self._bynmonthday)) | ||
| # byweekno | ||
@@ -448,2 +529,4 @@ if byweekno is None: | ||
| self._original_rule['byweekno'] = self._byweekno | ||
| # byweekday / bynweekday | ||
@@ -477,6 +560,16 @@ if byweekday is None: | ||
| self._byweekday = tuple(sorted(self._byweekday)) | ||
| orig_byweekday = [weekday(x) for x in self._byweekday] | ||
| else: | ||
| orig_byweekday = tuple() | ||
| if self._bynweekday is not None: | ||
| self._bynweekday = tuple(sorted(self._bynweekday)) | ||
| orig_bynweekday = [weekday(*x) for x in self._bynweekday] | ||
| else: | ||
| orig_bynweekday = tuple() | ||
| if 'byweekday' not in self._original_rule: | ||
| self._original_rule['byweekday'] = tuple(itertools.chain( | ||
| orig_byweekday, orig_bynweekday)) | ||
| # byhour | ||
@@ -500,2 +593,3 @@ if byhour is None: | ||
| self._byhour = tuple(sorted(self._byhour)) | ||
| self._original_rule['byhour'] = self._byhour | ||
@@ -520,2 +614,3 @@ # byminute | ||
| self._byminute = tuple(sorted(self._byminute)) | ||
| self._original_rule['byminute'] = self._byminute | ||
@@ -542,2 +637,3 @@ # bysecond | ||
| self._bysecond = tuple(sorted(self._bysecond)) | ||
| self._original_rule['bysecond'] = self._bysecond | ||
@@ -557,2 +653,64 @@ if self._freq >= HOURLY: | ||
| def __str__(self): | ||
| """ | ||
| Output a string that would generate this RRULE if passed to rrulestr. | ||
| This is mostly compatible with RFC2445, except for the | ||
| dateutil-specific extension BYEASTER. | ||
| """ | ||
| output = [] | ||
| h, m, s = [None] * 3 | ||
| if self._dtstart: | ||
| output.append(self._dtstart.strftime('DTSTART:%Y%m%dT%H%M%S')) | ||
| h, m, s = self._dtstart.timetuple()[3:6] | ||
| parts = ['FREQ=' + FREQNAMES[self._freq]] | ||
| if self._interval != 1: | ||
| parts.append('INTERVAL=' + str(self._interval)) | ||
| if self._wkst: | ||
| parts.append('WKST=' + str(self._wkst)) | ||
| if self._count: | ||
| parts.append('COUNT=' + str(self._count)) | ||
| if self._until: | ||
| parts.append(self._until.strftime('UNTIL=%Y%m%dT%H%M%S')) | ||
| if self._original_rule.get('byweekday') is not None: | ||
| # The str() method on weekday objects doesn't generate | ||
| # RFC2445-compliant strings, so we should modify that. | ||
| original_rule = dict(self._original_rule) | ||
| wday_strings = [] | ||
| for wday in original_rule['byweekday']: | ||
| if wday.n: | ||
| wday_strings.append('{n:+d}{wday}'.format( | ||
| n=wday.n, | ||
| wday=repr(wday)[0:2])) | ||
| else: | ||
| wday_strings.append(repr(wday)) | ||
| original_rule['byweekday'] = wday_strings | ||
| else: | ||
| original_rule = self._original_rule | ||
| partfmt = '{name}={vals}' | ||
| for name, key in [('BYSETPOS', 'bysetpos'), | ||
| ('BYMONTH', 'bymonth'), | ||
| ('BYMONTHDAY', 'bymonthday'), | ||
| ('BYYEARDAY', 'byyearday'), | ||
| ('BYWEEKNO', 'byweekno'), | ||
| ('BYDAY', 'byweekday'), | ||
| ('BYHOUR', 'byhour'), | ||
| ('BYMINUTE', 'byminute'), | ||
| ('BYSECOND', 'bysecond'), | ||
| ('BYEASTER', 'byeaster')]: | ||
| value = original_rule.get(key) | ||
| if value: | ||
| parts.append(partfmt.format(name=name, vals=(','.join(str(v) | ||
| for v in value)))) | ||
| output.append(';'.join(parts)) | ||
| return '\n'.join(output) | ||
| def _iter(self): | ||
@@ -666,3 +824,3 @@ year, month, day, hour, minute, second, weekday, yearday, _ = \ | ||
| if i is not None: | ||
| date = datetime.date.fromordinal(ii.yearordinal+i) | ||
| date = datetime.date.fromordinal(ii.yearordinal + i) | ||
| for time in timeset: | ||
@@ -764,6 +922,6 @@ res = datetime.datetime.combine(date, time) | ||
| # Jump to one iteration before next day | ||
| second += (((86399-(hour*3600+minute*60+second)) | ||
| // interval)*interval) | ||
| second += (((86399 - (hour * 3600 + minute * 60 + second)) | ||
| // interval) * interval) | ||
| rep_rate = (24*3600) | ||
| rep_rate = (24 * 3600) | ||
| valid = False | ||
@@ -831,5 +989,5 @@ for j in range(0, rep_rate // gcd(interval, rep_rate)): | ||
| Specifies the starting position. | ||
| :param byxxx: | ||
| :param byxxx: | ||
| An iterable containing the list of allowed values. | ||
| :param base: | ||
| :param base: | ||
| The largest allowable value for the specified frequency (e.g. | ||
@@ -869,5 +1027,5 @@ 24 hours, 60 minutes). | ||
| :param value: | ||
| :param value: | ||
| The old value of the component. | ||
| :param byxxx: | ||
| :param byxxx: | ||
| The `BYXXX` set, which should have been generated by | ||
@@ -912,4 +1070,4 @@ `rrule._construct_byset`, or something else which checks that a | ||
| if year != self.lastyear: | ||
| self.yearlen = 365+calendar.isleap(year) | ||
| self.nextyearlen = 365+calendar.isleap(year+1) | ||
| self.yearlen = 365 + calendar.isleap(year) | ||
| self.nextyearlen = 365 + calendar.isleap(year + 1) | ||
| firstyday = datetime.date(year, 1, 1) | ||
@@ -1065,6 +1223,6 @@ self.yearordinal = firstyday.toordinal() | ||
| def ddayset(self, year, month, day): | ||
| dset = [None]*self.yearlen | ||
| i = datetime.date(year, month, day).toordinal()-self.yearordinal | ||
| dset = [None] * self.yearlen | ||
| i = datetime.date(year, month, day).toordinal() - self.yearordinal | ||
| dset[i] = i | ||
| return dset, i, i+1 | ||
| return dset, i, i + 1 | ||
@@ -1077,3 +1235,3 @@ def htimeset(self, hour, minute, second): | ||
| tset.append(datetime.time(hour, minute, second, | ||
| tzinfo=rr._tzinfo)) | ||
| tzinfo=rr._tzinfo)) | ||
| tset.sort() | ||
@@ -1117,3 +1275,7 @@ return tset | ||
| except StopIteration: | ||
| self.genlist.remove(self) | ||
| if self.genlist[0] is self: | ||
| heapq.heappop(self.genlist) | ||
| else: | ||
| self.genlist.remove(self) | ||
| heapq.heapify(self.genlist) | ||
@@ -1170,3 +1332,2 @@ next = __next__ | ||
| self._genitem(rlist, gen) | ||
| rlist.sort() | ||
| exlist = [] | ||
@@ -1177,5 +1338,6 @@ self._exdate.sort() | ||
| self._genitem(exlist, gen) | ||
| exlist.sort() | ||
| lastdt = None | ||
| total = 0 | ||
| heapq.heapify(rlist) | ||
| heapq.heapify(exlist) | ||
| while rlist: | ||
@@ -1185,4 +1347,6 @@ ritem = rlist[0] | ||
| while exlist and exlist[0] < ritem: | ||
| advance_iterator(exlist[0]) | ||
| exlist.sort() | ||
| exitem = exlist[0] | ||
| advance_iterator(exitem) | ||
| if exlist and exlist[0] is exitem: | ||
| heapq.heapreplace(exlist, exitem) | ||
| if not exlist or ritem != exlist[0]: | ||
@@ -1193,3 +1357,4 @@ total += 1 | ||
| advance_iterator(ritem) | ||
| rlist.sort() | ||
| if rlist and rlist[0] is ritem: | ||
| heapq.heapreplace(rlist, ritem) | ||
| self._len = total | ||
@@ -1246,12 +1411,22 @@ | ||
| def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwarsg): | ||
| def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwargs): | ||
| """ | ||
| Two ways to specify this: +1MO or MO(+1) | ||
| """ | ||
| l = [] | ||
| for wday in value.split(','): | ||
| for i in range(len(wday)): | ||
| if wday[i] not in '+-0123456789': | ||
| break | ||
| n = wday[:i] or None | ||
| w = wday[i:] | ||
| if n: | ||
| n = int(n) | ||
| if '(' in wday: | ||
| # If it's of the form TH(+1), etc. | ||
| splt = wday.split('(') | ||
| w = splt[0] | ||
| n = int(splt[1][:-1]) | ||
| else: | ||
| # If it's of the form +1MO | ||
| for i in range(len(wday)): | ||
| if wday[i] not in '+-0123456789': | ||
| break | ||
| n = wday[:i] or None | ||
| w = wday[i:] | ||
| if n: | ||
| n = int(n) | ||
| l.append(weekdays[self._weekday_map[w]](n)) | ||
@@ -1258,0 +1433,0 @@ rrkwargs["byweekday"] = l |
+2
-184
@@ -1,184 +0,2 @@ | ||
| # This code was originally contributed by Jeffrey Harris. | ||
| import datetime | ||
| import struct | ||
| from six.moves import winreg | ||
| __all__ = ["tzwin", "tzwinlocal"] | ||
| ONEWEEK = datetime.timedelta(7) | ||
| TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" | ||
| TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones" | ||
| TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" | ||
| def _settzkeyname(): | ||
| handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) | ||
| try: | ||
| winreg.OpenKey(handle, TZKEYNAMENT).Close() | ||
| TZKEYNAME = TZKEYNAMENT | ||
| except WindowsError: | ||
| TZKEYNAME = TZKEYNAME9X | ||
| handle.Close() | ||
| return TZKEYNAME | ||
| TZKEYNAME = _settzkeyname() | ||
| class tzwinbase(datetime.tzinfo): | ||
| """tzinfo class based on win32's timezones available in the registry.""" | ||
| def utcoffset(self, dt): | ||
| if self._isdst(dt): | ||
| return datetime.timedelta(minutes=self._dstoffset) | ||
| else: | ||
| return datetime.timedelta(minutes=self._stdoffset) | ||
| def dst(self, dt): | ||
| if self._isdst(dt): | ||
| minutes = self._dstoffset - self._stdoffset | ||
| return datetime.timedelta(minutes=minutes) | ||
| else: | ||
| return datetime.timedelta(0) | ||
| def tzname(self, dt): | ||
| if self._isdst(dt): | ||
| return self._dstname | ||
| else: | ||
| return self._stdname | ||
| def list(): | ||
| """Return a list of all time zones known to the system.""" | ||
| handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) | ||
| tzkey = winreg.OpenKey(handle, TZKEYNAME) | ||
| result = [winreg.EnumKey(tzkey, i) | ||
| for i in range(winreg.QueryInfoKey(tzkey)[0])] | ||
| tzkey.Close() | ||
| handle.Close() | ||
| return result | ||
| list = staticmethod(list) | ||
| def display(self): | ||
| return self._display | ||
| def _isdst(self, dt): | ||
| if not self._dstmonth: | ||
| # dstmonth == 0 signals the zone has no daylight saving time | ||
| return False | ||
| dston = picknthweekday(dt.year, self._dstmonth, self._dstdayofweek, | ||
| self._dsthour, self._dstminute, | ||
| self._dstweeknumber) | ||
| dstoff = picknthweekday(dt.year, self._stdmonth, self._stddayofweek, | ||
| self._stdhour, self._stdminute, | ||
| self._stdweeknumber) | ||
| if dston < dstoff: | ||
| return dston <= dt.replace(tzinfo=None) < dstoff | ||
| else: | ||
| return not dstoff <= dt.replace(tzinfo=None) < dston | ||
| class tzwin(tzwinbase): | ||
| def __init__(self, name): | ||
| self._name = name | ||
| # multiple contexts only possible in 2.7 and 3.1, we still support 2.6 | ||
| with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: | ||
| with winreg.OpenKey(handle, | ||
| "%s\%s" % (TZKEYNAME, name)) as tzkey: | ||
| keydict = valuestodict(tzkey) | ||
| self._stdname = keydict["Std"].encode("iso-8859-1") | ||
| self._dstname = keydict["Dlt"].encode("iso-8859-1") | ||
| self._display = keydict["Display"] | ||
| # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm | ||
| tup = struct.unpack("=3l16h", keydict["TZI"]) | ||
| self._stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1 | ||
| self._dstoffset = self._stdoffset-tup[2] # + DaylightBias * -1 | ||
| # for the meaning see the win32 TIME_ZONE_INFORMATION structure docs | ||
| # http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx | ||
| (self._stdmonth, | ||
| self._stddayofweek, # Sunday = 0 | ||
| self._stdweeknumber, # Last = 5 | ||
| self._stdhour, | ||
| self._stdminute) = tup[4:9] | ||
| (self._dstmonth, | ||
| self._dstdayofweek, # Sunday = 0 | ||
| self._dstweeknumber, # Last = 5 | ||
| self._dsthour, | ||
| self._dstminute) = tup[12:17] | ||
| def __repr__(self): | ||
| return "tzwin(%s)" % repr(self._name) | ||
| def __reduce__(self): | ||
| return (self.__class__, (self._name,)) | ||
| class tzwinlocal(tzwinbase): | ||
| def __init__(self): | ||
| with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: | ||
| with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey: | ||
| keydict = valuestodict(tzlocalkey) | ||
| self._stdname = keydict["StandardName"].encode("iso-8859-1") | ||
| self._dstname = keydict["DaylightName"].encode("iso-8859-1") | ||
| try: | ||
| with winreg.OpenKey( | ||
| handle, "%s\%s" % (TZKEYNAME, self._stdname)) as tzkey: | ||
| _keydict = valuestodict(tzkey) | ||
| self._display = _keydict["Display"] | ||
| except OSError: | ||
| self._display = None | ||
| self._stdoffset = -keydict["Bias"]-keydict["StandardBias"] | ||
| self._dstoffset = self._stdoffset-keydict["DaylightBias"] | ||
| # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm | ||
| tup = struct.unpack("=8h", keydict["StandardStart"]) | ||
| (self._stdmonth, | ||
| self._stddayofweek, # Sunday = 0 | ||
| self._stdweeknumber, # Last = 5 | ||
| self._stdhour, | ||
| self._stdminute) = tup[1:6] | ||
| tup = struct.unpack("=8h", keydict["DaylightStart"]) | ||
| (self._dstmonth, | ||
| self._dstdayofweek, # Sunday = 0 | ||
| self._dstweeknumber, # Last = 5 | ||
| self._dsthour, | ||
| self._dstminute) = tup[1:6] | ||
| def __reduce__(self): | ||
| return (self.__class__, ()) | ||
| def picknthweekday(year, month, dayofweek, hour, minute, whichweek): | ||
| """dayofweek == 0 means Sunday, whichweek 5 means last instance""" | ||
| first = datetime.datetime(year, month, 1, hour, minute) | ||
| weekdayone = first.replace(day=((dayofweek-first.isoweekday()) % 7+1)) | ||
| for n in range(whichweek): | ||
| dt = weekdayone+(whichweek-n)*ONEWEEK | ||
| if dt.month == month: | ||
| return dt | ||
| def valuestodict(key): | ||
| """Convert a registry key's values to a dictionary.""" | ||
| dict = {} | ||
| size = winreg.QueryInfoKey(key)[1] | ||
| for i in range(size): | ||
| data = winreg.EnumValue(key, i) | ||
| dict[data[0]] = data[1] | ||
| return dict | ||
| # tzwin has moved to dateutil.tz.win | ||
| from .tz.win import * |
@@ -7,2 +7,4 @@ # -*- coding: utf-8 -*- | ||
| import shutil | ||
| import json | ||
| from subprocess import check_call | ||
@@ -16,11 +18,12 @@ from tarfile import TarFile | ||
| __all__ = ["gettz", "rebuild"] | ||
| __all__ = ["gettz", "gettz_db_metadata", "rebuild"] | ||
| _ZONEFILENAME = "dateutil-zoneinfo.tar.gz" | ||
| ZONEFILENAME = "dateutil-zoneinfo.tar.gz" | ||
| METADATA_FN = 'METADATA' | ||
| # python2.6 compatability. Note that TarFile.__exit__ != TarFile.close, but | ||
| # it's close enough for python2.6 | ||
| _tar_open = TarFile.open | ||
| tar_open = TarFile.open | ||
| if not hasattr(TarFile, '__exit__'): | ||
| def _tar_open(*args, **kwargs): | ||
| def tar_open(*args, **kwargs): | ||
| return closing(TarFile.open(*args, **kwargs)) | ||
@@ -36,3 +39,3 @@ | ||
| try: | ||
| return BytesIO(get_data(__name__, _ZONEFILENAME)) | ||
| return BytesIO(get_data(__name__, ZONEFILENAME)) | ||
| except IOError as e: # TODO switch to FileNotFoundError? | ||
@@ -46,3 +49,3 @@ warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror)) | ||
| if zonefile_stream is not None: | ||
| with _tar_open(fileobj=zonefile_stream, mode='r') as tf: | ||
| with tar_open(fileobj=zonefile_stream, mode='r') as tf: | ||
| # dict comprehension does not work on python2.6 | ||
@@ -55,3 +58,4 @@ # TODO: get back to the nicer syntax when we ditch python2.6 | ||
| filename=zf.name)) | ||
| for zf in tf.getmembers() if zf.isfile()) | ||
| for zf in tf.getmembers() | ||
| if zf.isfile() and zf.name != METADATA_FN) | ||
| # deal with links: They'll point to their parent object. Less | ||
@@ -65,4 +69,12 @@ # waste of memory | ||
| self.zones.update(links) | ||
| try: | ||
| metadata_json = tf.extractfile(tf.getmember(METADATA_FN)) | ||
| metadata_str = metadata_json.read().decode('UTF-8') | ||
| self.metadata = json.loads(metadata_str) | ||
| except KeyError: | ||
| # no metadata in tar file | ||
| self.metadata = None | ||
| else: | ||
| self.zones = dict() | ||
| self.metadata = None | ||
@@ -85,31 +97,13 @@ | ||
| def rebuild(filename, tag=None, format="gz", zonegroups=[]): | ||
| """Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar* | ||
| def gettz_db_metadata(): | ||
| """ Get the zonefile metadata | ||
| filename is the timezone tarball from ftp.iana.org/tz. | ||
| See `zonefile_metadata`_ | ||
| :returns: A dictionary with the database metadata | ||
| """ | ||
| tmpdir = tempfile.mkdtemp() | ||
| zonedir = os.path.join(tmpdir, "zoneinfo") | ||
| moduledir = os.path.dirname(__file__) | ||
| try: | ||
| with _tar_open(filename) as tf: | ||
| for name in zonegroups: | ||
| tf.extract(name, tmpdir) | ||
| filepaths = [os.path.join(tmpdir, n) for n in zonegroups] | ||
| try: | ||
| check_call(["zic", "-d", zonedir] + filepaths) | ||
| except OSError as e: | ||
| if e.errno == 2: | ||
| logging.error( | ||
| "Could not find zic. Perhaps you need to install " | ||
| "libc-bin or some other package that provides it, " | ||
| "or it's not in your PATH?") | ||
| raise | ||
| target = os.path.join(moduledir, _ZONEFILENAME) | ||
| with _tar_open(target, "w:%s" % format) as tf: | ||
| for entry in os.listdir(zonedir): | ||
| entrypath = os.path.join(zonedir, entry) | ||
| tf.add(entrypath, entry) | ||
| finally: | ||
| shutil.rmtree(tmpdir) | ||
| if len(_CLASS_ZONE_INSTANCE) == 0: | ||
| _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) | ||
| return _CLASS_ZONE_INSTANCE[0].metadata | ||
+91
-1
@@ -0,1 +1,91 @@ | ||
| Version 2.5.0 | ||
| ------------- | ||
| - Updated zoneinfo to 2016a | ||
| - zoneinfo_metadata file version increased to 2.0 - the updated updatezinfo.py | ||
| script will work with older zoneinfo_metadata.json files, but new metadata | ||
| files will not work with older updatezinfo.py versions. Additionally, we have | ||
| started hosting our own mirror of the Olson databases on a github pages | ||
| site (https://dateutil.github.io/tzdata/) (gh pr #183) | ||
| - dateutil zoneinfo tarballs now contain the full zoneinfo_metadata file used | ||
| to generate them. (gh issue #27, gh pr #85) | ||
| - relativedelta can now be safely subclassed without derived objects reverting | ||
| to base relativedelta objects as a result of arithmetic operations. | ||
| (lp:1010199, gh issue #44, pr #49) | ||
| - relativedelta 'weeks' parameter can now be set and retrieved as a property of | ||
| relativedelta instances. (lp: 727525, gh issue #45, pr #49) | ||
| - relativedelta now explicitly supports fractional relative weeks, days, hours, | ||
| minutes and seconds. Fractional values in absolute parameters (year, day, etc) | ||
| are now deprecated. (gh issue #40, pr #190) | ||
| - relativedelta objects previously did not use microseconds to determine of two | ||
| relativedelta objects were equal. This oversight has been corrected. | ||
| Contributed by @elprans (gh pr #113) | ||
| - rrule now has an xafter() method for retrieving multiple recurrences after a | ||
| specified date. (gh pr #38) | ||
| - str(rrule) now returns an RFC2445-compliant rrule string, contributed by | ||
| @schinckel and @armicron (lp:1406305, gh issue #47, prs #50, #62 and #160) | ||
| - rrule performance under certain conditions has been significantly improved | ||
| thanks to a patch contributed by @dekoza, based on an article by Brian Beck | ||
| (@exogen) (gh pr #136) | ||
| - The use of both the 'until' and 'count' parameters is now deprecated as | ||
| inconsistent with RFC2445 (gh pr #62, #185) | ||
| - Parsing an empty string will now raise a ValueError, rather than returning the | ||
| datetime passed to the 'default' parameter. (gh issue #78, pr #187) | ||
| - tzwinlocal objects now have a meaningful repr() and str() implementation | ||
| (gh issue #148, prs #184 and #186) | ||
| - Added equality logic for tzwin and tzwinlocal objects. (gh issue #151, | ||
| pr #180, #184) | ||
| - Added some flexibility in subclassing timelex, and switched the default | ||
| behavior over to using string methods rather than comparing against a fixed | ||
| list. (gh pr #122, #139) | ||
| - An issue causing tzstr() to crash on Python 2.x was fixed. (lp: 1331576, | ||
| gh issue #51, pr #55) | ||
| - An issue with string encoding causing exceptions under certain circumstances | ||
| when tzname() is called was fixed. (gh issue #60, #74, pr #75) | ||
| - Parser issue where calling parse() on dates with no day specified when the | ||
| day of the month in the default datetime (which is "today" if unspecified) is | ||
| greater than the number of days in the parsed month was fixed (this issue | ||
| tended to crop up between the 29th and 31st of the month, for obvious reasons) | ||
| (canonical gh issue #25, pr #30, #191) | ||
| - Fixed parser issue causing fuzzy_with_tokens to raise an unexpected exception | ||
| in certain circumstances. Contributed by @MichaelAquilina (gh pr #91) | ||
| - Fixed parser issue where years > 100 AD were incorrectly parsed. Contributed | ||
| by @Bachmann1234 (gh pr #130) | ||
| - Fixed parser issue where commas were not a valid separator between seconds | ||
| and microseconds, preventing parsing of ISO 8601 dates. Contributed by | ||
| @ryanss (gh issue #28, pr #106) | ||
| - Fixed issue with tzwin encoding in locales with non-Latin alphabets | ||
| (gh issue #92, pr #98) | ||
| - Fixed an issue where tzwin was not being properly imported on Windows. | ||
| Contributed by @labrys. (gh pr #134) | ||
| - Fixed a problem causing issues importing zoneinfo in certain circumstances. | ||
| Issue and solution contributed by @alexxv (gh issue #97, pr #99) | ||
| - Fixed an issue where dateutil timezones were not compatible with basic time | ||
| objects. One of many, many timezone related issues contributed and tested by | ||
| @labrys. (gh issue #132, pr #181) | ||
| - Fixed issue where tzwinlocal had an invalid utcoffset. (gh issue #135, | ||
| pr #141, #142) | ||
| - Fixed issue with tzwin and tzwinlocal where DST transitions were incorrectly | ||
| parsed from the registry. (gh issue #143, pr #178) | ||
| - updatezinfo.py no longer suppresses certain OSErrors. Contributed by @bjamesv | ||
| (gh pr #164) | ||
| - An issue that arose when timezone locale changes during runtime has been | ||
| fixed by @carlosxl and @mjschultz (gh issue #100, prs #107, #109) | ||
| - Python 3.5 was added to the supported platforms in the metadata (@tacaswell | ||
| gh pr #159) and the test suites (@moreati gh pr #117). | ||
| - An issue with tox failing without unittest2 installed in Python 2.6 was fixed | ||
| by @moreati (gh pr #115) | ||
| - Several deprecated functions were replaced in the tests by @moreati | ||
| (gh pr #116) | ||
| - Improved the logic in Travis and Appveyor to alleviate issues where builds | ||
| were failing due to connection issues when downloading the IANA timezone | ||
| files. In addition to adding our own mirror for the files (gh pr #183), the | ||
| download is now retried a number of times (with a delay) (gh pr #177) | ||
| - Many failing doctests were fixed by @moreati. (gh pr #120) | ||
| - Many fixes to the documentation (gh pr #103, gh pr #87 from @radarhere, | ||
| gh pr #154 from @gpoesia, gh pr #156 from @awsum, gh pr #168 from @ja8zyjits) | ||
| - Added a code coverage tool to the CI to help improve the library. (gh pr #182) | ||
| - We now have a mailing list - dateutil@python.org, graciously hosted by | ||
| Python.org. | ||
| Version 2.4.2 | ||
@@ -8,3 +98,3 @@ ------------- | ||
| stamps, triggering inappropriate errors. gh #56 (lp: 1428895), gh pr #63. | ||
| - Missing function "setcachsize" removed from zoneinfo __all__ list by @ryanss, | ||
| - Missing function "setcachesize" removed from zoneinfo __all__ list by @ryanss, | ||
| fixing an issue with wildcard imports of dateutil.zoneinfo. (gh pr #66). | ||
@@ -11,0 +101,0 @@ - (PyPi only) Fix an issue with source distributions not including the test |
+4
-3
| Metadata-Version: 1.1 | ||
| Name: python-dateutil | ||
| Version: 2.4.2 | ||
| Version: 2.5.0 | ||
| Summary: Extensions to the standard Python datetime module | ||
| Home-page: https://dateutil.readthedocs.org | ||
| Author: Yaron de Leeuw | ||
| Author-email: me@jarondl.net | ||
| Author: Paul Ganssle, Yaron de Leeuw | ||
| Author-email: dateutil@python.org | ||
| License: Simplified BSD | ||
@@ -25,3 +25,4 @@ Description: | ||
| Classifier: Programming Language :: Python :: 3.4 | ||
| Classifier: Programming Language :: Python :: 3.5 | ||
| Classifier: Topic :: Software Development :: Libraries | ||
| Requires: six |
| Metadata-Version: 1.1 | ||
| Name: python-dateutil | ||
| Version: 2.4.2 | ||
| Version: 2.5.0 | ||
| Summary: Extensions to the standard Python datetime module | ||
| Home-page: https://dateutil.readthedocs.org | ||
| Author: Yaron de Leeuw | ||
| Author-email: me@jarondl.net | ||
| Author: Paul Ganssle, Yaron de Leeuw | ||
| Author-email: dateutil@python.org | ||
| License: Simplified BSD | ||
@@ -25,3 +25,4 @@ Description: | ||
| Classifier: Programming Language :: Python :: 3.4 | ||
| Classifier: Programming Language :: Python :: 3.5 | ||
| Classifier: Topic :: Software Development :: Libraries | ||
| Requires: six |
@@ -14,8 +14,9 @@ LICENSE | ||
| dateutil/rrule.py | ||
| dateutil/tz.py | ||
| dateutil/tzwin.py | ||
| dateutil/test/__init__.py | ||
| dateutil/test/test.py | ||
| dateutil/tz/__init__.py | ||
| dateutil/tz/tz.py | ||
| dateutil/tz/win.py | ||
| dateutil/zoneinfo/__init__.py | ||
| dateutil/zoneinfo/dateutil-zoneinfo.tar.gz | ||
| dateutil/zoneinfo/rebuild.py | ||
| python_dateutil.egg-info/PKG-INFO | ||
@@ -22,0 +23,0 @@ python_dateutil.egg-info/SOURCES.txt |
+26
-7
@@ -12,2 +12,6 @@ dateutil - powerful extensions to datetime | ||
| .. image:: https://codecov.io/github/dateutil/dateutil/coverage.svg?branch=master | ||
| :target: https://codecov.io/github/dateutil/dateutil?branch=master | ||
| :alt: Code coverage | ||
| .. image:: https://img.shields.io/pypi/dd/python-dateutil.svg?style=flat-square | ||
@@ -22,3 +26,2 @@ :target: https://pypi.python.org/pypi/python-dateutil/ | ||
| The `dateutil` module provides powerful extensions to | ||
@@ -61,3 +64,3 @@ the standard `datetime` module, available in Python. | ||
| using Western, Orthodox or Julian algorithms; | ||
| * More than 400 test cases. | ||
| * A comprehensive test suite. | ||
@@ -100,3 +103,3 @@ Quick example | ||
| The dateutil module was written by Gustavo Niemeyer <gustavo@niemeyer.net> | ||
| in 2003 | ||
| in 2003. | ||
@@ -108,3 +111,7 @@ It is maintained by: | ||
| * Yaron de Leeuw <me@jarondl.net> 2014- | ||
| * Paul Ganssle <paul@ganssle.io> 2015- | ||
| Our mailing list is available at `dateutil@python.org <https://mail.python.org/mailman/listinfo/dateutil>`_. As it is hosted by the PSF, it is subject to the `PSF code of | ||
| conduct <https://www.python.org/psf/codeofconduct/>`_. | ||
| Building and releasing | ||
@@ -115,7 +122,15 @@ ====================== | ||
| that the zic command is in your path, and that you have network connectivity | ||
| to get the latest timezone information from IANA. If you have downloaded | ||
| the timezone data earlier, you can give the tarball as a parameter to | ||
| updatezinfo.py. | ||
| to get the latest timezone information from IANA, or from `our mirror of the | ||
| IANA database <https://dateutil.github.io/tzdata/>`_. | ||
| Starting with version 2.4.1, all source and binary distributions will be signed | ||
| by a PGP key that has, at the very least, been signed by the key which made the | ||
| previous release. A table of release signing keys can be found below: | ||
| =========== ============================ | ||
| Releases Signing key fingerprint | ||
| =========== ============================ | ||
| 2.4.1- `0xCD54FCE3D964BEFB`_ | ||
| =========== ============================ | ||
| Testing | ||
@@ -130,2 +145,6 @@ ======= | ||
| All github pull requests are automatically tested using travis. | ||
| All github pull requests are automatically tested using travis and appveyor. | ||
| .. _0xCD54FCE3D964BEFB: | ||
| https://pgp.mit.edu/pks/lookup?op=vindex&search=0xCD54FCE3D964BEFB |
+1
-1
@@ -5,5 +5,5 @@ [bdist_wheel] | ||
| [egg_info] | ||
| tag_date = 0 | ||
| tag_build = | ||
| tag_date = 0 | ||
| tag_svn_revision = 0 | ||
+5
-4
@@ -23,4 +23,4 @@ #!/usr/bin/python | ||
| description="Extensions to the standard Python datetime module", | ||
| author="Yaron de Leeuw", | ||
| author_email="me@jarondl.net", | ||
| author="Paul Ganssle, Yaron de Leeuw", | ||
| author_email="dateutil@python.org", | ||
| url="https://dateutil.readthedocs.org", | ||
@@ -32,3 +32,3 @@ license="Simplified BSD", | ||
| """, | ||
| packages=["dateutil", "dateutil.zoneinfo"], | ||
| packages=["dateutil", "dateutil.zoneinfo", "dateutil.tz"], | ||
| package_data={"dateutil.zoneinfo": ["dateutil-zoneinfo.tar.gz"]}, | ||
@@ -50,5 +50,6 @@ zip_safe=True, | ||
| 'Programming Language :: Python :: 3.4', | ||
| 'Programming Language :: Python :: 3.5', | ||
| 'Topic :: Software Development :: Libraries', | ||
| ], | ||
| test_suite="dateutil.test.test" | ||
| test_suite="dateutil.test" | ||
| ) |
+26
-5
@@ -8,2 +8,3 @@ #!/usr/bin/env python | ||
| from six.moves.urllib import request | ||
| from six.moves.urllib import error as urllib_error | ||
@@ -19,7 +20,26 @@ from dateutil.zoneinfo import rebuild | ||
| releases_urls = metadata['releases_url'] | ||
| if metadata['metadata_version'] < 2.0: | ||
| # In later versions the releases URL is a mirror URL | ||
| releases_urls = [releases_urls] | ||
| if not os.path.isfile(metadata['tzdata_file']): | ||
| print("Downloading tz file from iana") | ||
| request.urlretrieve(os.path.join(metadata['releases_url'], | ||
| metadata['tzdata_file']), | ||
| metadata['tzdata_file']) | ||
| for ii, releases_url in enumerate(releases_urls): | ||
| print("Downloading tz file from mirror {ii}".format(ii=ii)) | ||
| try: | ||
| request.urlretrieve(os.path.join(releases_url, | ||
| metadata['tzdata_file']), | ||
| metadata['tzdata_file']) | ||
| except urllib_error.URLError as e: | ||
| print("Download failed, trying next mirror.") | ||
| last_error = e | ||
| continue | ||
| last_error = None | ||
| break | ||
| if last_error is not None: | ||
| raise last_error | ||
| with open(metadata['tzdata_file'], 'rb') as tzfile: | ||
@@ -31,3 +51,4 @@ sha_hasher = hashlib.sha512() | ||
| print("Updating timezone information...") | ||
| rebuild(metadata['tzdata_file'], zonegroups=metadata['zonegroups']) | ||
| rebuild.rebuild(metadata['tzdata_file'], zonegroups=metadata['zonegroups'], | ||
| metadata=metadata) | ||
| print("Done.") | ||
@@ -34,0 +55,0 @@ |
| { | ||
| "metadata_version" : 0.1, | ||
| "releases_url" : "ftp://ftp.iana.org/tz/releases/", | ||
| "tzdata_file" : "tzdata2015b.tar.gz", | ||
| "tzdata_file_sha512" : "767782b87e62a8f7a4dbcae595d16a54197c9e04ca974d7016d11f90ebaf2537b804d111f204af9052c68d4670afe0af0af9e5b150867a357fc199bb541368d0", | ||
| "zonegroups" : [ | ||
| "metadata_version": 2.0, | ||
| "releases_url": [ | ||
| "https://dateutil.github.io/tzdata/tzdata/", | ||
| "ftp://ftp.iana.org/tz/releases/" | ||
| ], | ||
| "tzdata_file": "tzdata2016a.tar.gz", | ||
| "tzdata_file_sha512": "9aa5f61a73afa5070dfb1d1982945d268ea8215663d0cd594216500aff14797ea5591ccfd488dc2280902fa1820bf782623624912b669873728431258fe10ec1", | ||
| "tzversion": "2016a", | ||
| "zonegroups": [ | ||
| "africa", | ||
@@ -19,4 +23,4 @@ "antarctica", | ||
| "backzone", | ||
| "backward"] | ||
| } | ||
| "backward" | ||
| ] | ||
| } |
Sorry, the diff of this file is too big to display
-986
| # -*- 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 |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
27
3.85%370361
-28.16%4356
-41.67%