| Index: third_party/recipe_engine/third_party/dateutil/rrule.py
|
| diff --git a/third_party/recipe_engine/third_party/dateutil/rrule.py b/third_party/recipe_engine/third_party/dateutil/rrule.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..6bd83cad372262acd26f14cc2936c25823fe7f14
|
| --- /dev/null
|
| +++ b/third_party/recipe_engine/third_party/dateutil/rrule.py
|
| @@ -0,0 +1,1097 @@
|
| +"""
|
| +Copyright (c) 2003-2010 Gustavo Niemeyer <gustavo@niemeyer.net>
|
| +
|
| +This module offers extensions to the standard python 2.3+
|
| +datetime module.
|
| +"""
|
| +__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
| +__license__ = "PSF License"
|
| +
|
| +import itertools
|
| +import datetime
|
| +import calendar
|
| +import thread
|
| +import sys
|
| +
|
| +__all__ = ["rrule", "rruleset", "rrulestr",
|
| + "YEARLY", "MONTHLY", "WEEKLY", "DAILY",
|
| + "HOURLY", "MINUTELY", "SECONDLY",
|
| + "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
| +
|
| +# Every mask is 7 days longer to handle cross-year weekly periods.
|
| +M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30+
|
| + [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7)
|
| +M365MASK = list(M366MASK)
|
| +M29, M30, M31 = range(1,30), range(1,31), range(1,32)
|
| +MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])
|
| +MDAY365MASK = list(MDAY366MASK)
|
| +M29, M30, M31 = range(-29,0), range(-30,0), range(-31,0)
|
| +NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])
|
| +NMDAY365MASK = list(NMDAY366MASK)
|
| +M366RANGE = (0,31,60,91,121,152,182,213,244,274,305,335,366)
|
| +M365RANGE = (0,31,59,90,120,151,181,212,243,273,304,334,365)
|
| +WDAYMASK = [0,1,2,3,4,5,6]*55
|
| +del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31]
|
| +MDAY365MASK = tuple(MDAY365MASK)
|
| +M365MASK = tuple(M365MASK)
|
| +
|
| +(YEARLY,
|
| + MONTHLY,
|
| + WEEKLY,
|
| + DAILY,
|
| + HOURLY,
|
| + MINUTELY,
|
| + SECONDLY) = range(7)
|
| +
|
| +# Imported on demand.
|
| +easter = None
|
| +parser = None
|
| +
|
| +class weekday(object):
|
| + __slots__ = ["weekday", "n"]
|
| +
|
| + def __init__(self, weekday, n=None):
|
| + if n == 0:
|
| + raise ValueError, "Can't create weekday with n == 0"
|
| + self.weekday = weekday
|
| + self.n = n
|
| +
|
| + def __call__(self, n):
|
| + if n == self.n:
|
| + return self
|
| + else:
|
| + return self.__class__(self.weekday, n)
|
| +
|
| + def __eq__(self, other):
|
| + try:
|
| + if self.weekday != other.weekday or self.n != other.n:
|
| + return False
|
| + except AttributeError:
|
| + return False
|
| + return True
|
| +
|
| + def __repr__(self):
|
| + s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
|
| + if not self.n:
|
| + return s
|
| + else:
|
| + return "%s(%+d)" % (s, self.n)
|
| +
|
| +MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
|
| +
|
| +class rrulebase:
|
| + def __init__(self, cache=False):
|
| + if cache:
|
| + self._cache = []
|
| + self._cache_lock = thread.allocate_lock()
|
| + self._cache_gen = self._iter()
|
| + self._cache_complete = False
|
| + else:
|
| + self._cache = None
|
| + self._cache_complete = False
|
| + self._len = None
|
| +
|
| + def __iter__(self):
|
| + if self._cache_complete:
|
| + return iter(self._cache)
|
| + elif self._cache is None:
|
| + return self._iter()
|
| + else:
|
| + return self._iter_cached()
|
| +
|
| + def _iter_cached(self):
|
| + i = 0
|
| + gen = self._cache_gen
|
| + cache = self._cache
|
| + acquire = self._cache_lock.acquire
|
| + release = self._cache_lock.release
|
| + while gen:
|
| + if i == len(cache):
|
| + acquire()
|
| + if self._cache_complete:
|
| + break
|
| + try:
|
| + for j in range(10):
|
| + cache.append(gen.next())
|
| + except StopIteration:
|
| + self._cache_gen = gen = None
|
| + self._cache_complete = True
|
| + break
|
| + release()
|
| + yield cache[i]
|
| + i += 1
|
| + while i < self._len:
|
| + yield cache[i]
|
| + i += 1
|
| +
|
| + def __getitem__(self, item):
|
| + if self._cache_complete:
|
| + return self._cache[item]
|
| + elif isinstance(item, slice):
|
| + if item.step and item.step < 0:
|
| + return list(iter(self))[item]
|
| + else:
|
| + return list(itertools.islice(self,
|
| + item.start or 0,
|
| + item.stop or sys.maxint,
|
| + item.step or 1))
|
| + elif item >= 0:
|
| + gen = iter(self)
|
| + try:
|
| + for i in range(item+1):
|
| + res = gen.next()
|
| + except StopIteration:
|
| + raise IndexError
|
| + return res
|
| + else:
|
| + return list(iter(self))[item]
|
| +
|
| + def __contains__(self, item):
|
| + if self._cache_complete:
|
| + return item in self._cache
|
| + else:
|
| + for i in self:
|
| + if i == item:
|
| + return True
|
| + elif i > item:
|
| + return False
|
| + return False
|
| +
|
| + # __len__() introduces a large performance penality.
|
| + def count(self):
|
| + if self._len is None:
|
| + for x in self: pass
|
| + return self._len
|
| +
|
| + def before(self, dt, inc=False):
|
| + if self._cache_complete:
|
| + gen = self._cache
|
| + else:
|
| + gen = self
|
| + last = None
|
| + if inc:
|
| + for i in gen:
|
| + if i > dt:
|
| + break
|
| + last = i
|
| + else:
|
| + for i in gen:
|
| + if i >= dt:
|
| + break
|
| + last = i
|
| + return last
|
| +
|
| + def after(self, dt, inc=False):
|
| + if self._cache_complete:
|
| + gen = self._cache
|
| + else:
|
| + gen = self
|
| + if inc:
|
| + for i in gen:
|
| + if i >= dt:
|
| + return i
|
| + else:
|
| + for i in gen:
|
| + if i > dt:
|
| + return i
|
| + return None
|
| +
|
| + def between(self, after, before, inc=False):
|
| + if self._cache_complete:
|
| + gen = self._cache
|
| + else:
|
| + gen = self
|
| + started = False
|
| + l = []
|
| + if inc:
|
| + for i in gen:
|
| + if i > before:
|
| + break
|
| + elif not started:
|
| + if i >= after:
|
| + started = True
|
| + l.append(i)
|
| + else:
|
| + l.append(i)
|
| + else:
|
| + for i in gen:
|
| + if i >= before:
|
| + break
|
| + elif not started:
|
| + if i > after:
|
| + started = True
|
| + l.append(i)
|
| + else:
|
| + l.append(i)
|
| + return l
|
| +
|
| +class rrule(rrulebase):
|
| + def __init__(self, freq, dtstart=None,
|
| + interval=1, wkst=None, count=None, until=None, bysetpos=None,
|
| + bymonth=None, bymonthday=None, byyearday=None, byeaster=None,
|
| + byweekno=None, byweekday=None,
|
| + byhour=None, byminute=None, bysecond=None,
|
| + cache=False):
|
| + rrulebase.__init__(self, cache)
|
| + global easter
|
| + if not dtstart:
|
| + dtstart = datetime.datetime.now().replace(microsecond=0)
|
| + elif not isinstance(dtstart, datetime.datetime):
|
| + dtstart = datetime.datetime.fromordinal(dtstart.toordinal())
|
| + else:
|
| + dtstart = dtstart.replace(microsecond=0)
|
| + self._dtstart = dtstart
|
| + self._tzinfo = dtstart.tzinfo
|
| + self._freq = freq
|
| + self._interval = interval
|
| + self._count = count
|
| + if until and not isinstance(until, datetime.datetime):
|
| + until = datetime.datetime.fromordinal(until.toordinal())
|
| + self._until = until
|
| + if wkst is None:
|
| + self._wkst = calendar.firstweekday()
|
| + elif type(wkst) is int:
|
| + self._wkst = wkst
|
| + else:
|
| + self._wkst = wkst.weekday
|
| + if bysetpos is None:
|
| + self._bysetpos = None
|
| + elif type(bysetpos) is int:
|
| + if bysetpos == 0 or not (-366 <= bysetpos <= 366):
|
| + raise ValueError("bysetpos must be between 1 and 366, "
|
| + "or between -366 and -1")
|
| + self._bysetpos = (bysetpos,)
|
| + else:
|
| + self._bysetpos = tuple(bysetpos)
|
| + for pos in self._bysetpos:
|
| + if pos == 0 or not (-366 <= pos <= 366):
|
| + raise ValueError("bysetpos must be between 1 and 366, "
|
| + "or between -366 and -1")
|
| + if not (byweekno or byyearday or bymonthday or
|
| + byweekday is not None or byeaster is not None):
|
| + if freq == YEARLY:
|
| + if not bymonth:
|
| + bymonth = dtstart.month
|
| + bymonthday = dtstart.day
|
| + elif freq == MONTHLY:
|
| + bymonthday = dtstart.day
|
| + elif freq == WEEKLY:
|
| + byweekday = dtstart.weekday()
|
| + # bymonth
|
| + if not bymonth:
|
| + self._bymonth = None
|
| + elif type(bymonth) is int:
|
| + self._bymonth = (bymonth,)
|
| + else:
|
| + self._bymonth = tuple(bymonth)
|
| + # byyearday
|
| + if not byyearday:
|
| + self._byyearday = None
|
| + elif type(byyearday) is int:
|
| + self._byyearday = (byyearday,)
|
| + else:
|
| + self._byyearday = tuple(byyearday)
|
| + # byeaster
|
| + if byeaster is not None:
|
| + if not easter:
|
| + from dateutil import easter
|
| + if type(byeaster) is int:
|
| + self._byeaster = (byeaster,)
|
| + else:
|
| + self._byeaster = tuple(byeaster)
|
| + else:
|
| + self._byeaster = None
|
| + # bymonthay
|
| + if not bymonthday:
|
| + self._bymonthday = ()
|
| + self._bynmonthday = ()
|
| + elif type(bymonthday) is int:
|
| + if bymonthday < 0:
|
| + self._bynmonthday = (bymonthday,)
|
| + self._bymonthday = ()
|
| + else:
|
| + self._bymonthday = (bymonthday,)
|
| + self._bynmonthday = ()
|
| + else:
|
| + self._bymonthday = tuple([x for x in bymonthday if x > 0])
|
| + self._bynmonthday = tuple([x for x in bymonthday if x < 0])
|
| + # byweekno
|
| + if byweekno is None:
|
| + self._byweekno = None
|
| + elif type(byweekno) is int:
|
| + self._byweekno = (byweekno,)
|
| + else:
|
| + self._byweekno = tuple(byweekno)
|
| + # byweekday / bynweekday
|
| + if byweekday is None:
|
| + self._byweekday = None
|
| + self._bynweekday = None
|
| + elif type(byweekday) is int:
|
| + self._byweekday = (byweekday,)
|
| + self._bynweekday = None
|
| + elif hasattr(byweekday, "n"):
|
| + if not byweekday.n or freq > MONTHLY:
|
| + self._byweekday = (byweekday.weekday,)
|
| + self._bynweekday = None
|
| + else:
|
| + self._bynweekday = ((byweekday.weekday, byweekday.n),)
|
| + self._byweekday = None
|
| + else:
|
| + self._byweekday = []
|
| + self._bynweekday = []
|
| + for wday in byweekday:
|
| + if type(wday) is int:
|
| + self._byweekday.append(wday)
|
| + elif not wday.n or freq > MONTHLY:
|
| + self._byweekday.append(wday.weekday)
|
| + else:
|
| + self._bynweekday.append((wday.weekday, wday.n))
|
| + self._byweekday = tuple(self._byweekday)
|
| + self._bynweekday = tuple(self._bynweekday)
|
| + if not self._byweekday:
|
| + self._byweekday = None
|
| + elif not self._bynweekday:
|
| + self._bynweekday = None
|
| + # byhour
|
| + if byhour is None:
|
| + if freq < HOURLY:
|
| + self._byhour = (dtstart.hour,)
|
| + else:
|
| + self._byhour = None
|
| + elif type(byhour) is int:
|
| + self._byhour = (byhour,)
|
| + else:
|
| + self._byhour = tuple(byhour)
|
| + # byminute
|
| + if byminute is None:
|
| + if freq < MINUTELY:
|
| + self._byminute = (dtstart.minute,)
|
| + else:
|
| + self._byminute = None
|
| + elif type(byminute) is int:
|
| + self._byminute = (byminute,)
|
| + else:
|
| + self._byminute = tuple(byminute)
|
| + # bysecond
|
| + if bysecond is None:
|
| + if freq < SECONDLY:
|
| + self._bysecond = (dtstart.second,)
|
| + else:
|
| + self._bysecond = None
|
| + elif type(bysecond) is int:
|
| + self._bysecond = (bysecond,)
|
| + else:
|
| + self._bysecond = tuple(bysecond)
|
| +
|
| + if self._freq >= HOURLY:
|
| + self._timeset = None
|
| + else:
|
| + self._timeset = []
|
| + for hour in self._byhour:
|
| + for minute in self._byminute:
|
| + for second in self._bysecond:
|
| + self._timeset.append(
|
| + datetime.time(hour, minute, second,
|
| + tzinfo=self._tzinfo))
|
| + self._timeset.sort()
|
| + self._timeset = tuple(self._timeset)
|
| +
|
| + def _iter(self):
|
| + year, month, day, hour, minute, second, weekday, yearday, _ = \
|
| + self._dtstart.timetuple()
|
| +
|
| + # Some local variables to speed things up a bit
|
| + freq = self._freq
|
| + interval = self._interval
|
| + wkst = self._wkst
|
| + until = self._until
|
| + bymonth = self._bymonth
|
| + byweekno = self._byweekno
|
| + byyearday = self._byyearday
|
| + byweekday = self._byweekday
|
| + byeaster = self._byeaster
|
| + bymonthday = self._bymonthday
|
| + bynmonthday = self._bynmonthday
|
| + bysetpos = self._bysetpos
|
| + byhour = self._byhour
|
| + byminute = self._byminute
|
| + bysecond = self._bysecond
|
| +
|
| + ii = _iterinfo(self)
|
| + ii.rebuild(year, month)
|
| +
|
| + getdayset = {YEARLY:ii.ydayset,
|
| + MONTHLY:ii.mdayset,
|
| + WEEKLY:ii.wdayset,
|
| + DAILY:ii.ddayset,
|
| + HOURLY:ii.ddayset,
|
| + MINUTELY:ii.ddayset,
|
| + SECONDLY:ii.ddayset}[freq]
|
| +
|
| + if freq < HOURLY:
|
| + timeset = self._timeset
|
| + else:
|
| + gettimeset = {HOURLY:ii.htimeset,
|
| + MINUTELY:ii.mtimeset,
|
| + SECONDLY:ii.stimeset}[freq]
|
| + if ((freq >= HOURLY and
|
| + self._byhour and hour not in self._byhour) or
|
| + (freq >= MINUTELY and
|
| + self._byminute and minute not in self._byminute) or
|
| + (freq >= SECONDLY and
|
| + self._bysecond and second not in self._bysecond)):
|
| + timeset = ()
|
| + else:
|
| + timeset = gettimeset(hour, minute, second)
|
| +
|
| + total = 0
|
| + count = self._count
|
| + while True:
|
| + # Get dayset with the right frequency
|
| + dayset, start, end = getdayset(year, month, day)
|
| +
|
| + # Do the "hard" work ;-)
|
| + filtered = False
|
| + for i in dayset[start:end]:
|
| + if ((bymonth and ii.mmask[i] not in bymonth) or
|
| + (byweekno and not ii.wnomask[i]) or
|
| + (byweekday and ii.wdaymask[i] not in byweekday) or
|
| + (ii.nwdaymask and not ii.nwdaymask[i]) or
|
| + (byeaster and not ii.eastermask[i]) or
|
| + ((bymonthday or bynmonthday) and
|
| + ii.mdaymask[i] not in bymonthday and
|
| + ii.nmdaymask[i] not in bynmonthday) or
|
| + (byyearday and
|
| + ((i < ii.yearlen and i+1 not in byyearday
|
| + and -ii.yearlen+i not in byyearday) or
|
| + (i >= ii.yearlen and i+1-ii.yearlen not in byyearday
|
| + and -ii.nextyearlen+i-ii.yearlen
|
| + not in byyearday)))):
|
| + dayset[i] = None
|
| + filtered = True
|
| +
|
| + # Output results
|
| + if bysetpos and timeset:
|
| + poslist = []
|
| + for pos in bysetpos:
|
| + if pos < 0:
|
| + daypos, timepos = divmod(pos, len(timeset))
|
| + else:
|
| + daypos, timepos = divmod(pos-1, len(timeset))
|
| + try:
|
| + i = [x for x in dayset[start:end]
|
| + if x is not None][daypos]
|
| + time = timeset[timepos]
|
| + except IndexError:
|
| + pass
|
| + else:
|
| + date = datetime.date.fromordinal(ii.yearordinal+i)
|
| + res = datetime.datetime.combine(date, time)
|
| + if res not in poslist:
|
| + poslist.append(res)
|
| + poslist.sort()
|
| + for res in poslist:
|
| + if until and res > until:
|
| + self._len = total
|
| + return
|
| + elif res >= self._dtstart:
|
| + total += 1
|
| + yield res
|
| + if count:
|
| + count -= 1
|
| + if not count:
|
| + self._len = total
|
| + return
|
| + else:
|
| + for i in dayset[start:end]:
|
| + if i is not None:
|
| + date = datetime.date.fromordinal(ii.yearordinal+i)
|
| + for time in timeset:
|
| + res = datetime.datetime.combine(date, time)
|
| + if until and res > until:
|
| + self._len = total
|
| + return
|
| + elif res >= self._dtstart:
|
| + total += 1
|
| + yield res
|
| + if count:
|
| + count -= 1
|
| + if not count:
|
| + self._len = total
|
| + return
|
| +
|
| + # Handle frequency and interval
|
| + fixday = False
|
| + if freq == YEARLY:
|
| + year += interval
|
| + if year > datetime.MAXYEAR:
|
| + self._len = total
|
| + return
|
| + ii.rebuild(year, month)
|
| + elif freq == MONTHLY:
|
| + month += interval
|
| + if month > 12:
|
| + div, mod = divmod(month, 12)
|
| + month = mod
|
| + year += div
|
| + if month == 0:
|
| + month = 12
|
| + year -= 1
|
| + if year > datetime.MAXYEAR:
|
| + self._len = total
|
| + return
|
| + ii.rebuild(year, month)
|
| + elif freq == WEEKLY:
|
| + if wkst > weekday:
|
| + day += -(weekday+1+(6-wkst))+self._interval*7
|
| + else:
|
| + day += -(weekday-wkst)+self._interval*7
|
| + weekday = wkst
|
| + fixday = True
|
| + elif freq == DAILY:
|
| + day += interval
|
| + fixday = True
|
| + elif freq == HOURLY:
|
| + if filtered:
|
| + # Jump to one iteration before next day
|
| + hour += ((23-hour)//interval)*interval
|
| + while True:
|
| + hour += interval
|
| + div, mod = divmod(hour, 24)
|
| + if div:
|
| + hour = mod
|
| + day += div
|
| + fixday = True
|
| + if not byhour or hour in byhour:
|
| + break
|
| + timeset = gettimeset(hour, minute, second)
|
| + elif freq == MINUTELY:
|
| + if filtered:
|
| + # Jump to one iteration before next day
|
| + minute += ((1439-(hour*60+minute))//interval)*interval
|
| + while True:
|
| + minute += interval
|
| + div, mod = divmod(minute, 60)
|
| + if div:
|
| + minute = mod
|
| + hour += div
|
| + div, mod = divmod(hour, 24)
|
| + if div:
|
| + hour = mod
|
| + day += div
|
| + fixday = True
|
| + filtered = False
|
| + if ((not byhour or hour in byhour) and
|
| + (not byminute or minute in byminute)):
|
| + break
|
| + timeset = gettimeset(hour, minute, second)
|
| + elif freq == SECONDLY:
|
| + if filtered:
|
| + # Jump to one iteration before next day
|
| + second += (((86399-(hour*3600+minute*60+second))
|
| + //interval)*interval)
|
| + while True:
|
| + second += self._interval
|
| + div, mod = divmod(second, 60)
|
| + if div:
|
| + second = mod
|
| + minute += div
|
| + div, mod = divmod(minute, 60)
|
| + if div:
|
| + minute = mod
|
| + hour += div
|
| + div, mod = divmod(hour, 24)
|
| + if div:
|
| + hour = mod
|
| + day += div
|
| + fixday = True
|
| + if ((not byhour or hour in byhour) and
|
| + (not byminute or minute in byminute) and
|
| + (not bysecond or second in bysecond)):
|
| + break
|
| + timeset = gettimeset(hour, minute, second)
|
| +
|
| + if fixday and day > 28:
|
| + daysinmonth = calendar.monthrange(year, month)[1]
|
| + if day > daysinmonth:
|
| + while day > daysinmonth:
|
| + day -= daysinmonth
|
| + month += 1
|
| + if month == 13:
|
| + month = 1
|
| + year += 1
|
| + if year > datetime.MAXYEAR:
|
| + self._len = total
|
| + return
|
| + daysinmonth = calendar.monthrange(year, month)[1]
|
| + ii.rebuild(year, month)
|
| +
|
| +class _iterinfo(object):
|
| + __slots__ = ["rrule", "lastyear", "lastmonth",
|
| + "yearlen", "nextyearlen", "yearordinal", "yearweekday",
|
| + "mmask", "mrange", "mdaymask", "nmdaymask",
|
| + "wdaymask", "wnomask", "nwdaymask", "eastermask"]
|
| +
|
| + def __init__(self, rrule):
|
| + for attr in self.__slots__:
|
| + setattr(self, attr, None)
|
| + self.rrule = rrule
|
| +
|
| + def rebuild(self, year, month):
|
| + # Every mask is 7 days longer to handle cross-year weekly periods.
|
| + rr = self.rrule
|
| + if year != self.lastyear:
|
| + self.yearlen = 365+calendar.isleap(year)
|
| + self.nextyearlen = 365+calendar.isleap(year+1)
|
| + firstyday = datetime.date(year, 1, 1)
|
| + self.yearordinal = firstyday.toordinal()
|
| + self.yearweekday = firstyday.weekday()
|
| +
|
| + wday = datetime.date(year, 1, 1).weekday()
|
| + if self.yearlen == 365:
|
| + self.mmask = M365MASK
|
| + self.mdaymask = MDAY365MASK
|
| + self.nmdaymask = NMDAY365MASK
|
| + self.wdaymask = WDAYMASK[wday:]
|
| + self.mrange = M365RANGE
|
| + else:
|
| + self.mmask = M366MASK
|
| + self.mdaymask = MDAY366MASK
|
| + self.nmdaymask = NMDAY366MASK
|
| + self.wdaymask = WDAYMASK[wday:]
|
| + self.mrange = M366RANGE
|
| +
|
| + if not rr._byweekno:
|
| + self.wnomask = None
|
| + else:
|
| + self.wnomask = [0]*(self.yearlen+7)
|
| + #no1wkst = firstwkst = self.wdaymask.index(rr._wkst)
|
| + no1wkst = firstwkst = (7-self.yearweekday+rr._wkst)%7
|
| + if no1wkst >= 4:
|
| + no1wkst = 0
|
| + # Number of days in the year, plus the days we got
|
| + # from last year.
|
| + wyearlen = self.yearlen+(self.yearweekday-rr._wkst)%7
|
| + else:
|
| + # Number of days in the year, minus the days we
|
| + # left in last year.
|
| + wyearlen = self.yearlen-no1wkst
|
| + div, mod = divmod(wyearlen, 7)
|
| + numweeks = div+mod//4
|
| + for n in rr._byweekno:
|
| + if n < 0:
|
| + n += numweeks+1
|
| + if not (0 < n <= numweeks):
|
| + continue
|
| + if n > 1:
|
| + i = no1wkst+(n-1)*7
|
| + if no1wkst != firstwkst:
|
| + i -= 7-firstwkst
|
| + else:
|
| + i = no1wkst
|
| + for j in range(7):
|
| + self.wnomask[i] = 1
|
| + i += 1
|
| + if self.wdaymask[i] == rr._wkst:
|
| + break
|
| + if 1 in rr._byweekno:
|
| + # Check week number 1 of next year as well
|
| + # TODO: Check -numweeks for next year.
|
| + i = no1wkst+numweeks*7
|
| + if no1wkst != firstwkst:
|
| + i -= 7-firstwkst
|
| + if i < self.yearlen:
|
| + # If week starts in next year, we
|
| + # don't care about it.
|
| + for j in range(7):
|
| + self.wnomask[i] = 1
|
| + i += 1
|
| + if self.wdaymask[i] == rr._wkst:
|
| + break
|
| + if no1wkst:
|
| + # Check last week number of last year as
|
| + # well. If no1wkst is 0, either the year
|
| + # started on week start, or week number 1
|
| + # got days from last year, so there are no
|
| + # days from last year's last week number in
|
| + # this year.
|
| + if -1 not in rr._byweekno:
|
| + lyearweekday = datetime.date(year-1,1,1).weekday()
|
| + lno1wkst = (7-lyearweekday+rr._wkst)%7
|
| + lyearlen = 365+calendar.isleap(year-1)
|
| + if lno1wkst >= 4:
|
| + lno1wkst = 0
|
| + lnumweeks = 52+(lyearlen+
|
| + (lyearweekday-rr._wkst)%7)%7//4
|
| + else:
|
| + lnumweeks = 52+(self.yearlen-no1wkst)%7//4
|
| + else:
|
| + lnumweeks = -1
|
| + if lnumweeks in rr._byweekno:
|
| + for i in range(no1wkst):
|
| + self.wnomask[i] = 1
|
| +
|
| + if (rr._bynweekday and
|
| + (month != self.lastmonth or year != self.lastyear)):
|
| + ranges = []
|
| + if rr._freq == YEARLY:
|
| + if rr._bymonth:
|
| + for month in rr._bymonth:
|
| + ranges.append(self.mrange[month-1:month+1])
|
| + else:
|
| + ranges = [(0, self.yearlen)]
|
| + elif rr._freq == MONTHLY:
|
| + ranges = [self.mrange[month-1:month+1]]
|
| + if ranges:
|
| + # Weekly frequency won't get here, so we may not
|
| + # care about cross-year weekly periods.
|
| + self.nwdaymask = [0]*self.yearlen
|
| + for first, last in ranges:
|
| + last -= 1
|
| + for wday, n in rr._bynweekday:
|
| + if n < 0:
|
| + i = last+(n+1)*7
|
| + i -= (self.wdaymask[i]-wday)%7
|
| + else:
|
| + i = first+(n-1)*7
|
| + i += (7-self.wdaymask[i]+wday)%7
|
| + if first <= i <= last:
|
| + self.nwdaymask[i] = 1
|
| +
|
| + if rr._byeaster:
|
| + self.eastermask = [0]*(self.yearlen+7)
|
| + eyday = easter.easter(year).toordinal()-self.yearordinal
|
| + for offset in rr._byeaster:
|
| + self.eastermask[eyday+offset] = 1
|
| +
|
| + self.lastyear = year
|
| + self.lastmonth = month
|
| +
|
| + def ydayset(self, year, month, day):
|
| + return range(self.yearlen), 0, self.yearlen
|
| +
|
| + def mdayset(self, year, month, day):
|
| + set = [None]*self.yearlen
|
| + start, end = self.mrange[month-1:month+1]
|
| + for i in range(start, end):
|
| + set[i] = i
|
| + return set, start, end
|
| +
|
| + def wdayset(self, year, month, day):
|
| + # We need to handle cross-year weeks here.
|
| + set = [None]*(self.yearlen+7)
|
| + i = datetime.date(year, month, day).toordinal()-self.yearordinal
|
| + start = i
|
| + for j in range(7):
|
| + set[i] = i
|
| + i += 1
|
| + #if (not (0 <= i < self.yearlen) or
|
| + # self.wdaymask[i] == self.rrule._wkst):
|
| + # This will cross the year boundary, if necessary.
|
| + if self.wdaymask[i] == self.rrule._wkst:
|
| + break
|
| + return set, start, i
|
| +
|
| + def ddayset(self, year, month, day):
|
| + set = [None]*self.yearlen
|
| + i = datetime.date(year, month, day).toordinal()-self.yearordinal
|
| + set[i] = i
|
| + return set, i, i+1
|
| +
|
| + def htimeset(self, hour, minute, second):
|
| + set = []
|
| + rr = self.rrule
|
| + for minute in rr._byminute:
|
| + for second in rr._bysecond:
|
| + set.append(datetime.time(hour, minute, second,
|
| + tzinfo=rr._tzinfo))
|
| + set.sort()
|
| + return set
|
| +
|
| + def mtimeset(self, hour, minute, second):
|
| + set = []
|
| + rr = self.rrule
|
| + for second in rr._bysecond:
|
| + set.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo))
|
| + set.sort()
|
| + return set
|
| +
|
| + def stimeset(self, hour, minute, second):
|
| + return (datetime.time(hour, minute, second,
|
| + tzinfo=self.rrule._tzinfo),)
|
| +
|
| +
|
| +class rruleset(rrulebase):
|
| +
|
| + class _genitem:
|
| + def __init__(self, genlist, gen):
|
| + try:
|
| + self.dt = gen()
|
| + genlist.append(self)
|
| + except StopIteration:
|
| + pass
|
| + self.genlist = genlist
|
| + self.gen = gen
|
| +
|
| + def next(self):
|
| + try:
|
| + self.dt = self.gen()
|
| + except StopIteration:
|
| + self.genlist.remove(self)
|
| +
|
| + def __cmp__(self, other):
|
| + return cmp(self.dt, other.dt)
|
| +
|
| + def __init__(self, cache=False):
|
| + rrulebase.__init__(self, cache)
|
| + self._rrule = []
|
| + self._rdate = []
|
| + self._exrule = []
|
| + self._exdate = []
|
| +
|
| + def rrule(self, rrule):
|
| + self._rrule.append(rrule)
|
| +
|
| + def rdate(self, rdate):
|
| + self._rdate.append(rdate)
|
| +
|
| + def exrule(self, exrule):
|
| + self._exrule.append(exrule)
|
| +
|
| + def exdate(self, exdate):
|
| + self._exdate.append(exdate)
|
| +
|
| + def _iter(self):
|
| + rlist = []
|
| + self._rdate.sort()
|
| + self._genitem(rlist, iter(self._rdate).next)
|
| + for gen in [iter(x).next for x in self._rrule]:
|
| + self._genitem(rlist, gen)
|
| + rlist.sort()
|
| + exlist = []
|
| + self._exdate.sort()
|
| + self._genitem(exlist, iter(self._exdate).next)
|
| + for gen in [iter(x).next for x in self._exrule]:
|
| + self._genitem(exlist, gen)
|
| + exlist.sort()
|
| + lastdt = None
|
| + total = 0
|
| + while rlist:
|
| + ritem = rlist[0]
|
| + if not lastdt or lastdt != ritem.dt:
|
| + while exlist and exlist[0] < ritem:
|
| + exlist[0].next()
|
| + exlist.sort()
|
| + if not exlist or ritem != exlist[0]:
|
| + total += 1
|
| + yield ritem.dt
|
| + lastdt = ritem.dt
|
| + ritem.next()
|
| + rlist.sort()
|
| + self._len = total
|
| +
|
| +class _rrulestr:
|
| +
|
| + _freq_map = {"YEARLY": YEARLY,
|
| + "MONTHLY": MONTHLY,
|
| + "WEEKLY": WEEKLY,
|
| + "DAILY": DAILY,
|
| + "HOURLY": HOURLY,
|
| + "MINUTELY": MINUTELY,
|
| + "SECONDLY": SECONDLY}
|
| +
|
| + _weekday_map = {"MO":0,"TU":1,"WE":2,"TH":3,"FR":4,"SA":5,"SU":6}
|
| +
|
| + def _handle_int(self, rrkwargs, name, value, **kwargs):
|
| + rrkwargs[name.lower()] = int(value)
|
| +
|
| + def _handle_int_list(self, rrkwargs, name, value, **kwargs):
|
| + rrkwargs[name.lower()] = [int(x) for x in value.split(',')]
|
| +
|
| + _handle_INTERVAL = _handle_int
|
| + _handle_COUNT = _handle_int
|
| + _handle_BYSETPOS = _handle_int_list
|
| + _handle_BYMONTH = _handle_int_list
|
| + _handle_BYMONTHDAY = _handle_int_list
|
| + _handle_BYYEARDAY = _handle_int_list
|
| + _handle_BYEASTER = _handle_int_list
|
| + _handle_BYWEEKNO = _handle_int_list
|
| + _handle_BYHOUR = _handle_int_list
|
| + _handle_BYMINUTE = _handle_int_list
|
| + _handle_BYSECOND = _handle_int_list
|
| +
|
| + def _handle_FREQ(self, rrkwargs, name, value, **kwargs):
|
| + rrkwargs["freq"] = self._freq_map[value]
|
| +
|
| + def _handle_UNTIL(self, rrkwargs, name, value, **kwargs):
|
| + global parser
|
| + if not parser:
|
| + from dateutil import parser
|
| + try:
|
| + rrkwargs["until"] = parser.parse(value,
|
| + ignoretz=kwargs.get("ignoretz"),
|
| + tzinfos=kwargs.get("tzinfos"))
|
| + except ValueError:
|
| + raise ValueError, "invalid until date"
|
| +
|
| + def _handle_WKST(self, rrkwargs, name, value, **kwargs):
|
| + rrkwargs["wkst"] = self._weekday_map[value]
|
| +
|
| + def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwarsg):
|
| + 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)
|
| + l.append(weekdays[self._weekday_map[w]](n))
|
| + rrkwargs["byweekday"] = l
|
| +
|
| + _handle_BYDAY = _handle_BYWEEKDAY
|
| +
|
| + def _parse_rfc_rrule(self, line,
|
| + dtstart=None,
|
| + cache=False,
|
| + ignoretz=False,
|
| + tzinfos=None):
|
| + if line.find(':') != -1:
|
| + name, value = line.split(':')
|
| + if name != "RRULE":
|
| + raise ValueError, "unknown parameter name"
|
| + else:
|
| + value = line
|
| + rrkwargs = {}
|
| + for pair in value.split(';'):
|
| + name, value = pair.split('=')
|
| + name = name.upper()
|
| + value = value.upper()
|
| + try:
|
| + getattr(self, "_handle_"+name)(rrkwargs, name, value,
|
| + ignoretz=ignoretz,
|
| + tzinfos=tzinfos)
|
| + except AttributeError:
|
| + raise ValueError, "unknown parameter '%s'" % name
|
| + except (KeyError, ValueError):
|
| + raise ValueError, "invalid '%s': %s" % (name, value)
|
| + return rrule(dtstart=dtstart, cache=cache, **rrkwargs)
|
| +
|
| + def _parse_rfc(self, s,
|
| + dtstart=None,
|
| + cache=False,
|
| + unfold=False,
|
| + forceset=False,
|
| + compatible=False,
|
| + ignoretz=False,
|
| + tzinfos=None):
|
| + global parser
|
| + if compatible:
|
| + forceset = True
|
| + unfold = True
|
| + s = s.upper()
|
| + if not s.strip():
|
| + raise ValueError, "empty string"
|
| + if unfold:
|
| + lines = s.splitlines()
|
| + 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
|
| + else:
|
| + lines = s.split()
|
| + if (not forceset and len(lines) == 1 and
|
| + (s.find(':') == -1 or s.startswith('RRULE:'))):
|
| + return self._parse_rfc_rrule(lines[0], cache=cache,
|
| + dtstart=dtstart, ignoretz=ignoretz,
|
| + tzinfos=tzinfos)
|
| + else:
|
| + rrulevals = []
|
| + rdatevals = []
|
| + exrulevals = []
|
| + exdatevals = []
|
| + for line in lines:
|
| + if not line:
|
| + continue
|
| + if line.find(':') == -1:
|
| + name = "RRULE"
|
| + value = line
|
| + else:
|
| + name, value = line.split(':', 1)
|
| + parms = name.split(';')
|
| + if not parms:
|
| + raise ValueError, "empty property name"
|
| + name = parms[0]
|
| + parms = parms[1:]
|
| + if name == "RRULE":
|
| + for parm in parms:
|
| + raise ValueError, "unsupported RRULE parm: "+parm
|
| + rrulevals.append(value)
|
| + elif name == "RDATE":
|
| + for parm in parms:
|
| + if parm != "VALUE=DATE-TIME":
|
| + raise ValueError, "unsupported RDATE parm: "+parm
|
| + rdatevals.append(value)
|
| + elif name == "EXRULE":
|
| + for parm in parms:
|
| + raise ValueError, "unsupported EXRULE parm: "+parm
|
| + exrulevals.append(value)
|
| + elif name == "EXDATE":
|
| + for parm in parms:
|
| + if parm != "VALUE=DATE-TIME":
|
| + raise ValueError, "unsupported RDATE parm: "+parm
|
| + exdatevals.append(value)
|
| + elif name == "DTSTART":
|
| + for parm in parms:
|
| + raise ValueError, "unsupported DTSTART parm: "+parm
|
| + if not parser:
|
| + from dateutil import parser
|
| + dtstart = parser.parse(value, ignoretz=ignoretz,
|
| + tzinfos=tzinfos)
|
| + else:
|
| + raise ValueError, "unsupported property: "+name
|
| + if (forceset or len(rrulevals) > 1 or
|
| + rdatevals or exrulevals or exdatevals):
|
| + if not parser and (rdatevals or exdatevals):
|
| + from dateutil import parser
|
| + set = rruleset(cache=cache)
|
| + for value in rrulevals:
|
| + set.rrule(self._parse_rfc_rrule(value, dtstart=dtstart,
|
| + ignoretz=ignoretz,
|
| + tzinfos=tzinfos))
|
| + for value in rdatevals:
|
| + for datestr in value.split(','):
|
| + set.rdate(parser.parse(datestr,
|
| + ignoretz=ignoretz,
|
| + tzinfos=tzinfos))
|
| + for value in exrulevals:
|
| + set.exrule(self._parse_rfc_rrule(value, dtstart=dtstart,
|
| + ignoretz=ignoretz,
|
| + tzinfos=tzinfos))
|
| + for value in exdatevals:
|
| + for datestr in value.split(','):
|
| + set.exdate(parser.parse(datestr,
|
| + ignoretz=ignoretz,
|
| + tzinfos=tzinfos))
|
| + if compatible and dtstart:
|
| + set.rdate(dtstart)
|
| + return set
|
| + else:
|
| + return self._parse_rfc_rrule(rrulevals[0],
|
| + dtstart=dtstart,
|
| + cache=cache,
|
| + ignoretz=ignoretz,
|
| + tzinfos=tzinfos)
|
| +
|
| + def __call__(self, s, **kwargs):
|
| + return self._parse_rfc(s, **kwargs)
|
| +
|
| +rrulestr = _rrulestr()
|
| +
|
| +# vim:ts=4:sw=4:et
|
|
|