Path: blob/master/ invest-robot-contest_TinkoffBotTwitch-main/venv/lib/python3.8/site-packages/dateutil/rrule.py
7763 views
# -*- coding: utf-8 -*-1"""2The rrule module offers a small, complete, and very fast, implementation of3the recurrence rules documented in the4`iCalendar RFC <https://tools.ietf.org/html/rfc5545>`_,5including support for caching of results.6"""7import calendar8import datetime9import heapq10import itertools11import re12import sys13from functools import wraps14# For warning about deprecation of until and count15from warnings import warn1617from six import advance_iterator, integer_types1819from six.moves import _thread, range2021from ._common import weekday as weekdaybase2223try:24from math import gcd25except ImportError:26from fractions import gcd2728__all__ = ["rrule", "rruleset", "rrulestr",29"YEARLY", "MONTHLY", "WEEKLY", "DAILY",30"HOURLY", "MINUTELY", "SECONDLY",31"MO", "TU", "WE", "TH", "FR", "SA", "SU"]3233# Every mask is 7 days longer to handle cross-year weekly periods.34M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30 +35[7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7)36M365MASK = list(M366MASK)37M29, M30, M31 = list(range(1, 30)), list(range(1, 31)), list(range(1, 32))38MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])39MDAY365MASK = list(MDAY366MASK)40M29, M30, M31 = list(range(-29, 0)), list(range(-30, 0)), list(range(-31, 0))41NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])42NMDAY365MASK = list(NMDAY366MASK)43M366RANGE = (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366)44M365RANGE = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)45WDAYMASK = [0, 1, 2, 3, 4, 5, 6]*5546del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31]47MDAY365MASK = tuple(MDAY365MASK)48M365MASK = tuple(M365MASK)4950FREQNAMES = ['YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', 'HOURLY', 'MINUTELY', 'SECONDLY']5152(YEARLY,53MONTHLY,54WEEKLY,55DAILY,56HOURLY,57MINUTELY,58SECONDLY) = list(range(7))5960# Imported on demand.61easter = None62parser = None636465class weekday(weekdaybase):66"""67This version of weekday does not allow n = 0.68"""69def __init__(self, wkday, n=None):70if n == 0:71raise ValueError("Can't create weekday with n==0")7273super(weekday, self).__init__(wkday, n)747576MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))777879def _invalidates_cache(f):80"""81Decorator for rruleset methods which may invalidate the82cached length.83"""84@wraps(f)85def inner_func(self, *args, **kwargs):86rv = f(self, *args, **kwargs)87self._invalidate_cache()88return rv8990return inner_func919293class rrulebase(object):94def __init__(self, cache=False):95if cache:96self._cache = []97self._cache_lock = _thread.allocate_lock()98self._invalidate_cache()99else:100self._cache = None101self._cache_complete = False102self._len = None103104def __iter__(self):105if self._cache_complete:106return iter(self._cache)107elif self._cache is None:108return self._iter()109else:110return self._iter_cached()111112def _invalidate_cache(self):113if self._cache is not None:114self._cache = []115self._cache_complete = False116self._cache_gen = self._iter()117118if self._cache_lock.locked():119self._cache_lock.release()120121self._len = None122123def _iter_cached(self):124i = 0125gen = self._cache_gen126cache = self._cache127acquire = self._cache_lock.acquire128release = self._cache_lock.release129while gen:130if i == len(cache):131acquire()132if self._cache_complete:133break134try:135for j in range(10):136cache.append(advance_iterator(gen))137except StopIteration:138self._cache_gen = gen = None139self._cache_complete = True140break141release()142yield cache[i]143i += 1144while i < self._len:145yield cache[i]146i += 1147148def __getitem__(self, item):149if self._cache_complete:150return self._cache[item]151elif isinstance(item, slice):152if item.step and item.step < 0:153return list(iter(self))[item]154else:155return list(itertools.islice(self,156item.start or 0,157item.stop or sys.maxsize,158item.step or 1))159elif item >= 0:160gen = iter(self)161try:162for i in range(item+1):163res = advance_iterator(gen)164except StopIteration:165raise IndexError166return res167else:168return list(iter(self))[item]169170def __contains__(self, item):171if self._cache_complete:172return item in self._cache173else:174for i in self:175if i == item:176return True177elif i > item:178return False179return False180181# __len__() introduces a large performance penalty.182def count(self):183""" Returns the number of recurrences in this set. It will have go184trough the whole recurrence, if this hasn't been done before. """185if self._len is None:186for x in self:187pass188return self._len189190def before(self, dt, inc=False):191""" Returns the last recurrence before the given datetime instance. The192inc keyword defines what happens if dt is an occurrence. With193inc=True, if dt itself is an occurrence, it will be returned. """194if self._cache_complete:195gen = self._cache196else:197gen = self198last = None199if inc:200for i in gen:201if i > dt:202break203last = i204else:205for i in gen:206if i >= dt:207break208last = i209return last210211def after(self, dt, inc=False):212""" Returns the first recurrence after the given datetime instance. The213inc keyword defines what happens if dt is an occurrence. With214inc=True, if dt itself is an occurrence, it will be returned. """215if self._cache_complete:216gen = self._cache217else:218gen = self219if inc:220for i in gen:221if i >= dt:222return i223else:224for i in gen:225if i > dt:226return i227return None228229def xafter(self, dt, count=None, inc=False):230"""231Generator which yields up to `count` recurrences after the given232datetime instance, equivalent to `after`.233234:param dt:235The datetime at which to start generating recurrences.236237:param count:238The maximum number of recurrences to generate. If `None` (default),239dates are generated until the recurrence rule is exhausted.240241:param inc:242If `dt` is an instance of the rule and `inc` is `True`, it is243included in the output.244245:yields: Yields a sequence of `datetime` objects.246"""247248if self._cache_complete:249gen = self._cache250else:251gen = self252253# Select the comparison function254if inc:255comp = lambda dc, dtc: dc >= dtc256else:257comp = lambda dc, dtc: dc > dtc258259# Generate dates260n = 0261for d in gen:262if comp(d, dt):263if count is not None:264n += 1265if n > count:266break267268yield d269270def between(self, after, before, inc=False, count=1):271""" Returns all the occurrences of the rrule between after and before.272The inc keyword defines what happens if after and/or before are273themselves occurrences. With inc=True, they will be included in the274list, if they are found in the recurrence set. """275if self._cache_complete:276gen = self._cache277else:278gen = self279started = False280l = []281if inc:282for i in gen:283if i > before:284break285elif not started:286if i >= after:287started = True288l.append(i)289else:290l.append(i)291else:292for i in gen:293if i >= before:294break295elif not started:296if i > after:297started = True298l.append(i)299else:300l.append(i)301return l302303304class rrule(rrulebase):305"""306That's the base of the rrule operation. It accepts all the keywords307defined in the RFC as its constructor parameters (except byday,308which was renamed to byweekday) and more. The constructor prototype is::309310rrule(freq)311312Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,313or SECONDLY.314315.. note::316Per RFC section 3.3.10, recurrence instances falling on invalid dates317and times are ignored rather than coerced:318319Recurrence rules may generate recurrence instances with an invalid320date (e.g., February 30) or nonexistent local time (e.g., 1:30 AM321on a day where the local time is moved forward by an hour at 1:00322AM). Such recurrence instances MUST be ignored and MUST NOT be323counted as part of the recurrence set.324325This can lead to possibly surprising behavior when, for example, the326start date occurs at the end of the month:327328>>> from dateutil.rrule import rrule, MONTHLY329>>> from datetime import datetime330>>> start_date = datetime(2014, 12, 31)331>>> list(rrule(freq=MONTHLY, count=4, dtstart=start_date))332... # doctest: +NORMALIZE_WHITESPACE333[datetime.datetime(2014, 12, 31, 0, 0),334datetime.datetime(2015, 1, 31, 0, 0),335datetime.datetime(2015, 3, 31, 0, 0),336datetime.datetime(2015, 5, 31, 0, 0)]337338Additionally, it supports the following keyword arguments:339340:param dtstart:341The recurrence start. Besides being the base for the recurrence,342missing parameters in the final recurrence instances will also be343extracted from this date. If not given, datetime.now() will be used344instead.345:param interval:346The interval between each freq iteration. For example, when using347YEARLY, an interval of 2 means once every two years, but with HOURLY,348it means once every two hours. The default interval is 1.349:param wkst:350The week start day. Must be one of the MO, TU, WE constants, or an351integer, specifying the first day of the week. This will affect352recurrences based on weekly periods. The default week start is got353from calendar.firstweekday(), and may be modified by354calendar.setfirstweekday().355:param count:356If given, this determines how many occurrences will be generated.357358.. note::359As of version 2.5.0, the use of the keyword ``until`` in conjunction360with ``count`` is deprecated, to make sure ``dateutil`` is fully361compliant with `RFC-5545 Sec. 3.3.10 <https://tools.ietf.org/362html/rfc5545#section-3.3.10>`_. Therefore, ``until`` and ``count``363**must not** occur in the same call to ``rrule``.364:param until:365If given, this must be a datetime instance specifying the upper-bound366limit of the recurrence. The last recurrence in the rule is the greatest367datetime that is less than or equal to the value specified in the368``until`` parameter.369370.. note::371As of version 2.5.0, the use of the keyword ``until`` in conjunction372with ``count`` is deprecated, to make sure ``dateutil`` is fully373compliant with `RFC-5545 Sec. 3.3.10 <https://tools.ietf.org/374html/rfc5545#section-3.3.10>`_. Therefore, ``until`` and ``count``375**must not** occur in the same call to ``rrule``.376:param bysetpos:377If given, it must be either an integer, or a sequence of integers,378positive or negative. Each given integer will specify an occurrence379number, corresponding to the nth occurrence of the rule inside the380frequency period. For example, a bysetpos of -1 if combined with a381MONTHLY frequency, and a byweekday of (MO, TU, WE, TH, FR), will382result in the last work day of every month.383:param bymonth:384If given, it must be either an integer, or a sequence of integers,385meaning the months to apply the recurrence to.386:param bymonthday:387If given, it must be either an integer, or a sequence of integers,388meaning the month days to apply the recurrence to.389:param byyearday:390If given, it must be either an integer, or a sequence of integers,391meaning the year days to apply the recurrence to.392:param byeaster:393If given, it must be either an integer, or a sequence of integers,394positive or negative. Each integer will define an offset from the395Easter Sunday. Passing the offset 0 to byeaster will yield the Easter396Sunday itself. This is an extension to the RFC specification.397:param byweekno:398If given, it must be either an integer, or a sequence of integers,399meaning the week numbers to apply the recurrence to. Week numbers400have the meaning described in ISO8601, that is, the first week of401the year is that containing at least four days of the new year.402:param byweekday:403If given, it must be either an integer (0 == MO), a sequence of404integers, one of the weekday constants (MO, TU, etc), or a sequence405of these constants. When given, these variables will define the406weekdays where the recurrence will be applied. It's also possible to407use an argument n for the weekday instances, which will mean the nth408occurrence of this weekday in the period. For example, with MONTHLY,409or with YEARLY and BYMONTH, using FR(+1) in byweekday will specify the410first friday of the month where the recurrence happens. Notice that in411the RFC documentation, this is specified as BYDAY, but was renamed to412avoid the ambiguity of that keyword.413:param byhour:414If given, it must be either an integer, or a sequence of integers,415meaning the hours to apply the recurrence to.416:param byminute:417If given, it must be either an integer, or a sequence of integers,418meaning the minutes to apply the recurrence to.419:param bysecond:420If given, it must be either an integer, or a sequence of integers,421meaning the seconds to apply the recurrence to.422:param cache:423If given, it must be a boolean value specifying to enable or disable424caching of results. If you will use the same rrule instance multiple425times, enabling caching will improve the performance considerably.426"""427def __init__(self, freq, dtstart=None,428interval=1, wkst=None, count=None, until=None, bysetpos=None,429bymonth=None, bymonthday=None, byyearday=None, byeaster=None,430byweekno=None, byweekday=None,431byhour=None, byminute=None, bysecond=None,432cache=False):433super(rrule, self).__init__(cache)434global easter435if not dtstart:436if until and until.tzinfo:437dtstart = datetime.datetime.now(tz=until.tzinfo).replace(microsecond=0)438else:439dtstart = datetime.datetime.now().replace(microsecond=0)440elif not isinstance(dtstart, datetime.datetime):441dtstart = datetime.datetime.fromordinal(dtstart.toordinal())442else:443dtstart = dtstart.replace(microsecond=0)444self._dtstart = dtstart445self._tzinfo = dtstart.tzinfo446self._freq = freq447self._interval = interval448self._count = count449450# Cache the original byxxx rules, if they are provided, as the _byxxx451# attributes do not necessarily map to the inputs, and this can be452# a problem in generating the strings. Only store things if they've453# been supplied (the string retrieval will just use .get())454self._original_rule = {}455456if until and not isinstance(until, datetime.datetime):457until = datetime.datetime.fromordinal(until.toordinal())458self._until = until459460if self._dtstart and self._until:461if (self._dtstart.tzinfo is not None) != (self._until.tzinfo is not None):462# According to RFC5545 Section 3.3.10:463# https://tools.ietf.org/html/rfc5545#section-3.3.10464#465# > If the "DTSTART" property is specified as a date with UTC466# > time or a date with local time and time zone reference,467# > then the UNTIL rule part MUST be specified as a date with468# > UTC time.469raise ValueError(470'RRULE UNTIL values must be specified in UTC when DTSTART '471'is timezone-aware'472)473474if count is not None and until:475warn("Using both 'count' and 'until' is inconsistent with RFC 5545"476" and has been deprecated in dateutil. Future versions will "477"raise an error.", DeprecationWarning)478479if wkst is None:480self._wkst = calendar.firstweekday()481elif isinstance(wkst, integer_types):482self._wkst = wkst483else:484self._wkst = wkst.weekday485486if bysetpos is None:487self._bysetpos = None488elif isinstance(bysetpos, integer_types):489if bysetpos == 0 or not (-366 <= bysetpos <= 366):490raise ValueError("bysetpos must be between 1 and 366, "491"or between -366 and -1")492self._bysetpos = (bysetpos,)493else:494self._bysetpos = tuple(bysetpos)495for pos in self._bysetpos:496if pos == 0 or not (-366 <= pos <= 366):497raise ValueError("bysetpos must be between 1 and 366, "498"or between -366 and -1")499500if self._bysetpos:501self._original_rule['bysetpos'] = self._bysetpos502503if (byweekno is None and byyearday is None and bymonthday is None and504byweekday is None and byeaster is None):505if freq == YEARLY:506if bymonth is None:507bymonth = dtstart.month508self._original_rule['bymonth'] = None509bymonthday = dtstart.day510self._original_rule['bymonthday'] = None511elif freq == MONTHLY:512bymonthday = dtstart.day513self._original_rule['bymonthday'] = None514elif freq == WEEKLY:515byweekday = dtstart.weekday()516self._original_rule['byweekday'] = None517518# bymonth519if bymonth is None:520self._bymonth = None521else:522if isinstance(bymonth, integer_types):523bymonth = (bymonth,)524525self._bymonth = tuple(sorted(set(bymonth)))526527if 'bymonth' not in self._original_rule:528self._original_rule['bymonth'] = self._bymonth529530# byyearday531if byyearday is None:532self._byyearday = None533else:534if isinstance(byyearday, integer_types):535byyearday = (byyearday,)536537self._byyearday = tuple(sorted(set(byyearday)))538self._original_rule['byyearday'] = self._byyearday539540# byeaster541if byeaster is not None:542if not easter:543from dateutil import easter544if isinstance(byeaster, integer_types):545self._byeaster = (byeaster,)546else:547self._byeaster = tuple(sorted(byeaster))548549self._original_rule['byeaster'] = self._byeaster550else:551self._byeaster = None552553# bymonthday554if bymonthday is None:555self._bymonthday = ()556self._bynmonthday = ()557else:558if isinstance(bymonthday, integer_types):559bymonthday = (bymonthday,)560561bymonthday = set(bymonthday) # Ensure it's unique562563self._bymonthday = tuple(sorted(x for x in bymonthday if x > 0))564self._bynmonthday = tuple(sorted(x for x in bymonthday if x < 0))565566# Storing positive numbers first, then negative numbers567if 'bymonthday' not in self._original_rule:568self._original_rule['bymonthday'] = tuple(569itertools.chain(self._bymonthday, self._bynmonthday))570571# byweekno572if byweekno is None:573self._byweekno = None574else:575if isinstance(byweekno, integer_types):576byweekno = (byweekno,)577578self._byweekno = tuple(sorted(set(byweekno)))579580self._original_rule['byweekno'] = self._byweekno581582# byweekday / bynweekday583if byweekday is None:584self._byweekday = None585self._bynweekday = None586else:587# If it's one of the valid non-sequence types, convert to a588# single-element sequence before the iterator that builds the589# byweekday set.590if isinstance(byweekday, integer_types) or hasattr(byweekday, "n"):591byweekday = (byweekday,)592593self._byweekday = set()594self._bynweekday = set()595for wday in byweekday:596if isinstance(wday, integer_types):597self._byweekday.add(wday)598elif not wday.n or freq > MONTHLY:599self._byweekday.add(wday.weekday)600else:601self._bynweekday.add((wday.weekday, wday.n))602603if not self._byweekday:604self._byweekday = None605elif not self._bynweekday:606self._bynweekday = None607608if self._byweekday is not None:609self._byweekday = tuple(sorted(self._byweekday))610orig_byweekday = [weekday(x) for x in self._byweekday]611else:612orig_byweekday = ()613614if self._bynweekday is not None:615self._bynweekday = tuple(sorted(self._bynweekday))616orig_bynweekday = [weekday(*x) for x in self._bynweekday]617else:618orig_bynweekday = ()619620if 'byweekday' not in self._original_rule:621self._original_rule['byweekday'] = tuple(itertools.chain(622orig_byweekday, orig_bynweekday))623624# byhour625if byhour is None:626if freq < HOURLY:627self._byhour = {dtstart.hour}628else:629self._byhour = None630else:631if isinstance(byhour, integer_types):632byhour = (byhour,)633634if freq == HOURLY:635self._byhour = self.__construct_byset(start=dtstart.hour,636byxxx=byhour,637base=24)638else:639self._byhour = set(byhour)640641self._byhour = tuple(sorted(self._byhour))642self._original_rule['byhour'] = self._byhour643644# byminute645if byminute is None:646if freq < MINUTELY:647self._byminute = {dtstart.minute}648else:649self._byminute = None650else:651if isinstance(byminute, integer_types):652byminute = (byminute,)653654if freq == MINUTELY:655self._byminute = self.__construct_byset(start=dtstart.minute,656byxxx=byminute,657base=60)658else:659self._byminute = set(byminute)660661self._byminute = tuple(sorted(self._byminute))662self._original_rule['byminute'] = self._byminute663664# bysecond665if bysecond is None:666if freq < SECONDLY:667self._bysecond = ((dtstart.second,))668else:669self._bysecond = None670else:671if isinstance(bysecond, integer_types):672bysecond = (bysecond,)673674self._bysecond = set(bysecond)675676if freq == SECONDLY:677self._bysecond = self.__construct_byset(start=dtstart.second,678byxxx=bysecond,679base=60)680else:681self._bysecond = set(bysecond)682683self._bysecond = tuple(sorted(self._bysecond))684self._original_rule['bysecond'] = self._bysecond685686if self._freq >= HOURLY:687self._timeset = None688else:689self._timeset = []690for hour in self._byhour:691for minute in self._byminute:692for second in self._bysecond:693self._timeset.append(694datetime.time(hour, minute, second,695tzinfo=self._tzinfo))696self._timeset.sort()697self._timeset = tuple(self._timeset)698699def __str__(self):700"""701Output a string that would generate this RRULE if passed to rrulestr.702This is mostly compatible with RFC5545, except for the703dateutil-specific extension BYEASTER.704"""705706output = []707h, m, s = [None] * 3708if self._dtstart:709output.append(self._dtstart.strftime('DTSTART:%Y%m%dT%H%M%S'))710h, m, s = self._dtstart.timetuple()[3:6]711712parts = ['FREQ=' + FREQNAMES[self._freq]]713if self._interval != 1:714parts.append('INTERVAL=' + str(self._interval))715716if self._wkst:717parts.append('WKST=' + repr(weekday(self._wkst))[0:2])718719if self._count is not None:720parts.append('COUNT=' + str(self._count))721722if self._until:723parts.append(self._until.strftime('UNTIL=%Y%m%dT%H%M%S'))724725if self._original_rule.get('byweekday') is not None:726# The str() method on weekday objects doesn't generate727# RFC5545-compliant strings, so we should modify that.728original_rule = dict(self._original_rule)729wday_strings = []730for wday in original_rule['byweekday']:731if wday.n:732wday_strings.append('{n:+d}{wday}'.format(733n=wday.n,734wday=repr(wday)[0:2]))735else:736wday_strings.append(repr(wday))737738original_rule['byweekday'] = wday_strings739else:740original_rule = self._original_rule741742partfmt = '{name}={vals}'743for name, key in [('BYSETPOS', 'bysetpos'),744('BYMONTH', 'bymonth'),745('BYMONTHDAY', 'bymonthday'),746('BYYEARDAY', 'byyearday'),747('BYWEEKNO', 'byweekno'),748('BYDAY', 'byweekday'),749('BYHOUR', 'byhour'),750('BYMINUTE', 'byminute'),751('BYSECOND', 'bysecond'),752('BYEASTER', 'byeaster')]:753value = original_rule.get(key)754if value:755parts.append(partfmt.format(name=name, vals=(','.join(str(v)756for v in value))))757758output.append('RRULE:' + ';'.join(parts))759return '\n'.join(output)760761def replace(self, **kwargs):762"""Return new rrule with same attributes except for those attributes given new763values by whichever keyword arguments are specified."""764new_kwargs = {"interval": self._interval,765"count": self._count,766"dtstart": self._dtstart,767"freq": self._freq,768"until": self._until,769"wkst": self._wkst,770"cache": False if self._cache is None else True }771new_kwargs.update(self._original_rule)772new_kwargs.update(kwargs)773return rrule(**new_kwargs)774775def _iter(self):776year, month, day, hour, minute, second, weekday, yearday, _ = \777self._dtstart.timetuple()778779# Some local variables to speed things up a bit780freq = self._freq781interval = self._interval782wkst = self._wkst783until = self._until784bymonth = self._bymonth785byweekno = self._byweekno786byyearday = self._byyearday787byweekday = self._byweekday788byeaster = self._byeaster789bymonthday = self._bymonthday790bynmonthday = self._bynmonthday791bysetpos = self._bysetpos792byhour = self._byhour793byminute = self._byminute794bysecond = self._bysecond795796ii = _iterinfo(self)797ii.rebuild(year, month)798799getdayset = {YEARLY: ii.ydayset,800MONTHLY: ii.mdayset,801WEEKLY: ii.wdayset,802DAILY: ii.ddayset,803HOURLY: ii.ddayset,804MINUTELY: ii.ddayset,805SECONDLY: ii.ddayset}[freq]806807if freq < HOURLY:808timeset = self._timeset809else:810gettimeset = {HOURLY: ii.htimeset,811MINUTELY: ii.mtimeset,812SECONDLY: ii.stimeset}[freq]813if ((freq >= HOURLY and814self._byhour and hour not in self._byhour) or815(freq >= MINUTELY and816self._byminute and minute not in self._byminute) or817(freq >= SECONDLY and818self._bysecond and second not in self._bysecond)):819timeset = ()820else:821timeset = gettimeset(hour, minute, second)822823total = 0824count = self._count825while True:826# Get dayset with the right frequency827dayset, start, end = getdayset(year, month, day)828829# Do the "hard" work ;-)830filtered = False831for i in dayset[start:end]:832if ((bymonth and ii.mmask[i] not in bymonth) or833(byweekno and not ii.wnomask[i]) or834(byweekday and ii.wdaymask[i] not in byweekday) or835(ii.nwdaymask and not ii.nwdaymask[i]) or836(byeaster and not ii.eastermask[i]) or837((bymonthday or bynmonthday) and838ii.mdaymask[i] not in bymonthday and839ii.nmdaymask[i] not in bynmonthday) or840(byyearday and841((i < ii.yearlen and i+1 not in byyearday and842-ii.yearlen+i not in byyearday) or843(i >= ii.yearlen and i+1-ii.yearlen not in byyearday and844-ii.nextyearlen+i-ii.yearlen not in byyearday)))):845dayset[i] = None846filtered = True847848# Output results849if bysetpos and timeset:850poslist = []851for pos in bysetpos:852if pos < 0:853daypos, timepos = divmod(pos, len(timeset))854else:855daypos, timepos = divmod(pos-1, len(timeset))856try:857i = [x for x in dayset[start:end]858if x is not None][daypos]859time = timeset[timepos]860except IndexError:861pass862else:863date = datetime.date.fromordinal(ii.yearordinal+i)864res = datetime.datetime.combine(date, time)865if res not in poslist:866poslist.append(res)867poslist.sort()868for res in poslist:869if until and res > until:870self._len = total871return872elif res >= self._dtstart:873if count is not None:874count -= 1875if count < 0:876self._len = total877return878total += 1879yield res880else:881for i in dayset[start:end]:882if i is not None:883date = datetime.date.fromordinal(ii.yearordinal + i)884for time in timeset:885res = datetime.datetime.combine(date, time)886if until and res > until:887self._len = total888return889elif res >= self._dtstart:890if count is not None:891count -= 1892if count < 0:893self._len = total894return895896total += 1897yield res898899# Handle frequency and interval900fixday = False901if freq == YEARLY:902year += interval903if year > datetime.MAXYEAR:904self._len = total905return906ii.rebuild(year, month)907elif freq == MONTHLY:908month += interval909if month > 12:910div, mod = divmod(month, 12)911month = mod912year += div913if month == 0:914month = 12915year -= 1916if year > datetime.MAXYEAR:917self._len = total918return919ii.rebuild(year, month)920elif freq == WEEKLY:921if wkst > weekday:922day += -(weekday+1+(6-wkst))+self._interval*7923else:924day += -(weekday-wkst)+self._interval*7925weekday = wkst926fixday = True927elif freq == DAILY:928day += interval929fixday = True930elif freq == HOURLY:931if filtered:932# Jump to one iteration before next day933hour += ((23-hour)//interval)*interval934935if byhour:936ndays, hour = self.__mod_distance(value=hour,937byxxx=self._byhour,938base=24)939else:940ndays, hour = divmod(hour+interval, 24)941942if ndays:943day += ndays944fixday = True945946timeset = gettimeset(hour, minute, second)947elif freq == MINUTELY:948if filtered:949# Jump to one iteration before next day950minute += ((1439-(hour*60+minute))//interval)*interval951952valid = False953rep_rate = (24*60)954for j in range(rep_rate // gcd(interval, rep_rate)):955if byminute:956nhours, minute = \957self.__mod_distance(value=minute,958byxxx=self._byminute,959base=60)960else:961nhours, minute = divmod(minute+interval, 60)962963div, hour = divmod(hour+nhours, 24)964if div:965day += div966fixday = True967filtered = False968969if not byhour or hour in byhour:970valid = True971break972973if not valid:974raise ValueError('Invalid combination of interval and ' +975'byhour resulting in empty rule.')976977timeset = gettimeset(hour, minute, second)978elif freq == SECONDLY:979if filtered:980# Jump to one iteration before next day981second += (((86399 - (hour * 3600 + minute * 60 + second))982// interval) * interval)983984rep_rate = (24 * 3600)985valid = False986for j in range(0, rep_rate // gcd(interval, rep_rate)):987if bysecond:988nminutes, second = \989self.__mod_distance(value=second,990byxxx=self._bysecond,991base=60)992else:993nminutes, second = divmod(second+interval, 60)994995div, minute = divmod(minute+nminutes, 60)996if div:997hour += div998div, hour = divmod(hour, 24)999if div:1000day += div1001fixday = True10021003if ((not byhour or hour in byhour) and1004(not byminute or minute in byminute) and1005(not bysecond or second in bysecond)):1006valid = True1007break10081009if not valid:1010raise ValueError('Invalid combination of interval, ' +1011'byhour and byminute resulting in empty' +1012' rule.')10131014timeset = gettimeset(hour, minute, second)10151016if fixday and day > 28:1017daysinmonth = calendar.monthrange(year, month)[1]1018if day > daysinmonth:1019while day > daysinmonth:1020day -= daysinmonth1021month += 11022if month == 13:1023month = 11024year += 11025if year > datetime.MAXYEAR:1026self._len = total1027return1028daysinmonth = calendar.monthrange(year, month)[1]1029ii.rebuild(year, month)10301031def __construct_byset(self, start, byxxx, base):1032"""1033If a `BYXXX` sequence is passed to the constructor at the same level as1034`FREQ` (e.g. `FREQ=HOURLY,BYHOUR={2,4,7},INTERVAL=3`), there are some1035specifications which cannot be reached given some starting conditions.10361037This occurs whenever the interval is not coprime with the base of a1038given unit and the difference between the starting position and the1039ending position is not coprime with the greatest common denominator1040between the interval and the base. For example, with a FREQ of hourly1041starting at 17:00 and an interval of 4, the only valid values for1042BYHOUR would be {21, 1, 5, 9, 13, 17}, because 4 and 24 are not1043coprime.10441045:param start:1046Specifies the starting position.1047:param byxxx:1048An iterable containing the list of allowed values.1049:param base:1050The largest allowable value for the specified frequency (e.g.105124 hours, 60 minutes).10521053This does not preserve the type of the iterable, returning a set, since1054the values should be unique and the order is irrelevant, this will1055speed up later lookups.10561057In the event of an empty set, raises a :exception:`ValueError`, as this1058results in an empty rrule.1059"""10601061cset = set()10621063# Support a single byxxx value.1064if isinstance(byxxx, integer_types):1065byxxx = (byxxx, )10661067for num in byxxx:1068i_gcd = gcd(self._interval, base)1069# Use divmod rather than % because we need to wrap negative nums.1070if i_gcd == 1 or divmod(num - start, i_gcd)[1] == 0:1071cset.add(num)10721073if len(cset) == 0:1074raise ValueError("Invalid rrule byxxx generates an empty set.")10751076return cset10771078def __mod_distance(self, value, byxxx, base):1079"""1080Calculates the next value in a sequence where the `FREQ` parameter is1081specified along with a `BYXXX` parameter at the same "level"1082(e.g. `HOURLY` specified with `BYHOUR`).10831084:param value:1085The old value of the component.1086:param byxxx:1087The `BYXXX` set, which should have been generated by1088`rrule._construct_byset`, or something else which checks that a1089valid rule is present.1090:param base:1091The largest allowable value for the specified frequency (e.g.109224 hours, 60 minutes).10931094If a valid value is not found after `base` iterations (the maximum1095number before the sequence would start to repeat), this raises a1096:exception:`ValueError`, as no valid values were found.10971098This returns a tuple of `divmod(n*interval, base)`, where `n` is the1099smallest number of `interval` repetitions until the next specified1100value in `byxxx` is found.1101"""1102accumulator = 01103for ii in range(1, base + 1):1104# Using divmod() over % to account for negative intervals1105div, value = divmod(value + self._interval, base)1106accumulator += div1107if value in byxxx:1108return (accumulator, value)110911101111class _iterinfo(object):1112__slots__ = ["rrule", "lastyear", "lastmonth",1113"yearlen", "nextyearlen", "yearordinal", "yearweekday",1114"mmask", "mrange", "mdaymask", "nmdaymask",1115"wdaymask", "wnomask", "nwdaymask", "eastermask"]11161117def __init__(self, rrule):1118for attr in self.__slots__:1119setattr(self, attr, None)1120self.rrule = rrule11211122def rebuild(self, year, month):1123# Every mask is 7 days longer to handle cross-year weekly periods.1124rr = self.rrule1125if year != self.lastyear:1126self.yearlen = 365 + calendar.isleap(year)1127self.nextyearlen = 365 + calendar.isleap(year + 1)1128firstyday = datetime.date(year, 1, 1)1129self.yearordinal = firstyday.toordinal()1130self.yearweekday = firstyday.weekday()11311132wday = datetime.date(year, 1, 1).weekday()1133if self.yearlen == 365:1134self.mmask = M365MASK1135self.mdaymask = MDAY365MASK1136self.nmdaymask = NMDAY365MASK1137self.wdaymask = WDAYMASK[wday:]1138self.mrange = M365RANGE1139else:1140self.mmask = M366MASK1141self.mdaymask = MDAY366MASK1142self.nmdaymask = NMDAY366MASK1143self.wdaymask = WDAYMASK[wday:]1144self.mrange = M366RANGE11451146if not rr._byweekno:1147self.wnomask = None1148else:1149self.wnomask = [0]*(self.yearlen+7)1150# no1wkst = firstwkst = self.wdaymask.index(rr._wkst)1151no1wkst = firstwkst = (7-self.yearweekday+rr._wkst) % 71152if no1wkst >= 4:1153no1wkst = 01154# Number of days in the year, plus the days we got1155# from last year.1156wyearlen = self.yearlen+(self.yearweekday-rr._wkst) % 71157else:1158# Number of days in the year, minus the days we1159# left in last year.1160wyearlen = self.yearlen-no1wkst1161div, mod = divmod(wyearlen, 7)1162numweeks = div+mod//41163for n in rr._byweekno:1164if n < 0:1165n += numweeks+11166if not (0 < n <= numweeks):1167continue1168if n > 1:1169i = no1wkst+(n-1)*71170if no1wkst != firstwkst:1171i -= 7-firstwkst1172else:1173i = no1wkst1174for j in range(7):1175self.wnomask[i] = 11176i += 11177if self.wdaymask[i] == rr._wkst:1178break1179if 1 in rr._byweekno:1180# Check week number 1 of next year as well1181# TODO: Check -numweeks for next year.1182i = no1wkst+numweeks*71183if no1wkst != firstwkst:1184i -= 7-firstwkst1185if i < self.yearlen:1186# If week starts in next year, we1187# don't care about it.1188for j in range(7):1189self.wnomask[i] = 11190i += 11191if self.wdaymask[i] == rr._wkst:1192break1193if no1wkst:1194# Check last week number of last year as1195# well. If no1wkst is 0, either the year1196# started on week start, or week number 11197# got days from last year, so there are no1198# days from last year's last week number in1199# this year.1200if -1 not in rr._byweekno:1201lyearweekday = datetime.date(year-1, 1, 1).weekday()1202lno1wkst = (7-lyearweekday+rr._wkst) % 71203lyearlen = 365+calendar.isleap(year-1)1204if lno1wkst >= 4:1205lno1wkst = 01206lnumweeks = 52+(lyearlen +1207(lyearweekday-rr._wkst) % 7) % 7//41208else:1209lnumweeks = 52+(self.yearlen-no1wkst) % 7//41210else:1211lnumweeks = -11212if lnumweeks in rr._byweekno:1213for i in range(no1wkst):1214self.wnomask[i] = 112151216if (rr._bynweekday and (month != self.lastmonth or1217year != self.lastyear)):1218ranges = []1219if rr._freq == YEARLY:1220if rr._bymonth:1221for month in rr._bymonth:1222ranges.append(self.mrange[month-1:month+1])1223else:1224ranges = [(0, self.yearlen)]1225elif rr._freq == MONTHLY:1226ranges = [self.mrange[month-1:month+1]]1227if ranges:1228# Weekly frequency won't get here, so we may not1229# care about cross-year weekly periods.1230self.nwdaymask = [0]*self.yearlen1231for first, last in ranges:1232last -= 11233for wday, n in rr._bynweekday:1234if n < 0:1235i = last+(n+1)*71236i -= (self.wdaymask[i]-wday) % 71237else:1238i = first+(n-1)*71239i += (7-self.wdaymask[i]+wday) % 71240if first <= i <= last:1241self.nwdaymask[i] = 112421243if rr._byeaster:1244self.eastermask = [0]*(self.yearlen+7)1245eyday = easter.easter(year).toordinal()-self.yearordinal1246for offset in rr._byeaster:1247self.eastermask[eyday+offset] = 112481249self.lastyear = year1250self.lastmonth = month12511252def ydayset(self, year, month, day):1253return list(range(self.yearlen)), 0, self.yearlen12541255def mdayset(self, year, month, day):1256dset = [None]*self.yearlen1257start, end = self.mrange[month-1:month+1]1258for i in range(start, end):1259dset[i] = i1260return dset, start, end12611262def wdayset(self, year, month, day):1263# We need to handle cross-year weeks here.1264dset = [None]*(self.yearlen+7)1265i = datetime.date(year, month, day).toordinal()-self.yearordinal1266start = i1267for j in range(7):1268dset[i] = i1269i += 11270# if (not (0 <= i < self.yearlen) or1271# self.wdaymask[i] == self.rrule._wkst):1272# This will cross the year boundary, if necessary.1273if self.wdaymask[i] == self.rrule._wkst:1274break1275return dset, start, i12761277def ddayset(self, year, month, day):1278dset = [None] * self.yearlen1279i = datetime.date(year, month, day).toordinal() - self.yearordinal1280dset[i] = i1281return dset, i, i + 112821283def htimeset(self, hour, minute, second):1284tset = []1285rr = self.rrule1286for minute in rr._byminute:1287for second in rr._bysecond:1288tset.append(datetime.time(hour, minute, second,1289tzinfo=rr._tzinfo))1290tset.sort()1291return tset12921293def mtimeset(self, hour, minute, second):1294tset = []1295rr = self.rrule1296for second in rr._bysecond:1297tset.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo))1298tset.sort()1299return tset13001301def stimeset(self, hour, minute, second):1302return (datetime.time(hour, minute, second,1303tzinfo=self.rrule._tzinfo),)130413051306class rruleset(rrulebase):1307""" The rruleset type allows more complex recurrence setups, mixing1308multiple rules, dates, exclusion rules, and exclusion dates. The type1309constructor takes the following keyword arguments:13101311:param cache: If True, caching of results will be enabled, improving1312performance of multiple queries considerably. """13131314class _genitem(object):1315def __init__(self, genlist, gen):1316try:1317self.dt = advance_iterator(gen)1318genlist.append(self)1319except StopIteration:1320pass1321self.genlist = genlist1322self.gen = gen13231324def __next__(self):1325try:1326self.dt = advance_iterator(self.gen)1327except StopIteration:1328if self.genlist[0] is self:1329heapq.heappop(self.genlist)1330else:1331self.genlist.remove(self)1332heapq.heapify(self.genlist)13331334next = __next__13351336def __lt__(self, other):1337return self.dt < other.dt13381339def __gt__(self, other):1340return self.dt > other.dt13411342def __eq__(self, other):1343return self.dt == other.dt13441345def __ne__(self, other):1346return self.dt != other.dt13471348def __init__(self, cache=False):1349super(rruleset, self).__init__(cache)1350self._rrule = []1351self._rdate = []1352self._exrule = []1353self._exdate = []13541355@_invalidates_cache1356def rrule(self, rrule):1357""" Include the given :py:class:`rrule` instance in the recurrence set1358generation. """1359self._rrule.append(rrule)13601361@_invalidates_cache1362def rdate(self, rdate):1363""" Include the given :py:class:`datetime` instance in the recurrence1364set generation. """1365self._rdate.append(rdate)13661367@_invalidates_cache1368def exrule(self, exrule):1369""" Include the given rrule instance in the recurrence set exclusion1370list. Dates which are part of the given recurrence rules will not1371be generated, even if some inclusive rrule or rdate matches them.1372"""1373self._exrule.append(exrule)13741375@_invalidates_cache1376def exdate(self, exdate):1377""" Include the given datetime instance in the recurrence set1378exclusion list. Dates included that way will not be generated,1379even if some inclusive rrule or rdate matches them. """1380self._exdate.append(exdate)13811382def _iter(self):1383rlist = []1384self._rdate.sort()1385self._genitem(rlist, iter(self._rdate))1386for gen in [iter(x) for x in self._rrule]:1387self._genitem(rlist, gen)1388exlist = []1389self._exdate.sort()1390self._genitem(exlist, iter(self._exdate))1391for gen in [iter(x) for x in self._exrule]:1392self._genitem(exlist, gen)1393lastdt = None1394total = 01395heapq.heapify(rlist)1396heapq.heapify(exlist)1397while rlist:1398ritem = rlist[0]1399if not lastdt or lastdt != ritem.dt:1400while exlist and exlist[0] < ritem:1401exitem = exlist[0]1402advance_iterator(exitem)1403if exlist and exlist[0] is exitem:1404heapq.heapreplace(exlist, exitem)1405if not exlist or ritem != exlist[0]:1406total += 11407yield ritem.dt1408lastdt = ritem.dt1409advance_iterator(ritem)1410if rlist and rlist[0] is ritem:1411heapq.heapreplace(rlist, ritem)1412self._len = total14131414141514161417class _rrulestr(object):1418""" Parses a string representation of a recurrence rule or set of1419recurrence rules.14201421:param s:1422Required, a string defining one or more recurrence rules.14231424:param dtstart:1425If given, used as the default recurrence start if not specified in the1426rule string.14271428:param cache:1429If set ``True`` caching of results will be enabled, improving1430performance of multiple queries considerably.14311432:param unfold:1433If set ``True`` indicates that a rule string is split over more1434than one line and should be joined before processing.14351436:param forceset:1437If set ``True`` forces a :class:`dateutil.rrule.rruleset` to1438be returned.14391440:param compatible:1441If set ``True`` forces ``unfold`` and ``forceset`` to be ``True``.14421443:param ignoretz:1444If set ``True``, time zones in parsed strings are ignored and a naive1445:class:`datetime.datetime` object is returned.14461447:param tzids:1448If given, a callable or mapping used to retrieve a1449:class:`datetime.tzinfo` from a string representation.1450Defaults to :func:`dateutil.tz.gettz`.14511452:param tzinfos:1453Additional time zone names / aliases which may be present in a string1454representation. See :func:`dateutil.parser.parse` for more1455information.14561457:return:1458Returns a :class:`dateutil.rrule.rruleset` or1459:class:`dateutil.rrule.rrule`1460"""14611462_freq_map = {"YEARLY": YEARLY,1463"MONTHLY": MONTHLY,1464"WEEKLY": WEEKLY,1465"DAILY": DAILY,1466"HOURLY": HOURLY,1467"MINUTELY": MINUTELY,1468"SECONDLY": SECONDLY}14691470_weekday_map = {"MO": 0, "TU": 1, "WE": 2, "TH": 3,1471"FR": 4, "SA": 5, "SU": 6}14721473def _handle_int(self, rrkwargs, name, value, **kwargs):1474rrkwargs[name.lower()] = int(value)14751476def _handle_int_list(self, rrkwargs, name, value, **kwargs):1477rrkwargs[name.lower()] = [int(x) for x in value.split(',')]14781479_handle_INTERVAL = _handle_int1480_handle_COUNT = _handle_int1481_handle_BYSETPOS = _handle_int_list1482_handle_BYMONTH = _handle_int_list1483_handle_BYMONTHDAY = _handle_int_list1484_handle_BYYEARDAY = _handle_int_list1485_handle_BYEASTER = _handle_int_list1486_handle_BYWEEKNO = _handle_int_list1487_handle_BYHOUR = _handle_int_list1488_handle_BYMINUTE = _handle_int_list1489_handle_BYSECOND = _handle_int_list14901491def _handle_FREQ(self, rrkwargs, name, value, **kwargs):1492rrkwargs["freq"] = self._freq_map[value]14931494def _handle_UNTIL(self, rrkwargs, name, value, **kwargs):1495global parser1496if not parser:1497from dateutil import parser1498try:1499rrkwargs["until"] = parser.parse(value,1500ignoretz=kwargs.get("ignoretz"),1501tzinfos=kwargs.get("tzinfos"))1502except ValueError:1503raise ValueError("invalid until date")15041505def _handle_WKST(self, rrkwargs, name, value, **kwargs):1506rrkwargs["wkst"] = self._weekday_map[value]15071508def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwargs):1509"""1510Two ways to specify this: +1MO or MO(+1)1511"""1512l = []1513for wday in value.split(','):1514if '(' in wday:1515# If it's of the form TH(+1), etc.1516splt = wday.split('(')1517w = splt[0]1518n = int(splt[1][:-1])1519elif len(wday):1520# If it's of the form +1MO1521for i in range(len(wday)):1522if wday[i] not in '+-0123456789':1523break1524n = wday[:i] or None1525w = wday[i:]1526if n:1527n = int(n)1528else:1529raise ValueError("Invalid (empty) BYDAY specification.")15301531l.append(weekdays[self._weekday_map[w]](n))1532rrkwargs["byweekday"] = l15331534_handle_BYDAY = _handle_BYWEEKDAY15351536def _parse_rfc_rrule(self, line,1537dtstart=None,1538cache=False,1539ignoretz=False,1540tzinfos=None):1541if line.find(':') != -1:1542name, value = line.split(':')1543if name != "RRULE":1544raise ValueError("unknown parameter name")1545else:1546value = line1547rrkwargs = {}1548for pair in value.split(';'):1549name, value = pair.split('=')1550name = name.upper()1551value = value.upper()1552try:1553getattr(self, "_handle_"+name)(rrkwargs, name, value,1554ignoretz=ignoretz,1555tzinfos=tzinfos)1556except AttributeError:1557raise ValueError("unknown parameter '%s'" % name)1558except (KeyError, ValueError):1559raise ValueError("invalid '%s': %s" % (name, value))1560return rrule(dtstart=dtstart, cache=cache, **rrkwargs)15611562def _parse_date_value(self, date_value, parms, rule_tzids,1563ignoretz, tzids, tzinfos):1564global parser1565if not parser:1566from dateutil import parser15671568datevals = []1569value_found = False1570TZID = None15711572for parm in parms:1573if parm.startswith("TZID="):1574try:1575tzkey = rule_tzids[parm.split('TZID=')[-1]]1576except KeyError:1577continue1578if tzids is None:1579from . import tz1580tzlookup = tz.gettz1581elif callable(tzids):1582tzlookup = tzids1583else:1584tzlookup = getattr(tzids, 'get', None)1585if tzlookup is None:1586msg = ('tzids must be a callable, mapping, or None, '1587'not %s' % tzids)1588raise ValueError(msg)15891590TZID = tzlookup(tzkey)1591continue15921593# RFC 5445 3.8.2.4: The VALUE parameter is optional, but may be found1594# only once.1595if parm not in {"VALUE=DATE-TIME", "VALUE=DATE"}:1596raise ValueError("unsupported parm: " + parm)1597else:1598if value_found:1599msg = ("Duplicate value parameter found in: " + parm)1600raise ValueError(msg)1601value_found = True16021603for datestr in date_value.split(','):1604date = parser.parse(datestr, ignoretz=ignoretz, tzinfos=tzinfos)1605if TZID is not None:1606if date.tzinfo is None:1607date = date.replace(tzinfo=TZID)1608else:1609raise ValueError('DTSTART/EXDATE specifies multiple timezone')1610datevals.append(date)16111612return datevals16131614def _parse_rfc(self, s,1615dtstart=None,1616cache=False,1617unfold=False,1618forceset=False,1619compatible=False,1620ignoretz=False,1621tzids=None,1622tzinfos=None):1623global parser1624if compatible:1625forceset = True1626unfold = True16271628TZID_NAMES = dict(map(1629lambda x: (x.upper(), x),1630re.findall('TZID=(?P<name>[^:]+):', s)1631))1632s = s.upper()1633if not s.strip():1634raise ValueError("empty string")1635if unfold:1636lines = s.splitlines()1637i = 01638while i < len(lines):1639line = lines[i].rstrip()1640if not line:1641del lines[i]1642elif i > 0 and line[0] == " ":1643lines[i-1] += line[1:]1644del lines[i]1645else:1646i += 11647else:1648lines = s.split()1649if (not forceset and len(lines) == 1 and (s.find(':') == -1 or1650s.startswith('RRULE:'))):1651return self._parse_rfc_rrule(lines[0], cache=cache,1652dtstart=dtstart, ignoretz=ignoretz,1653tzinfos=tzinfos)1654else:1655rrulevals = []1656rdatevals = []1657exrulevals = []1658exdatevals = []1659for line in lines:1660if not line:1661continue1662if line.find(':') == -1:1663name = "RRULE"1664value = line1665else:1666name, value = line.split(':', 1)1667parms = name.split(';')1668if not parms:1669raise ValueError("empty property name")1670name = parms[0]1671parms = parms[1:]1672if name == "RRULE":1673for parm in parms:1674raise ValueError("unsupported RRULE parm: "+parm)1675rrulevals.append(value)1676elif name == "RDATE":1677for parm in parms:1678if parm != "VALUE=DATE-TIME":1679raise ValueError("unsupported RDATE parm: "+parm)1680rdatevals.append(value)1681elif name == "EXRULE":1682for parm in parms:1683raise ValueError("unsupported EXRULE parm: "+parm)1684exrulevals.append(value)1685elif name == "EXDATE":1686exdatevals.extend(1687self._parse_date_value(value, parms,1688TZID_NAMES, ignoretz,1689tzids, tzinfos)1690)1691elif name == "DTSTART":1692dtvals = self._parse_date_value(value, parms, TZID_NAMES,1693ignoretz, tzids, tzinfos)1694if len(dtvals) != 1:1695raise ValueError("Multiple DTSTART values specified:" +1696value)1697dtstart = dtvals[0]1698else:1699raise ValueError("unsupported property: "+name)1700if (forceset or len(rrulevals) > 1 or rdatevals1701or exrulevals or exdatevals):1702if not parser and (rdatevals or exdatevals):1703from dateutil import parser1704rset = rruleset(cache=cache)1705for value in rrulevals:1706rset.rrule(self._parse_rfc_rrule(value, dtstart=dtstart,1707ignoretz=ignoretz,1708tzinfos=tzinfos))1709for value in rdatevals:1710for datestr in value.split(','):1711rset.rdate(parser.parse(datestr,1712ignoretz=ignoretz,1713tzinfos=tzinfos))1714for value in exrulevals:1715rset.exrule(self._parse_rfc_rrule(value, dtstart=dtstart,1716ignoretz=ignoretz,1717tzinfos=tzinfos))1718for value in exdatevals:1719rset.exdate(value)1720if compatible and dtstart:1721rset.rdate(dtstart)1722return rset1723else:1724return self._parse_rfc_rrule(rrulevals[0],1725dtstart=dtstart,1726cache=cache,1727ignoretz=ignoretz,1728tzinfos=tzinfos)17291730def __call__(self, s, **kwargs):1731return self._parse_rfc(s, **kwargs)173217331734rrulestr = _rrulestr()17351736# vim:ts=4:sw=4:et173717381739