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()