NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

rrule.py (43489B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\dateutil\rrule.py
      2 import itertools
      3 import datetime
      4 import calendar
      5 import sys
      6 try:
      7     from math import gcd
      8 except ImportError:
      9     from fractions import gcd
     10 
     11 from six import advance_iterator, integer_types
     12 from six.moves import _thread, range
     13 import heapq
     14 from ._common import weekday as weekdaybase
     15 from warnings import warn
     16 __all__ = ['rrule',
     17  'rruleset',
     18  'rrulestr',
     19  'YEARLY',
     20  'MONTHLY',
     21  'WEEKLY',
     22  'DAILY',
     23  'HOURLY',
     24  'MINUTELY',
     25  'SECONDLY',
     26  'MO',
     27  'TU',
     28  'WE',
     29  'TH',
     30  'FR',
     31  'SA',
     32  'SU']
     33 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)
     34 M365MASK = list(M366MASK)
     35 M29, M30, M31 = (list(range(1, 30)), list(range(1, 31)), list(range(1, 32)))
     36 MDAY366MASK = tuple(M31 + M29 + M31 + M30 + M31 + M30 + M31 + M31 + M30 + M31 + M30 + M31 + M31[:7])
     37 MDAY365MASK = list(MDAY366MASK)
     38 M29, M30, M31 = (list(range(-29, 0)), list(range(-30, 0)), list(range(-31, 0)))
     39 NMDAY366MASK = tuple(M31 + M29 + M31 + M30 + M31 + M30 + M31 + M31 + M30 + M31 + M30 + M31 + M31[:7])
     40 NMDAY365MASK = list(NMDAY366MASK)
     41 M366RANGE = (0,
     42  31,
     43  60,
     44  91,
     45  121,
     46  152,
     47  182,
     48  213,
     49  244,
     50  274,
     51  305,
     52  335,
     53  366)
     54 M365RANGE = (0,
     55  31,
     56  59,
     57  90,
     58  120,
     59  151,
     60  181,
     61  212,
     62  243,
     63  273,
     64  304,
     65  334,
     66  365)
     67 WDAYMASK = [0,
     68  1,
     69  2,
     70  3,
     71  4,
     72  5,
     73  6] * 55
     74 del M29
     75 del M30
     76 del M31
     77 del M365MASK[59]
     78 del MDAY365MASK[59]
     79 del NMDAY365MASK[31]
     80 MDAY365MASK = tuple(MDAY365MASK)
     81 M365MASK = tuple(M365MASK)
     82 FREQNAMES = ['YEARLY',
     83  'MONTHLY',
     84  'WEEKLY',
     85  'DAILY',
     86  'HOURLY',
     87  'MINUTELY',
     88  'SECONDLY']
     89 YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY = list(range(7))
     90 easter = None
     91 parser = None
     92 
     93 class weekday(weekdaybase):
     94 
     95     def __init__(self, wkday, n = None):
     96         if n == 0:
     97             raise ValueError("Can't create weekday with n==0")
     98         super(weekday, self).__init__(wkday, n)
     99 
    100 
    101 MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([ weekday(x) for x in range(7) ])
    102 
    103 def _invalidates_cache(f):
    104 
    105     def inner_func(self, *args, **kwargs):
    106         rv = f(self, *args, **kwargs)
    107         self._invalidate_cache()
    108         return rv
    109 
    110     return inner_func
    111 
    112 
    113 class rrulebase(object):
    114 
    115     def __init__(self, cache = False):
    116         if cache:
    117             self._cache = []
    118             self._cache_lock = _thread.allocate_lock()
    119             self._invalidate_cache()
    120         else:
    121             self._cache = None
    122             self._cache_complete = False
    123             self._len = None
    124 
    125     def __iter__(self):
    126         if self._cache_complete:
    127             return iter(self._cache)
    128         elif self._cache is None:
    129             return self._iter()
    130         else:
    131             return self._iter_cached()
    132 
    133     def _invalidate_cache(self):
    134         if self._cache is not None:
    135             self._cache = []
    136             self._cache_complete = False
    137             self._cache_gen = self._iter()
    138             if self._cache_lock.locked():
    139                 self._cache_lock.release()
    140         self._len = None
    141 
    142     def _iter_cached(self):
    143         i = 0
    144         gen = self._cache_gen
    145         cache = self._cache
    146         acquire = self._cache_lock.acquire
    147         release = self._cache_lock.release
    148         while gen:
    149             if i == len(cache):
    150                 acquire()
    151                 if self._cache_complete:
    152                     break
    153                 try:
    154                     for j in range(10):
    155                         cache.append(advance_iterator(gen))
    156 
    157                 except StopIteration:
    158                     self._cache_gen = gen = None
    159                     self._cache_complete = True
    160                     break
    161 
    162                 release()
    163             yield cache[i]
    164             i += 1
    165 
    166         while i < self._len:
    167             yield cache[i]
    168             i += 1
    169 
    170     def __getitem__(self, item):
    171         if self._cache_complete:
    172             return self._cache[item]
    173         if isinstance(item, slice):
    174             if item.step and item.step < 0:
    175                 return list(iter(self))[item]
    176             else:
    177                 return list(itertools.islice(self, item.start or 0, item.stop or sys.maxsize, item.step or 1))
    178         else:
    179             if item >= 0:
    180                 gen = iter(self)
    181                 try:
    182                     for i in range(item + 1):
    183                         res = advance_iterator(gen)
    184 
    185                 except StopIteration:
    186                     raise IndexError
    187 
    188                 return res
    189             return list(iter(self))[item]
    190 
    191     def __contains__(self, item):
    192         if self._cache_complete:
    193             return item in self._cache
    194         for i in self:
    195             if i == item:
    196                 return True
    197             if i > item:
    198                 return False
    199 
    200         return False
    201 
    202     def count(self):
    203         if self._len is None:
    204             for x in self:
    205                 pass
    206 
    207         return self._len
    208 
    209     def before(self, dt, inc = False):
    210         if self._cache_complete:
    211             gen = self._cache
    212         else:
    213             gen = self
    214         last = None
    215         if inc:
    216             for i in gen:
    217                 if i > dt:
    218                     break
    219                 last = i
    220 
    221         else:
    222             for i in gen:
    223                 if i >= dt:
    224                     break
    225                 last = i
    226 
    227         return last
    228 
    229     def after(self, dt, inc = False):
    230         if self._cache_complete:
    231             gen = self._cache
    232         else:
    233             gen = self
    234         if inc:
    235             for i in gen:
    236                 if i >= dt:
    237                     return i
    238 
    239         else:
    240             for i in gen:
    241                 if i > dt:
    242                     return i
    243 
    244     def xafter(self, dt, count = None, inc = False):
    245         if self._cache_complete:
    246             gen = self._cache
    247         else:
    248             gen = self
    249         if inc:
    250             comp = lambda dc, dtc: dc >= dtc
    251         else:
    252             comp = lambda dc, dtc: dc > dtc
    253         n = 0
    254         for d in gen:
    255             if comp(d, dt):
    256                 yield d
    257                 if count is not None:
    258                     n += 1
    259                     if n >= count:
    260                         break
    261 
    262     def between(self, after, before, inc = False, count = 1):
    263         if self._cache_complete:
    264             gen = self._cache
    265         else:
    266             gen = self
    267         started = False
    268         l = []
    269         if inc:
    270             for i in gen:
    271                 if i > before:
    272                     break
    273                 elif not started:
    274                     if i >= after:
    275                         started = True
    276                         l.append(i)
    277                 else:
    278                     l.append(i)
    279 
    280         else:
    281             for i in gen:
    282                 if i >= before:
    283                     break
    284                 elif not started:
    285                     if i > after:
    286                         started = True
    287                         l.append(i)
    288                 else:
    289                     l.append(i)
    290 
    291         return l
    292 
    293 
    294 class rrule(rrulebase):
    295 
    296     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):
    297         global easter
    298         super(rrule, self).__init__(cache)
    299         if not dtstart:
    300             dtstart = datetime.datetime.now().replace(microsecond=0)
    301         elif not isinstance(dtstart, datetime.datetime):
    302             dtstart = datetime.datetime.fromordinal(dtstart.toordinal())
    303         else:
    304             dtstart = dtstart.replace(microsecond=0)
    305         self._dtstart = dtstart
    306         self._tzinfo = dtstart.tzinfo
    307         self._freq = freq
    308         self._interval = interval
    309         self._count = count
    310         self._original_rule = {}
    311         if until and not isinstance(until, datetime.datetime):
    312             until = datetime.datetime.fromordinal(until.toordinal())
    313         self._until = until
    314         if count and until:
    315             warn("Using both 'count' and 'until' is inconsistent with RFC 2445 and has been deprecated in dateutil. Future versions will raise an error.", DeprecationWarning)
    316         if wkst is None:
    317             self._wkst = calendar.firstweekday()
    318         elif isinstance(wkst, integer_types):
    319             self._wkst = wkst
    320         else:
    321             self._wkst = wkst.weekday
    322         if bysetpos is None:
    323             self._bysetpos = None
    324         elif isinstance(bysetpos, integer_types):
    325             if bysetpos == 0 or not -366 <= bysetpos <= 366:
    326                 raise ValueError('bysetpos must be between 1 and 366, or between -366 and -1')
    327             self._bysetpos = (bysetpos,)
    328         else:
    329             self._bysetpos = tuple(bysetpos)
    330             for pos in self._bysetpos:
    331                 if pos == 0 or not -366 <= pos <= 366:
    332                     raise ValueError('bysetpos must be between 1 and 366, or between -366 and -1')
    333 
    334         if self._bysetpos:
    335             self._original_rule['bysetpos'] = self._bysetpos
    336         if byweekno is None and byyearday is None and bymonthday is None and byweekday is None and byeaster is None:
    337             if freq == YEARLY:
    338                 if bymonth is None:
    339                     bymonth = dtstart.month
    340                     self._original_rule['bymonth'] = None
    341                 bymonthday = dtstart.day
    342                 self._original_rule['bymonthday'] = None
    343             elif freq == MONTHLY:
    344                 bymonthday = dtstart.day
    345                 self._original_rule['bymonthday'] = None
    346             elif freq == WEEKLY:
    347                 byweekday = dtstart.weekday()
    348                 self._original_rule['byweekday'] = None
    349         if bymonth is None:
    350             self._bymonth = None
    351         else:
    352             if isinstance(bymonth, integer_types):
    353                 bymonth = (bymonth,)
    354             self._bymonth = tuple(sorted(set(bymonth)))
    355             if 'bymonth' not in self._original_rule:
    356                 self._original_rule['bymonth'] = self._bymonth
    357         if byyearday is None:
    358             self._byyearday = None
    359         else:
    360             if isinstance(byyearday, integer_types):
    361                 byyearday = (byyearday,)
    362             self._byyearday = tuple(sorted(set(byyearday)))
    363             self._original_rule['byyearday'] = self._byyearday
    364         if byeaster is not None:
    365             if not easter:
    366                 from dateutil import easter
    367             if isinstance(byeaster, integer_types):
    368                 self._byeaster = (byeaster,)
    369             else:
    370                 self._byeaster = tuple(sorted(byeaster))
    371             self._original_rule['byeaster'] = self._byeaster
    372         else:
    373             self._byeaster = None
    374         if bymonthday is None:
    375             self._bymonthday = ()
    376             self._bynmonthday = ()
    377         else:
    378             if isinstance(bymonthday, integer_types):
    379                 bymonthday = (bymonthday,)
    380             bymonthday = set(bymonthday)
    381             self._bymonthday = tuple(sorted([ x for x in bymonthday if x > 0 ]))
    382             self._bynmonthday = tuple(sorted([ x for x in bymonthday if x < 0 ]))
    383             if 'bymonthday' not in self._original_rule:
    384                 self._original_rule['bymonthday'] = tuple(itertools.chain(self._bymonthday, self._bynmonthday))
    385         if byweekno is None:
    386             self._byweekno = None
    387         else:
    388             if isinstance(byweekno, integer_types):
    389                 byweekno = (byweekno,)
    390             self._byweekno = tuple(sorted(set(byweekno)))
    391             self._original_rule['byweekno'] = self._byweekno
    392         if byweekday is None:
    393             self._byweekday = None
    394             self._bynweekday = None
    395         else:
    396             if isinstance(byweekday, integer_types) or hasattr(byweekday, 'n'):
    397                 byweekday = (byweekday,)
    398             self._byweekday = set()
    399             self._bynweekday = set()
    400             for wday in byweekday:
    401                 if isinstance(wday, integer_types):
    402                     self._byweekday.add(wday)
    403                 elif not wday.n or freq > MONTHLY:
    404                     self._byweekday.add(wday.weekday)
    405                 else:
    406                     self._bynweekday.add((wday.weekday, wday.n))
    407 
    408             if not self._byweekday:
    409                 self._byweekday = None
    410             elif not self._bynweekday:
    411                 self._bynweekday = None
    412             if self._byweekday is not None:
    413                 self._byweekday = tuple(sorted(self._byweekday))
    414                 orig_byweekday = [ weekday(x) for x in self._byweekday ]
    415             else:
    416                 orig_byweekday = tuple()
    417             if self._bynweekday is not None:
    418                 self._bynweekday = tuple(sorted(self._bynweekday))
    419                 orig_bynweekday = [ weekday(*x) for x in self._bynweekday ]
    420             else:
    421                 orig_bynweekday = tuple()
    422             if 'byweekday' not in self._original_rule:
    423                 self._original_rule['byweekday'] = tuple(itertools.chain(orig_byweekday, orig_bynweekday))
    424         if byhour is None:
    425             if freq < HOURLY:
    426                 self._byhour = set((dtstart.hour,))
    427             else:
    428                 self._byhour = None
    429         else:
    430             if isinstance(byhour, integer_types):
    431                 byhour = (byhour,)
    432             if freq == HOURLY:
    433                 self._byhour = self.__construct_byset(start=dtstart.hour, byxxx=byhour, base=24)
    434             else:
    435                 self._byhour = set(byhour)
    436             self._byhour = tuple(sorted(self._byhour))
    437             self._original_rule['byhour'] = self._byhour
    438         if byminute is None:
    439             if freq < MINUTELY:
    440                 self._byminute = set((dtstart.minute,))
    441             else:
    442                 self._byminute = None
    443         else:
    444             if isinstance(byminute, integer_types):
    445                 byminute = (byminute,)
    446             if freq == MINUTELY:
    447                 self._byminute = self.__construct_byset(start=dtstart.minute, byxxx=byminute, base=60)
    448             else:
    449                 self._byminute = set(byminute)
    450             self._byminute = tuple(sorted(self._byminute))
    451             self._original_rule['byminute'] = self._byminute
    452         if bysecond is None:
    453             if freq < SECONDLY:
    454                 self._bysecond = (dtstart.second,)
    455             else:
    456                 self._bysecond = None
    457         else:
    458             if isinstance(bysecond, integer_types):
    459                 bysecond = (bysecond,)
    460             self._bysecond = set(bysecond)
    461             if freq == SECONDLY:
    462                 self._bysecond = self.__construct_byset(start=dtstart.second, byxxx=bysecond, base=60)
    463             else:
    464                 self._bysecond = set(bysecond)
    465             self._bysecond = tuple(sorted(self._bysecond))
    466             self._original_rule['bysecond'] = self._bysecond
    467         if self._freq >= HOURLY:
    468             self._timeset = None
    469         else:
    470             self._timeset = []
    471             for hour in self._byhour:
    472                 for minute in self._byminute:
    473                     for second in self._bysecond:
    474                         self._timeset.append(datetime.time(hour, minute, second, tzinfo=self._tzinfo))
    475 
    476             self._timeset.sort()
    477             self._timeset = tuple(self._timeset)
    478 
    479     def __str__(self):
    480         output = []
    481         h, m, s = [None] * 3
    482         if self._dtstart:
    483             output.append(self._dtstart.strftime('DTSTART:%Y%m%dT%H%M%S'))
    484             h, m, s = self._dtstart.timetuple()[3:6]
    485         parts = ['FREQ=' + FREQNAMES[self._freq]]
    486         if self._interval != 1:
    487             parts.append('INTERVAL=' + str(self._interval))
    488         if self._wkst:
    489             parts.append('WKST=' + repr(weekday(self._wkst))[0:2])
    490         if self._count:
    491             parts.append('COUNT=' + str(self._count))
    492         if self._until:
    493             parts.append(self._until.strftime('UNTIL=%Y%m%dT%H%M%S'))
    494         if self._original_rule.get('byweekday') is not None:
    495             original_rule = dict(self._original_rule)
    496             wday_strings = []
    497             for wday in original_rule['byweekday']:
    498                 if wday.n:
    499                     wday_strings.append('{n:+d}{wday}'.format(n=wday.n, wday=repr(wday)[0:2]))
    500                 else:
    501                     wday_strings.append(repr(wday))
    502 
    503             original_rule['byweekday'] = wday_strings
    504         else:
    505             original_rule = self._original_rule
    506         partfmt = '{name}={vals}'
    507         for name, key in [('BYSETPOS', 'bysetpos'),
    508          ('BYMONTH', 'bymonth'),
    509          ('BYMONTHDAY', 'bymonthday'),
    510          ('BYYEARDAY', 'byyearday'),
    511          ('BYWEEKNO', 'byweekno'),
    512          ('BYDAY', 'byweekday'),
    513          ('BYHOUR', 'byhour'),
    514          ('BYMINUTE', 'byminute'),
    515          ('BYSECOND', 'bysecond'),
    516          ('BYEASTER', 'byeaster')]:
    517             value = original_rule.get(key)
    518             if value:
    519                 parts.append(partfmt.format(name=name, vals=','.join((str(v) for v in value))))
    520 
    521         output.append(';'.join(parts))
    522         return '\n'.join(output)
    523 
    524     def replace(self, **kwargs):
    525         new_kwargs = {'interval': self._interval,
    526          'count': self._count,
    527          'dtstart': self._dtstart,
    528          'freq': self._freq,
    529          'until': self._until,
    530          'wkst': self._wkst,
    531          'cache': False if self._cache is None else True}
    532         new_kwargs.update(self._original_rule)
    533         new_kwargs.update(kwargs)
    534         return rrule(**new_kwargs)
    535 
    536     def _iter(self):
    537         year, month, day, hour, minute, second, weekday, yearday, _ = self._dtstart.timetuple()
    538         freq = self._freq
    539         interval = self._interval
    540         wkst = self._wkst
    541         until = self._until
    542         bymonth = self._bymonth
    543         byweekno = self._byweekno
    544         byyearday = self._byyearday
    545         byweekday = self._byweekday
    546         byeaster = self._byeaster
    547         bymonthday = self._bymonthday
    548         bynmonthday = self._bynmonthday
    549         bysetpos = self._bysetpos
    550         byhour = self._byhour
    551         byminute = self._byminute
    552         bysecond = self._bysecond
    553         ii = _iterinfo(self)
    554         ii.rebuild(year, month)
    555         getdayset = {YEARLY: ii.ydayset,
    556          MONTHLY: ii.mdayset,
    557          WEEKLY: ii.wdayset,
    558          DAILY: ii.ddayset,
    559          HOURLY: ii.ddayset,
    560          MINUTELY: ii.ddayset,
    561          SECONDLY: ii.ddayset}[freq]
    562         if freq < HOURLY:
    563             timeset = self._timeset
    564         else:
    565             gettimeset = {HOURLY: ii.htimeset,
    566              MINUTELY: ii.mtimeset,
    567              SECONDLY: ii.stimeset}[freq]
    568             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:
    569                 timeset = ()
    570             else:
    571                 timeset = gettimeset(hour, minute, second)
    572         total = 0
    573         count = self._count
    574         while True:
    575             dayset, start, end = getdayset(year, month, day)
    576             filtered = False
    577             for i in dayset[start:end]:
    578                 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):
    579                     dayset[i] = None
    580                     filtered = True
    581 
    582             if bysetpos and timeset:
    583                 poslist = []
    584                 for pos in bysetpos:
    585                     if pos < 0:
    586                         daypos, timepos = divmod(pos, len(timeset))
    587                     else:
    588                         daypos, timepos = divmod(pos - 1, len(timeset))
    589                     try:
    590                         i = [ x for x in dayset[start:end] if x is not None ][daypos]
    591                         time = timeset[timepos]
    592                     except IndexError:
    593                         pass
    594                     else:
    595                         date = datetime.date.fromordinal(ii.yearordinal + i)
    596                         res = datetime.datetime.combine(date, time)
    597                         if res not in poslist:
    598                             poslist.append(res)
    599 
    600                 poslist.sort()
    601                 for res in poslist:
    602                     if until and res > until:
    603                         self._len = total
    604                         return
    605                     if res >= self._dtstart:
    606                         total += 1
    607                         yield res
    608                         if count:
    609                             count -= 1
    610                             if not count:
    611                                 self._len = total
    612                                 return
    613 
    614             else:
    615                 for i in dayset[start:end]:
    616                     if i is not None:
    617                         date = datetime.date.fromordinal(ii.yearordinal + i)
    618                         for time in timeset:
    619                             res = datetime.datetime.combine(date, time)
    620                             if until and res > until:
    621                                 self._len = total
    622                                 return
    623                             if res >= self._dtstart:
    624                                 total += 1
    625                                 yield res
    626                                 if count:
    627                                     count -= 1
    628                                     if not count:
    629                                         self._len = total
    630                                         return
    631 
    632             fixday = False
    633             if freq == YEARLY:
    634                 year += interval
    635                 if year > datetime.MAXYEAR:
    636                     self._len = total
    637                     return
    638                 ii.rebuild(year, month)
    639             elif freq == MONTHLY:
    640                 month += interval
    641                 if month > 12:
    642                     div, mod = divmod(month, 12)
    643                     month = mod
    644                     year += div
    645                     if month == 0:
    646                         month = 12
    647                         year -= 1
    648                     if year > datetime.MAXYEAR:
    649                         self._len = total
    650                         return
    651                 ii.rebuild(year, month)
    652             elif freq == WEEKLY:
    653                 if wkst > weekday:
    654                     day += -(weekday + 1 + (6 - wkst)) + self._interval * 7
    655                 else:
    656                     day += -(weekday - wkst) + self._interval * 7
    657                 weekday = wkst
    658                 fixday = True
    659             elif freq == DAILY:
    660                 day += interval
    661                 fixday = True
    662             elif freq == HOURLY:
    663                 if filtered:
    664                     hour += (23 - hour) // interval * interval
    665                 if byhour:
    666                     ndays, hour = self.__mod_distance(value=hour, byxxx=self._byhour, base=24)
    667                 else:
    668                     ndays, hour = divmod(hour + interval, 24)
    669                 if ndays:
    670                     day += ndays
    671                     fixday = True
    672                 timeset = gettimeset(hour, minute, second)
    673             elif freq == MINUTELY:
    674                 if filtered:
    675                     minute += (1439 - (hour * 60 + minute)) // interval * interval
    676                 valid = False
    677                 rep_rate = 1440
    678                 for j in range(rep_rate // gcd(interval, rep_rate)):
    679                     if byminute:
    680                         nhours, minute = self.__mod_distance(value=minute, byxxx=self._byminute, base=60)
    681                     else:
    682                         nhours, minute = divmod(minute + interval, 60)
    683                     div, hour = divmod(hour + nhours, 24)
    684                     if div:
    685                         day += div
    686                         fixday = True
    687                         filtered = False
    688                     if not byhour or hour in byhour:
    689                         valid = True
    690                         break
    691 
    692                 if not valid:
    693                     raise ValueError('Invalid combination of interval and ' + 'byhour resulting in empty rule.')
    694                 timeset = gettimeset(hour, minute, second)
    695             elif freq == SECONDLY:
    696                 if filtered:
    697                     second += (86399 - (hour * 3600 + minute * 60 + second)) // interval * interval
    698                 rep_rate = 86400
    699                 valid = False
    700                 for j in range(0, rep_rate // gcd(interval, rep_rate)):
    701                     if bysecond:
    702                         nminutes, second = self.__mod_distance(value=second, byxxx=self._bysecond, base=60)
    703                     else:
    704                         nminutes, second = divmod(second + interval, 60)
    705                     div, minute = divmod(minute + nminutes, 60)
    706                     if div:
    707                         hour += div
    708                         div, hour = divmod(hour, 24)
    709                         if div:
    710                             day += div
    711                             fixday = True
    712                     if (not byhour or hour in byhour) and (not byminute or minute in byminute) and (not bysecond or second in bysecond):
    713                         valid = True
    714                         break
    715 
    716                 if not valid:
    717                     raise ValueError('Invalid combination of interval, ' + 'byhour and byminute resulting in empty' + ' rule.')
    718                 timeset = gettimeset(hour, minute, second)
    719             if fixday and day > 28:
    720                 daysinmonth = calendar.monthrange(year, month)[1]
    721                 if day > daysinmonth:
    722                     while day > daysinmonth:
    723                         day -= daysinmonth
    724                         month += 1
    725                         if month == 13:
    726                             month = 1
    727                             year += 1
    728                             if year > datetime.MAXYEAR:
    729                                 self._len = total
    730                                 return
    731                         daysinmonth = calendar.monthrange(year, month)[1]
    732 
    733                     ii.rebuild(year, month)
    734 
    735     def __construct_byset(self, start, byxxx, base):
    736         cset = set()
    737         if isinstance(byxxx, integer_types):
    738             byxxx = (byxxx,)
    739         for num in byxxx:
    740             i_gcd = gcd(self._interval, base)
    741             if i_gcd == 1 or divmod(num - start, i_gcd)[1] == 0:
    742                 cset.add(num)
    743 
    744         if len(cset) == 0:
    745             raise ValueError('Invalid rrule byxxx generates an empty set.')
    746         return cset
    747 
    748     def __mod_distance(self, value, byxxx, base):
    749         accumulator = 0
    750         for ii in range(1, base + 1):
    751             div, value = divmod(value + self._interval, base)
    752             accumulator += div
    753             if value in byxxx:
    754                 return (accumulator, value)
    755 
    756 
    757 class _iterinfo(object):
    758     __slots__ = ['rrule',
    759      'lastyear',
    760      'lastmonth',
    761      'yearlen',
    762      'nextyearlen',
    763      'yearordinal',
    764      'yearweekday',
    765      'mmask',
    766      'mrange',
    767      'mdaymask',
    768      'nmdaymask',
    769      'wdaymask',
    770      'wnomask',
    771      'nwdaymask',
    772      'eastermask']
    773 
    774     def __init__(self, rrule):
    775         for attr in self.__slots__:
    776             setattr(self, attr, None)
    777 
    778         self.rrule = rrule
    779 
    780     def rebuild(self, year, month):
    781         rr = self.rrule
    782         if year != self.lastyear:
    783             self.yearlen = 365 + calendar.isleap(year)
    784             self.nextyearlen = 365 + calendar.isleap(year + 1)
    785             firstyday = datetime.date(year, 1, 1)
    786             self.yearordinal = firstyday.toordinal()
    787             self.yearweekday = firstyday.weekday()
    788             wday = datetime.date(year, 1, 1).weekday()
    789             if self.yearlen == 365:
    790                 self.mmask = M365MASK
    791                 self.mdaymask = MDAY365MASK
    792                 self.nmdaymask = NMDAY365MASK
    793                 self.wdaymask = WDAYMASK[wday:]
    794                 self.mrange = M365RANGE
    795             else:
    796                 self.mmask = M366MASK
    797                 self.mdaymask = MDAY366MASK
    798                 self.nmdaymask = NMDAY366MASK
    799                 self.wdaymask = WDAYMASK[wday:]
    800                 self.mrange = M366RANGE
    801             if not rr._byweekno:
    802                 self.wnomask = None
    803             else:
    804                 self.wnomask = [0] * (self.yearlen + 7)
    805                 no1wkst = firstwkst = (7 - self.yearweekday + rr._wkst) % 7
    806                 if no1wkst >= 4:
    807                     no1wkst = 0
    808                     wyearlen = self.yearlen + (self.yearweekday - rr._wkst) % 7
    809                 else:
    810                     wyearlen = self.yearlen - no1wkst
    811                 div, mod = divmod(wyearlen, 7)
    812                 numweeks = div + mod // 4
    813                 for n in rr._byweekno:
    814                     if n < 0:
    815                         n += numweeks + 1
    816                     if not 0 < n <= numweeks:
    817                         continue
    818                     if n > 1:
    819                         i = no1wkst + (n - 1) * 7
    820                         if no1wkst != firstwkst:
    821                             i -= 7 - firstwkst
    822                     else:
    823                         i = no1wkst
    824                     for j in range(7):
    825                         self.wnomask[i] = 1
    826                         i += 1
    827                         if self.wdaymask[i] == rr._wkst:
    828                             break
    829 
    830                 if 1 in rr._byweekno:
    831                     i = no1wkst + numweeks * 7
    832                     if no1wkst != firstwkst:
    833                         i -= 7 - firstwkst
    834                     if i < self.yearlen:
    835                         for j in range(7):
    836                             self.wnomask[i] = 1
    837                             i += 1
    838                             if self.wdaymask[i] == rr._wkst:
    839                                 break
    840 
    841                 if no1wkst:
    842                     if -1 not in rr._byweekno:
    843                         lyearweekday = datetime.date(year - 1, 1, 1).weekday()
    844                         lno1wkst = (7 - lyearweekday + rr._wkst) % 7
    845                         lyearlen = 365 + calendar.isleap(year - 1)
    846                         if lno1wkst >= 4:
    847                             lno1wkst = 0
    848                             lnumweeks = 52 + (lyearlen + (lyearweekday - rr._wkst) % 7) % 7 // 4
    849                         else:
    850                             lnumweeks = 52 + (self.yearlen - no1wkst) % 7 // 4
    851                     else:
    852                         lnumweeks = -1
    853                     if lnumweeks in rr._byweekno:
    854                         for i in range(no1wkst):
    855                             self.wnomask[i] = 1
    856 
    857         if rr._bynweekday and (month != self.lastmonth or year != self.lastyear):
    858             ranges = []
    859             if rr._freq == YEARLY:
    860                 if rr._bymonth:
    861                     for month in rr._bymonth:
    862                         ranges.append(self.mrange[month - 1:month + 1])
    863 
    864                 else:
    865                     ranges = [(0, self.yearlen)]
    866             elif rr._freq == MONTHLY:
    867                 ranges = [self.mrange[month - 1:month + 1]]
    868             if ranges:
    869                 self.nwdaymask = [0] * self.yearlen
    870                 for first, last in ranges:
    871                     last -= 1
    872                     for wday, n in rr._bynweekday:
    873                         if n < 0:
    874                             i = last + (n + 1) * 7
    875                             i -= (self.wdaymask[i] - wday) % 7
    876                         else:
    877                             i = first + (n - 1) * 7
    878                             i += (7 - self.wdaymask[i] + wday) % 7
    879                         if first <= i <= last:
    880                             self.nwdaymask[i] = 1
    881 
    882         if rr._byeaster:
    883             self.eastermask = [0] * (self.yearlen + 7)
    884             eyday = easter.easter(year).toordinal() - self.yearordinal
    885             for offset in rr._byeaster:
    886                 self.eastermask[eyday + offset] = 1
    887 
    888         self.lastyear = year
    889         self.lastmonth = month
    890 
    891     def ydayset(self, year, month, day):
    892         return (list(range(self.yearlen)), 0, self.yearlen)
    893 
    894     def mdayset(self, year, month, day):
    895         dset = [None] * self.yearlen
    896         start, end = self.mrange[month - 1:month + 1]
    897         for i in range(start, end):
    898             dset[i] = i
    899 
    900         return (dset, start, end)
    901 
    902     def wdayset(self, year, month, day):
    903         dset = [None] * (self.yearlen + 7)
    904         i = datetime.date(year, month, day).toordinal() - self.yearordinal
    905         start = i
    906         for j in range(7):
    907             dset[i] = i
    908             i += 1
    909             if self.wdaymask[i] == self.rrule._wkst:
    910                 break
    911 
    912         return (dset, start, i)
    913 
    914     def ddayset(self, year, month, day):
    915         dset = [None] * self.yearlen
    916         i = datetime.date(year, month, day).toordinal() - self.yearordinal
    917         dset[i] = i
    918         return (dset, i, i + 1)
    919 
    920     def htimeset(self, hour, minute, second):
    921         tset = []
    922         rr = self.rrule
    923         for minute in rr._byminute:
    924             for second in rr._bysecond:
    925                 tset.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo))
    926 
    927         tset.sort()
    928         return tset
    929 
    930     def mtimeset(self, hour, minute, second):
    931         tset = []
    932         rr = self.rrule
    933         for second in rr._bysecond:
    934             tset.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo))
    935 
    936         tset.sort()
    937         return tset
    938 
    939     def stimeset(self, hour, minute, second):
    940         return (datetime.time(hour, minute, second, tzinfo=self.rrule._tzinfo),)
    941 
    942 
    943 class rruleset(rrulebase):
    944 
    945     class _genitem(object):
    946 
    947         def __init__(self, genlist, gen):
    948             try:
    949                 self.dt = advance_iterator(gen)
    950                 genlist.append(self)
    951             except StopIteration:
    952                 pass
    953 
    954             self.genlist = genlist
    955             self.gen = gen
    956 
    957         def __next__(self):
    958             try:
    959                 self.dt = advance_iterator(self.gen)
    960             except StopIteration:
    961                 if self.genlist[0] is self:
    962                     heapq.heappop(self.genlist)
    963                 else:
    964                     self.genlist.remove(self)
    965                     heapq.heapify(self.genlist)
    966 
    967         next = __next__
    968 
    969         def __lt__(self, other):
    970             return self.dt < other.dt
    971 
    972         def __gt__(self, other):
    973             return self.dt > other.dt
    974 
    975         def __eq__(self, other):
    976             return self.dt == other.dt
    977 
    978         def __ne__(self, other):
    979             return self.dt != other.dt
    980 
    981     def __init__(self, cache = False):
    982         super(rruleset, self).__init__(cache)
    983         self._rrule = []
    984         self._rdate = []
    985         self._exrule = []
    986         self._exdate = []
    987 
    988     @_invalidates_cache
    989     def rrule(self, rrule):
    990         self._rrule.append(rrule)
    991 
    992     @_invalidates_cache
    993     def rdate(self, rdate):
    994         self._rdate.append(rdate)
    995 
    996     @_invalidates_cache
    997     def exrule(self, exrule):
    998         self._exrule.append(exrule)
    999 
   1000     @_invalidates_cache
   1001     def exdate(self, exdate):
   1002         self._exdate.append(exdate)
   1003 
   1004     def _iter(self):
   1005         rlist = []
   1006         self._rdate.sort()
   1007         self._genitem(rlist, iter(self._rdate))
   1008         for gen in [ iter(x) for x in self._rrule ]:
   1009             self._genitem(rlist, gen)
   1010 
   1011         exlist = []
   1012         self._exdate.sort()
   1013         self._genitem(exlist, iter(self._exdate))
   1014         for gen in [ iter(x) for x in self._exrule ]:
   1015             self._genitem(exlist, gen)
   1016 
   1017         lastdt = None
   1018         total = 0
   1019         heapq.heapify(rlist)
   1020         heapq.heapify(exlist)
   1021         while rlist:
   1022             ritem = rlist[0]
   1023             if not lastdt or lastdt != ritem.dt:
   1024                 while exlist and exlist[0] < ritem:
   1025                     exitem = exlist[0]
   1026                     advance_iterator(exitem)
   1027                     if exlist and exlist[0] is exitem:
   1028                         heapq.heapreplace(exlist, exitem)
   1029 
   1030                 if not exlist or ritem != exlist[0]:
   1031                     total += 1
   1032                     yield ritem.dt
   1033                 lastdt = ritem.dt
   1034             advance_iterator(ritem)
   1035             if rlist and rlist[0] is ritem:
   1036                 heapq.heapreplace(rlist, ritem)
   1037 
   1038         self._len = total
   1039 
   1040 
   1041 class _rrulestr(object):
   1042     _freq_map = {'YEARLY': YEARLY,
   1043      'MONTHLY': MONTHLY,
   1044      'WEEKLY': WEEKLY,
   1045      'DAILY': DAILY,
   1046      'HOURLY': HOURLY,
   1047      'MINUTELY': MINUTELY,
   1048      'SECONDLY': SECONDLY}
   1049     _weekday_map = {'MO': 0,
   1050      'TU': 1,
   1051      'WE': 2,
   1052      'TH': 3,
   1053      'FR': 4,
   1054      'SA': 5,
   1055      'SU': 6}
   1056 
   1057     def _handle_int(self, rrkwargs, name, value, **kwargs):
   1058         rrkwargs[name.lower()] = int(value)
   1059 
   1060     def _handle_int_list(self, rrkwargs, name, value, **kwargs):
   1061         rrkwargs[name.lower()] = [ int(x) for x in value.split(',') ]
   1062 
   1063     _handle_INTERVAL = _handle_int
   1064     _handle_COUNT = _handle_int
   1065     _handle_BYSETPOS = _handle_int_list
   1066     _handle_BYMONTH = _handle_int_list
   1067     _handle_BYMONTHDAY = _handle_int_list
   1068     _handle_BYYEARDAY = _handle_int_list
   1069     _handle_BYEASTER = _handle_int_list
   1070     _handle_BYWEEKNO = _handle_int_list
   1071     _handle_BYHOUR = _handle_int_list
   1072     _handle_BYMINUTE = _handle_int_list
   1073     _handle_BYSECOND = _handle_int_list
   1074 
   1075     def _handle_FREQ(self, rrkwargs, name, value, **kwargs):
   1076         rrkwargs['freq'] = self._freq_map[value]
   1077 
   1078     def _handle_UNTIL(self, rrkwargs, name, value, **kwargs):
   1079         global parser
   1080         if not parser:
   1081             from dateutil import parser
   1082         try:
   1083             rrkwargs['until'] = parser.parse(value, ignoretz=kwargs.get('ignoretz'), tzinfos=kwargs.get('tzinfos'))
   1084         except ValueError:
   1085             raise ValueError('invalid until date')
   1086 
   1087     def _handle_WKST(self, rrkwargs, name, value, **kwargs):
   1088         rrkwargs['wkst'] = self._weekday_map[value]
   1089 
   1090     def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwargs):
   1091         l = []
   1092         for wday in value.split(','):
   1093             if '(' in wday:
   1094                 splt = wday.split('(')
   1095                 w = splt[0]
   1096                 n = int(splt[1][:-1])
   1097             elif len(wday):
   1098                 for i in range(len(wday)):
   1099                     if wday[i] not in '+-0123456789':
   1100                         break
   1101 
   1102                 n = wday[:i] or None
   1103                 w = wday[i:]
   1104                 if n:
   1105                     n = int(n)
   1106             else:
   1107                 raise ValueError('Invalid (empty) BYDAY specification.')
   1108             l.append(weekdays[self._weekday_map[w]](n))
   1109 
   1110         rrkwargs['byweekday'] = l
   1111 
   1112     _handle_BYDAY = _handle_BYWEEKDAY
   1113 
   1114     def _parse_rfc_rrule(self, line, dtstart = None, cache = False, ignoretz = False, tzinfos = None):
   1115         if line.find(':') != -1:
   1116             name, value = line.split(':')
   1117             if name != 'RRULE':
   1118                 raise ValueError('unknown parameter name')
   1119         else:
   1120             value = line
   1121         rrkwargs = {}
   1122         for pair in value.split(';'):
   1123             name, value = pair.split('=')
   1124             name = name.upper()
   1125             value = value.upper()
   1126             try:
   1127                 getattr(self, '_handle_' + name)(rrkwargs, name, value, ignoretz=ignoretz, tzinfos=tzinfos)
   1128             except AttributeError:
   1129                 raise ValueError("unknown parameter '%s'" % name)
   1130             except (KeyError, ValueError):
   1131                 raise ValueError("invalid '%s': %s" % (name, value))
   1132 
   1133         return rrule(dtstart=dtstart, cache=cache, **rrkwargs)
   1134 
   1135     def _parse_rfc(self, s, dtstart = None, cache = False, unfold = False, forceset = False, compatible = False, ignoretz = False, tzinfos = None):
   1136         global parser
   1137         if compatible:
   1138             forceset = True
   1139             unfold = True
   1140         s = s.upper()
   1141         if not s.strip():
   1142             raise ValueError('empty string')
   1143         if unfold:
   1144             lines = s.splitlines()
   1145             i = 0
   1146             while i < len(lines):
   1147                 line = lines[i].rstrip()
   1148                 if not line:
   1149                     del lines[i]
   1150                 elif i > 0 and line[0] == ' ':
   1151                     lines[i - 1] += line[1:]
   1152                     del lines[i]
   1153                 else:
   1154                     i += 1
   1155 
   1156         else:
   1157             lines = s.split()
   1158         if not forceset and len(lines) == 1 and (s.find(':') == -1 or s.startswith('RRULE:')):
   1159             return self._parse_rfc_rrule(lines[0], cache=cache, dtstart=dtstart, ignoretz=ignoretz, tzinfos=tzinfos)
   1160         rrulevals = []
   1161         rdatevals = []
   1162         exrulevals = []
   1163         exdatevals = []
   1164         for line in lines:
   1165             if not line:
   1166                 continue
   1167             if line.find(':') == -1:
   1168                 name = 'RRULE'
   1169                 value = line
   1170             else:
   1171                 name, value = line.split(':', 1)
   1172             parms = name.split(';')
   1173             if not parms:
   1174                 raise ValueError('empty property name')
   1175             name = parms[0]
   1176             parms = parms[1:]
   1177             if name == 'RRULE':
   1178                 for parm in parms:
   1179                     raise ValueError('unsupported RRULE parm: ' + parm)
   1180 
   1181                 rrulevals.append(value)
   1182             elif name == 'RDATE':
   1183                 for parm in parms:
   1184                     if parm != 'VALUE=DATE-TIME':
   1185                         raise ValueError('unsupported RDATE parm: ' + parm)
   1186 
   1187                 rdatevals.append(value)
   1188             elif name == 'EXRULE':
   1189                 for parm in parms:
   1190                     raise ValueError('unsupported EXRULE parm: ' + parm)
   1191 
   1192                 exrulevals.append(value)
   1193             elif name == 'EXDATE':
   1194                 for parm in parms:
   1195                     if parm != 'VALUE=DATE-TIME':
   1196                         raise ValueError('unsupported RDATE parm: ' + parm)
   1197 
   1198                 exdatevals.append(value)
   1199             elif name == 'DTSTART':
   1200                 for parm in parms:
   1201                     raise ValueError('unsupported DTSTART parm: ' + parm)
   1202 
   1203                 if not parser:
   1204                     from dateutil import parser
   1205                 dtstart = parser.parse(value, ignoretz=ignoretz, tzinfos=tzinfos)
   1206             else:
   1207                 raise ValueError('unsupported property: ' + name)
   1208 
   1209         if forceset or len(rrulevals) > 1 or rdatevals or exrulevals or exdatevals:
   1210             if not parser and (rdatevals or exdatevals):
   1211                 from dateutil import parser
   1212             rset = rruleset(cache=cache)
   1213             for value in rrulevals:
   1214                 rset.rrule(self._parse_rfc_rrule(value, dtstart=dtstart, ignoretz=ignoretz, tzinfos=tzinfos))
   1215 
   1216             for value in rdatevals:
   1217                 for datestr in value.split(','):
   1218                     rset.rdate(parser.parse(datestr, ignoretz=ignoretz, tzinfos=tzinfos))
   1219 
   1220             for value in exrulevals:
   1221                 rset.exrule(self._parse_rfc_rrule(value, dtstart=dtstart, ignoretz=ignoretz, tzinfos=tzinfos))
   1222 
   1223             for value in exdatevals:
   1224                 for datestr in value.split(','):
   1225                     rset.exdate(parser.parse(datestr, ignoretz=ignoretz, tzinfos=tzinfos))
   1226 
   1227             if compatible and dtstart:
   1228                 rset.rdate(dtstart)
   1229             return rset
   1230         else:
   1231             return self._parse_rfc_rrule(rrulevals[0], dtstart=dtstart, cache=cache, ignoretz=ignoretz, tzinfos=tzinfos)
   1232 
   1233     def __call__(self, s, **kwargs):
   1234         return self._parse_rfc(s, **kwargs)
   1235 
   1236 
   1237 rrulestr = _rrulestr()