Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
rrule.py1738 linesDownload Raw Back to dateutil
1# -*- coding: utf-8 -*-2"""3The rrule module offers a small, complete, and very fast, implementation of4the recurrence rules documented in the5`iCalendar RFC <https://tools.ietf.org/html/rfc5545>`_,6including support for caching of results.7"""8import calendar9import datetime10import heapq11import itertools12import re13import sys14from functools import wraps15# For warning about deprecation of until and count16from warnings import warn17 18from six import advance_iterator, integer_types19 20from six.moves import _thread, range21 22from ._common import weekday as weekdaybase23 24try:25    from math import gcd26except ImportError:27    from fractions import gcd28 29__all__ = ["rrule", "rruleset", "rrulestr",30           "YEARLY", "MONTHLY", "WEEKLY", "DAILY",31           "HOURLY", "MINUTELY", "SECONDLY",32           "MO", "TU", "WE", "TH", "FR", "SA", "SU"]33 34# Every mask is 7 days longer to handle cross-year weekly periods.35M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30 +36                 [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7)37M365MASK = list(M366MASK)38M29, M30, M31 = list(range(1, 30)), list(range(1, 31)), list(range(1, 32))39MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])40MDAY365MASK = list(MDAY366MASK)41M29, M30, M31 = list(range(-29, 0)), list(range(-30, 0)), list(range(-31, 0))42NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])43NMDAY365MASK = list(NMDAY366MASK)44M366RANGE = (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366)45M365RANGE = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)46WDAYMASK = [0, 1, 2, 3, 4, 5, 6]*5547del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31]48MDAY365MASK = tuple(MDAY365MASK)49M365MASK = tuple(M365MASK)50 51FREQNAMES = ['YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', 'HOURLY', 'MINUTELY', 'SECONDLY']52 53(YEARLY,54 MONTHLY,55 WEEKLY,56 DAILY,57 HOURLY,58 MINUTELY,59 SECONDLY) = list(range(7))60 61# Imported on demand.62easter = None63parser = None64 65 66class weekday(weekdaybase):67    """68    This version of weekday does not allow n = 0.69    """70    def __init__(self, wkday, n=None):71        if n == 0:72            raise ValueError("Can't create weekday with n==0")73 74        super(weekday, self).__init__(wkday, n)75 76 77MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))78 79 80def _invalidates_cache(f):81    """82    Decorator for rruleset methods which may invalidate the83    cached length.84    """85    @wraps(f)86    def inner_func(self, *args, **kwargs):87        rv = f(self, *args, **kwargs)88        self._invalidate_cache()89        return rv90 91    return inner_func92 93 94class rrulebase(object):95    def __init__(self, cache=False):96        if cache:97            self._cache = []98            self._cache_lock = _thread.allocate_lock()99            self._invalidate_cache()100        else:101            self._cache = None102            self._cache_complete = False103            self._len = None104 105    def __iter__(self):106        if self._cache_complete:107            return iter(self._cache)108        elif self._cache is None:109            return self._iter()110        else:111            return self._iter_cached()112 113    def _invalidate_cache(self):114        if self._cache is not None:115            self._cache = []116            self._cache_complete = False117            self._cache_gen = self._iter()118 119            if self._cache_lock.locked():120                self._cache_lock.release()121 122        self._len = None123 124    def _iter_cached(self):125        i = 0126        gen = self._cache_gen127        cache = self._cache128        acquire = self._cache_lock.acquire129        release = self._cache_lock.release130        while gen:131            if i == len(cache):132                acquire()133                if self._cache_complete:134                    break135                try:136                    for j in range(10):137                        cache.append(advance_iterator(gen))138                except StopIteration:139                    self._cache_gen = gen = None140                    self._cache_complete = True141                    break142                release()143            yield cache[i]144            i += 1145        while i < self._len:146            yield cache[i]147            i += 1148 149    def __getitem__(self, item):150        if self._cache_complete:151            return self._cache[item]152        elif isinstance(item, slice):153            if item.step and item.step < 0:154                return list(iter(self))[item]155            else:156                return list(itertools.islice(self,157                                             item.start or 0,158                                             item.stop or sys.maxsize,159                                             item.step or 1))160        elif item >= 0:161            gen = iter(self)162            try:163                for i in range(item+1):164                    res = advance_iterator(gen)165            except StopIteration:166                raise IndexError167            return res168        else:169            return list(iter(self))[item]170 171    def __contains__(self, item):172        if self._cache_complete:173            return item in self._cache174        else:175            for i in self:176                if i == item:177                    return True178                elif i > item:179                    return False180        return False181 182    # __len__() introduces a large performance penalty.183    def count(self):184        """ Returns the number of recurrences in this set. It will have go185            through the whole recurrence, if this hasn't been done before. """186        if self._len is None:187            for x in self:188                pass189        return self._len190 191    def before(self, dt, inc=False):192        """ Returns the last recurrence before the given datetime instance. The193            inc keyword defines what happens if dt is an occurrence. With194            inc=True, if dt itself is an occurrence, it will be returned. """195        if self._cache_complete:196            gen = self._cache197        else:198            gen = self199        last = None200        if inc:201            for i in gen:202                if i > dt:203                    break204                last = i205        else:206            for i in gen:207                if i >= dt:208                    break209                last = i210        return last211 212    def after(self, dt, inc=False):213        """ Returns the first recurrence after the given datetime instance. The214            inc keyword defines what happens if dt is an occurrence. With215            inc=True, if dt itself is an occurrence, it will be returned.  """216        if self._cache_complete:217            gen = self._cache218        else:219            gen = self220        if inc:221            for i in gen:222                if i >= dt:223                    return i224        else:225            for i in gen:226                if i > dt:227                    return i228        return None229 230    def xafter(self, dt, count=None, inc=False):231        """232        Generator which yields up to `count` recurrences after the given233        datetime instance, equivalent to `after`.234 235        :param dt:236            The datetime at which to start generating recurrences.237 238        :param count:239            The maximum number of recurrences to generate. If `None` (default),240            dates are generated until the recurrence rule is exhausted.241 242        :param inc:243            If `dt` is an instance of the rule and `inc` is `True`, it is244            included in the output.245 246        :yields: Yields a sequence of `datetime` objects.247        """248 249        if self._cache_complete:250            gen = self._cache251        else:252            gen = self253 254        # Select the comparison function255        if inc:256            comp = lambda dc, dtc: dc >= dtc257        else:258            comp = lambda dc, dtc: dc > dtc259 260        # Generate dates261        n = 0262        for d in gen:263            if comp(d, dt):264                if count is not None:265                    n += 1266                    if n > count:267                        break268 269                yield d270 271    def between(self, after, before, inc=False, count=1):272        """ Returns all the occurrences of the rrule between after and before.273        The inc keyword defines what happens if after and/or before are274        themselves occurrences. With inc=True, they will be included in the275        list, if they are found in the recurrence set. """276        if self._cache_complete:277            gen = self._cache278        else:279            gen = self280        started = False281        l = []282        if inc:283            for i in gen:284                if i > before:285                    break286                elif not started:287                    if i >= after:288                        started = True289                        l.append(i)290                else:291                    l.append(i)292        else:293            for i in gen:294                if i >= before:295                    break296                elif not started:297                    if i > after:298                        started = True299                        l.append(i)300                else:301                    l.append(i)302        return l303 304 305class rrule(rrulebase):306    """307    That's the base of the rrule operation. It accepts all the keywords308    defined in the RFC as its constructor parameters (except byday,309    which was renamed to byweekday) and more. The constructor prototype is::310 311            rrule(freq)312 313    Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,314    or SECONDLY.315 316    .. note::317        Per RFC section 3.3.10, recurrence instances falling on invalid dates318        and times are ignored rather than coerced:319 320            Recurrence rules may generate recurrence instances with an invalid321            date (e.g., February 30) or nonexistent local time (e.g., 1:30 AM322            on a day where the local time is moved forward by an hour at 1:00323            AM).  Such recurrence instances MUST be ignored and MUST NOT be324            counted as part of the recurrence set.325 326        This can lead to possibly surprising behavior when, for example, the327        start date occurs at the end of the month:328 329        >>> from dateutil.rrule import rrule, MONTHLY330        >>> from datetime import datetime331        >>> start_date = datetime(2014, 12, 31)332        >>> list(rrule(freq=MONTHLY, count=4, dtstart=start_date))333        ... # doctest: +NORMALIZE_WHITESPACE334        [datetime.datetime(2014, 12, 31, 0, 0),335         datetime.datetime(2015, 1, 31, 0, 0),336         datetime.datetime(2015, 3, 31, 0, 0),337         datetime.datetime(2015, 5, 31, 0, 0)]338 339    Additionally, it supports the following keyword arguments:340 341    :param dtstart:342        The recurrence start. Besides being the base for the recurrence,343        missing parameters in the final recurrence instances will also be344        extracted from this date. If not given, datetime.now() will be used345        instead.346    :param interval:347        The interval between each freq iteration. For example, when using348        YEARLY, an interval of 2 means once every two years, but with HOURLY,349        it means once every two hours. The default interval is 1.350    :param wkst:351        The week start day. Must be one of the MO, TU, WE constants, or an352        integer, specifying the first day of the week. This will affect353        recurrences based on weekly periods. The default week start is got354        from calendar.firstweekday(), and may be modified by355        calendar.setfirstweekday().356    :param count:357        If given, this determines how many occurrences will be generated.358 359        .. note::360            As of version 2.5.0, the use of the keyword ``until`` in conjunction361            with ``count`` is deprecated, to make sure ``dateutil`` is fully362            compliant with `RFC-5545 Sec. 3.3.10 <https://tools.ietf.org/363            html/rfc5545#section-3.3.10>`_. Therefore, ``until`` and ``count``364            **must not** occur in the same call to ``rrule``.365    :param until:366        If given, this must be a datetime instance specifying the upper-bound367        limit of the recurrence. The last recurrence in the rule is the greatest368        datetime that is less than or equal to the value specified in the369        ``until`` parameter.370 371        .. note::372            As of version 2.5.0, the use of the keyword ``until`` in conjunction373            with ``count`` is deprecated, to make sure ``dateutil`` is fully374            compliant with `RFC-5545 Sec. 3.3.10 <https://tools.ietf.org/375            html/rfc5545#section-3.3.10>`_. Therefore, ``until`` and ``count``376            **must not** occur in the same call to ``rrule``.377    :param bysetpos:378        If given, it must be either an integer, or a sequence of integers,379        positive or negative. Each given integer will specify an occurrence380        number, corresponding to the nth occurrence of the rule inside the381        frequency period. For example, a bysetpos of -1 if combined with a382        MONTHLY frequency, and a byweekday of (MO, TU, WE, TH, FR), will383        result in the last work day of every month.384    :param bymonth:385        If given, it must be either an integer, or a sequence of integers,386        meaning the months to apply the recurrence to.387    :param bymonthday:388        If given, it must be either an integer, or a sequence of integers,389        meaning the month days to apply the recurrence to.390    :param byyearday:391        If given, it must be either an integer, or a sequence of integers,392        meaning the year days to apply the recurrence to.393    :param byeaster:394        If given, it must be either an integer, or a sequence of integers,395        positive or negative. Each integer will define an offset from the396        Easter Sunday. Passing the offset 0 to byeaster will yield the Easter397        Sunday itself. This is an extension to the RFC specification.398    :param byweekno:399        If given, it must be either an integer, or a sequence of integers,400        meaning the week numbers to apply the recurrence to. Week numbers401        have the meaning described in ISO8601, that is, the first week of402        the year is that containing at least four days of the new year.403    :param byweekday:404        If given, it must be either an integer (0 == MO), a sequence of405        integers, one of the weekday constants (MO, TU, etc), or a sequence406        of these constants. When given, these variables will define the407        weekdays where the recurrence will be applied. It's also possible to408        use an argument n for the weekday instances, which will mean the nth409        occurrence of this weekday in the period. For example, with MONTHLY,410        or with YEARLY and BYMONTH, using FR(+1) in byweekday will specify the411        first friday of the month where the recurrence happens. Notice that in412        the RFC documentation, this is specified as BYDAY, but was renamed to413        avoid the ambiguity of that keyword.414    :param byhour:415        If given, it must be either an integer, or a sequence of integers,416        meaning the hours to apply the recurrence to.417    :param byminute:418        If given, it must be either an integer, or a sequence of integers,419        meaning the minutes to apply the recurrence to.420    :param bysecond:421        If given, it must be either an integer, or a sequence of integers,422        meaning the seconds to apply the recurrence to.423    :param cache:424        If given, it must be a boolean value specifying to enable or disable425        caching of results. If you will use the same rrule instance multiple426        times, enabling caching will improve the performance considerably.427     """428    def __init__(self, freq, dtstart=None,429                 interval=1, wkst=None, count=None, until=None, bysetpos=None,430                 bymonth=None, bymonthday=None, byyearday=None, byeaster=None,431                 byweekno=None, byweekday=None,432                 byhour=None, byminute=None, bysecond=None,433                 cache=False):434        super(rrule, self).__init__(cache)435        global easter436        if not dtstart:437            if until and until.tzinfo:438                dtstart = datetime.datetime.now(tz=until.tzinfo).replace(microsecond=0)439            else:440                dtstart = datetime.datetime.now().replace(microsecond=0)441        elif not isinstance(dtstart, datetime.datetime):442            dtstart = datetime.datetime.fromordinal(dtstart.toordinal())443        else:444            dtstart = dtstart.replace(microsecond=0)445        self._dtstart = dtstart446        self._tzinfo = dtstart.tzinfo447        self._freq = freq448        self._interval = interval449        self._count = count450 451        # Cache the original byxxx rules, if they are provided, as the _byxxx452        # attributes do not necessarily map to the inputs, and this can be453        # a problem in generating the strings. Only store things if they've454        # been supplied (the string retrieval will just use .get())455        self._original_rule = {}456 457        if until and not isinstance(until, datetime.datetime):458            until = datetime.datetime.fromordinal(until.toordinal())459        self._until = until460 461        if self._dtstart and self._until:462            if (self._dtstart.tzinfo is not None) != (self._until.tzinfo is not None):463                # According to RFC5545 Section 3.3.10:464                # https://tools.ietf.org/html/rfc5545#section-3.3.10465                #466                # > If the "DTSTART" property is specified as a date with UTC467                # > time or a date with local time and time zone reference,468                # > then the UNTIL rule part MUST be specified as a date with469                # > UTC time.470                raise ValueError(471                    'RRULE UNTIL values must be specified in UTC when DTSTART '472                    'is timezone-aware'473                )474 475        if count is not None and until:476            warn("Using both 'count' and 'until' is inconsistent with RFC 5545"477                 " and has been deprecated in dateutil. Future versions will "478                 "raise an error.", DeprecationWarning)479 480        if wkst is None:481            self._wkst = calendar.firstweekday()482        elif isinstance(wkst, integer_types):483            self._wkst = wkst484        else:485            self._wkst = wkst.weekday486 487        if bysetpos is None:488            self._bysetpos = None489        elif isinstance(bysetpos, integer_types):490            if bysetpos == 0 or not (-366 <= bysetpos <= 366):491                raise ValueError("bysetpos must be between 1 and 366, "492                                 "or between -366 and -1")493            self._bysetpos = (bysetpos,)494        else:495            self._bysetpos = tuple(bysetpos)496            for pos in self._bysetpos:497                if pos == 0 or not (-366 <= pos <= 366):498                    raise ValueError("bysetpos must be between 1 and 366, "499                                     "or between -366 and -1")500 501        if self._bysetpos:502            self._original_rule['bysetpos'] = self._bysetpos503 504        if (byweekno is None and byyearday is None and bymonthday is None and505                byweekday is None and byeaster is None):506            if freq == YEARLY:507                if bymonth is None:508                    bymonth = dtstart.month509                    self._original_rule['bymonth'] = None510                bymonthday = dtstart.day511                self._original_rule['bymonthday'] = None512            elif freq == MONTHLY:513                bymonthday = dtstart.day514                self._original_rule['bymonthday'] = None515            elif freq == WEEKLY:516                byweekday = dtstart.weekday()517                self._original_rule['byweekday'] = None518 519        # bymonth520        if bymonth is None:521            self._bymonth = None522        else:523            if isinstance(bymonth, integer_types):524                bymonth = (bymonth,)525 526            self._bymonth = tuple(sorted(set(bymonth)))527 528            if 'bymonth' not in self._original_rule:529                self._original_rule['bymonth'] = self._bymonth530 531        # byyearday532        if byyearday is None:533            self._byyearday = None534        else:535            if isinstance(byyearday, integer_types):536                byyearday = (byyearday,)537 538            self._byyearday = tuple(sorted(set(byyearday)))539            self._original_rule['byyearday'] = self._byyearday540 541        # byeaster542        if byeaster is not None:543            if not easter:544                from dateutil import easter545            if isinstance(byeaster, integer_types):546                self._byeaster = (byeaster,)547            else:548                self._byeaster = tuple(sorted(byeaster))549 550            self._original_rule['byeaster'] = self._byeaster551        else:552            self._byeaster = None553 554        # bymonthday555        if bymonthday is None:556            self._bymonthday = ()557            self._bynmonthday = ()558        else:559            if isinstance(bymonthday, integer_types):560                bymonthday = (bymonthday,)561 562            bymonthday = set(bymonthday)            # Ensure it's unique563 564            self._bymonthday = tuple(sorted(x for x in bymonthday if x > 0))565            self._bynmonthday = tuple(sorted(x for x in bymonthday if x < 0))566 567            # Storing positive numbers first, then negative numbers568            if 'bymonthday' not in self._original_rule:569                self._original_rule['bymonthday'] = tuple(570                    itertools.chain(self._bymonthday, self._bynmonthday))571 572        # byweekno573        if byweekno is None:574            self._byweekno = None575        else:576            if isinstance(byweekno, integer_types):577                byweekno = (byweekno,)578 579            self._byweekno = tuple(sorted(set(byweekno)))580 581            self._original_rule['byweekno'] = self._byweekno582 583        # byweekday / bynweekday584        if byweekday is None:585            self._byweekday = None586            self._bynweekday = None587        else:588            # If it's one of the valid non-sequence types, convert to a589            # single-element sequence before the iterator that builds the590            # byweekday set.591            if isinstance(byweekday, integer_types) or hasattr(byweekday, "n"):592                byweekday = (byweekday,)593 594            self._byweekday = set()595            self._bynweekday = set()596            for wday in byweekday:597                if isinstance(wday, integer_types):598                    self._byweekday.add(wday)599                elif not wday.n or freq > MONTHLY:600                    self._byweekday.add(wday.weekday)601                else:602                    self._bynweekday.add((wday.weekday, wday.n))603 604            if not self._byweekday:605                self._byweekday = None606            elif not self._bynweekday:607                self._bynweekday = None608 609            if self._byweekday is not None:610                self._byweekday = tuple(sorted(self._byweekday))611                orig_byweekday = [weekday(x) for x in self._byweekday]612            else:613                orig_byweekday = ()614 615            if self._bynweekday is not None:616                self._bynweekday = tuple(sorted(self._bynweekday))617                orig_bynweekday = [weekday(*x) for x in self._bynweekday]618            else:619                orig_bynweekday = ()620 621            if 'byweekday' not in self._original_rule:622                self._original_rule['byweekday'] = tuple(itertools.chain(623                    orig_byweekday, orig_bynweekday))624 625        # byhour626        if byhour is None:627            if freq < HOURLY:628                self._byhour = {dtstart.hour}629            else:630                self._byhour = None631        else:632            if isinstance(byhour, integer_types):633                byhour = (byhour,)634 635            if freq == HOURLY:636                self._byhour = self.__construct_byset(start=dtstart.hour,637                                                      byxxx=byhour,638                                                      base=24)639            else:640                self._byhour = set(byhour)641 642            self._byhour = tuple(sorted(self._byhour))643            self._original_rule['byhour'] = self._byhour644 645        # byminute646        if byminute is None:647            if freq < MINUTELY:648                self._byminute = {dtstart.minute}649            else:650                self._byminute = None651        else:652            if isinstance(byminute, integer_types):653                byminute = (byminute,)654 655            if freq == MINUTELY:656                self._byminute = self.__construct_byset(start=dtstart.minute,657                                                        byxxx=byminute,658                                                        base=60)659            else:660                self._byminute = set(byminute)661 662            self._byminute = tuple(sorted(self._byminute))663            self._original_rule['byminute'] = self._byminute664 665        # bysecond666        if bysecond is None:667            if freq < SECONDLY:668                self._bysecond = ((dtstart.second,))669            else:670                self._bysecond = None671        else:672            if isinstance(bysecond, integer_types):673                bysecond = (bysecond,)674 675            self._bysecond = set(bysecond)676 677            if freq == SECONDLY:678                self._bysecond = self.__construct_byset(start=dtstart.second,679                                                        byxxx=bysecond,680                                                        base=60)681            else:682                self._bysecond = set(bysecond)683 684            self._bysecond = tuple(sorted(self._bysecond))685            self._original_rule['bysecond'] = self._bysecond686 687        if self._freq >= HOURLY:688            self._timeset = None689        else:690            self._timeset = []691            for hour in self._byhour:692                for minute in self._byminute:693                    for second in self._bysecond:694                        self._timeset.append(695                            datetime.time(hour, minute, second,696                                          tzinfo=self._tzinfo))697            self._timeset.sort()698            self._timeset = tuple(self._timeset)699 700    def __str__(self):701        """702        Output a string that would generate this RRULE if passed to rrulestr.703        This is mostly compatible with RFC5545, except for the704        dateutil-specific extension BYEASTER.705        """706 707        output = []708        h, m, s = [None] * 3709        if self._dtstart:710            output.append(self._dtstart.strftime('DTSTART:%Y%m%dT%H%M%S'))711            h, m, s = self._dtstart.timetuple()[3:6]712 713        parts = ['FREQ=' + FREQNAMES[self._freq]]714        if self._interval != 1:715            parts.append('INTERVAL=' + str(self._interval))716 717        if self._wkst:718            parts.append('WKST=' + repr(weekday(self._wkst))[0:2])719 720        if self._count is not None:721            parts.append('COUNT=' + str(self._count))722 723        if self._until:724            parts.append(self._until.strftime('UNTIL=%Y%m%dT%H%M%S'))725 726        if self._original_rule.get('byweekday') is not None:727            # The str() method on weekday objects doesn't generate728            # RFC5545-compliant strings, so we should modify that.729            original_rule = dict(self._original_rule)730            wday_strings = []731            for wday in original_rule['byweekday']:732                if wday.n:733                    wday_strings.append('{n:+d}{wday}'.format(734                        n=wday.n,735                        wday=repr(wday)[0:2]))736                else:737                    wday_strings.append(repr(wday))738 739            original_rule['byweekday'] = wday_strings740        else:741            original_rule = self._original_rule742 743        partfmt = '{name}={vals}'744        for name, key in [('BYSETPOS', 'bysetpos'),745                          ('BYMONTH', 'bymonth'),746                          ('BYMONTHDAY', 'bymonthday'),747                          ('BYYEARDAY', 'byyearday'),748                          ('BYWEEKNO', 'byweekno'),749                          ('BYDAY', 'byweekday'),750                          ('BYHOUR', 'byhour'),751                          ('BYMINUTE', 'byminute'),752                          ('BYSECOND', 'bysecond'),753                          ('BYEASTER', 'byeaster')]:754            value = original_rule.get(key)755            if value:756                parts.append(partfmt.format(name=name, vals=(','.join(str(v)757                                                             for v in value))))758 759        output.append('RRULE:' + ';'.join(parts))760        return '\n'.join(output)761 762    def replace(self, **kwargs):763        """Return new rrule with same attributes except for those attributes given new764           values by whichever keyword arguments are specified."""765        new_kwargs = {"interval": self._interval,766                      "count": self._count,767                      "dtstart": self._dtstart,768                      "freq": self._freq,769                      "until": self._until,770                      "wkst": self._wkst,771                      "cache": False if self._cache is None else True }772        new_kwargs.update(self._original_rule)773        new_kwargs.update(kwargs)774        return rrule(**new_kwargs)775 776    def _iter(self):777        year, month, day, hour, minute, second, weekday, yearday, _ = \778            self._dtstart.timetuple()779 780        # Some local variables to speed things up a bit781        freq = self._freq782        interval = self._interval783        wkst = self._wkst784        until = self._until785        bymonth = self._bymonth786        byweekno = self._byweekno787        byyearday = self._byyearday788        byweekday = self._byweekday789        byeaster = self._byeaster790        bymonthday = self._bymonthday791        bynmonthday = self._bynmonthday792        bysetpos = self._bysetpos793        byhour = self._byhour794        byminute = self._byminute795        bysecond = self._bysecond796 797        ii = _iterinfo(self)798        ii.rebuild(year, month)799 800        getdayset = {YEARLY: ii.ydayset,801                     MONTHLY: ii.mdayset,802                     WEEKLY: ii.wdayset,803                     DAILY: ii.ddayset,804                     HOURLY: ii.ddayset,805                     MINUTELY: ii.ddayset,806                     SECONDLY: ii.ddayset}[freq]807 808        if freq < HOURLY:809            timeset = self._timeset810        else:811            gettimeset = {HOURLY: ii.htimeset,812                          MINUTELY: ii.mtimeset,813                          SECONDLY: ii.stimeset}[freq]814            if ((freq >= HOURLY and815                 self._byhour and hour not in self._byhour) or816                (freq >= MINUTELY and817                 self._byminute and minute not in self._byminute) or818                (freq >= SECONDLY and819                 self._bysecond and second not in self._bysecond)):820                timeset = ()821            else:822                timeset = gettimeset(hour, minute, second)823 824        total = 0825        count = self._count826        while True:827            # Get dayset with the right frequency828            dayset, start, end = getdayset(year, month, day)829 830            # Do the "hard" work ;-)831            filtered = False832            for i in dayset[start:end]:833                if ((bymonth and ii.mmask[i] not in bymonth) or834                    (byweekno and not ii.wnomask[i]) or835                    (byweekday and ii.wdaymask[i] not in byweekday) or836                    (ii.nwdaymask and not ii.nwdaymask[i]) or837                    (byeaster and not ii.eastermask[i]) or838                    ((bymonthday or bynmonthday) and839                     ii.mdaymask[i] not in bymonthday and840                     ii.nmdaymask[i] not in bynmonthday) or841                    (byyearday and842                     ((i < ii.yearlen and i+1 not in byyearday and843                       -ii.yearlen+i not in byyearday) or844                      (i >= ii.yearlen and i+1-ii.yearlen not in byyearday and845                       -ii.nextyearlen+i-ii.yearlen not in byyearday)))):846                    dayset[i] = None847                    filtered = True848 849            # Output results850            if bysetpos and timeset:851                poslist = []852                for pos in bysetpos:853                    if pos < 0:854                        daypos, timepos = divmod(pos, len(timeset))855                    else:856                        daypos, timepos = divmod(pos-1, len(timeset))857                    try:858                        i = [x for x in dayset[start:end]859                             if x is not None][daypos]860                        time = timeset[timepos]861                    except IndexError:862                        pass863                    else:864                        date = datetime.date.fromordinal(ii.yearordinal+i)865                        res = datetime.datetime.combine(date, time)866                        if res not in poslist:867                            poslist.append(res)868                poslist.sort()869                for res in poslist:870                    if until and res > until:871                        self._len = total872                        return873                    elif res >= self._dtstart:874                        if count is not None:875                            count -= 1876                            if count < 0:877                                self._len = total878                                return879                        total += 1880                        yield res881            else:882                for i in dayset[start:end]:883                    if i is not None:884                        date = datetime.date.fromordinal(ii.yearordinal + i)885                        for time in timeset:886                            res = datetime.datetime.combine(date, time)887                            if until and res > until:888                                self._len = total889                                return890                            elif res >= self._dtstart:891                                if count is not None:892                                    count -= 1893                                    if count < 0:894                                        self._len = total895                                        return896 897                                total += 1898                                yield res899 900            # Handle frequency and interval901            fixday = False902            if freq == YEARLY:903                year += interval904                if year > datetime.MAXYEAR:905                    self._len = total906                    return907                ii.rebuild(year, month)908            elif freq == MONTHLY:909                month += interval910                if month > 12:911                    div, mod = divmod(month, 12)912                    month = mod913                    year += div914                    if month == 0:915                        month = 12916                        year -= 1917                    if year > datetime.MAXYEAR:918                        self._len = total919                        return920                ii.rebuild(year, month)921            elif freq == WEEKLY:922                if wkst > weekday:923                    day += -(weekday+1+(6-wkst))+self._interval*7924                else:925                    day += -(weekday-wkst)+self._interval*7926                weekday = wkst927                fixday = True928            elif freq == DAILY:929                day += interval930                fixday = True931            elif freq == HOURLY:932                if filtered:933                    # Jump to one iteration before next day934                    hour += ((23-hour)//interval)*interval935 936                if byhour:937                    ndays, hour = self.__mod_distance(value=hour,938                                                      byxxx=self._byhour,939                                                      base=24)940                else:941                    ndays, hour = divmod(hour+interval, 24)942 943                if ndays:944                    day += ndays945                    fixday = True946 947                timeset = gettimeset(hour, minute, second)948            elif freq == MINUTELY:949                if filtered:950                    # Jump to one iteration before next day951                    minute += ((1439-(hour*60+minute))//interval)*interval952 953                valid = False954                rep_rate = (24*60)955                for j in range(rep_rate // gcd(interval, rep_rate)):956                    if byminute:957                        nhours, minute = \958                            self.__mod_distance(value=minute,959                                                byxxx=self._byminute,960                                                base=60)961                    else:962                        nhours, minute = divmod(minute+interval, 60)963 964                    div, hour = divmod(hour+nhours, 24)965                    if div:966                        day += div967                        fixday = True968                        filtered = False969 970                    if not byhour or hour in byhour:971                        valid = True972                        break973 974                if not valid:975                    raise ValueError('Invalid combination of interval and ' +976                                     'byhour resulting in empty rule.')977 978                timeset = gettimeset(hour, minute, second)979            elif freq == SECONDLY:980                if filtered:981                    # Jump to one iteration before next day982                    second += (((86399 - (hour * 3600 + minute * 60 + second))983                                // interval) * interval)984 985                rep_rate = (24 * 3600)986                valid = False987                for j in range(0, rep_rate // gcd(interval, rep_rate)):988                    if bysecond:989                        nminutes, second = \990                            self.__mod_distance(value=second,991                                                byxxx=self._bysecond,992                                                base=60)993                    else:994                        nminutes, second = divmod(second+interval, 60)995 996                    div, minute = divmod(minute+nminutes, 60)997                    if div:998                        hour += div999                        div, hour = divmod(hour, 24)1000                        if div:1001                            day += div1002                            fixday = True1003 1004                    if ((not byhour or hour in byhour) and1005                            (not byminute or minute in byminute) and1006                            (not bysecond or second in bysecond)):1007                        valid = True1008                        break1009 1010                if not valid:1011                    raise ValueError('Invalid combination of interval, ' +1012                                     'byhour and byminute resulting in empty' +1013                                     ' rule.')1014 1015                timeset = gettimeset(hour, minute, second)1016 1017            if fixday and day > 28:1018                daysinmonth = calendar.monthrange(year, month)[1]1019                if day > daysinmonth:1020                    while day > daysinmonth:1021                        day -= daysinmonth1022                        month += 11023                        if month == 13:1024                            month = 11025                            year += 11026                            if year > datetime.MAXYEAR:1027                                self._len = total1028                                return1029                        daysinmonth = calendar.monthrange(year, month)[1]1030                    ii.rebuild(year, month)1031 1032    def __construct_byset(self, start, byxxx, base):1033        """1034        If a `BYXXX` sequence is passed to the constructor at the same level as1035        `FREQ` (e.g. `FREQ=HOURLY,BYHOUR={2,4,7},INTERVAL=3`), there are some1036        specifications which cannot be reached given some starting conditions.1037 1038        This occurs whenever the interval is not coprime with the base of a1039        given unit and the difference between the starting position and the1040        ending position is not coprime with the greatest common denominator1041        between the interval and the base. For example, with a FREQ of hourly1042        starting at 17:00 and an interval of 4, the only valid values for1043        BYHOUR would be {21, 1, 5, 9, 13, 17}, because 4 and 24 are not1044        coprime.1045 1046        :param start:1047            Specifies the starting position.1048        :param byxxx:1049            An iterable containing the list of allowed values.1050        :param base:1051            The largest allowable value for the specified frequency (e.g.1052            24 hours, 60 minutes).1053 1054        This does not preserve the type of the iterable, returning a set, since1055        the values should be unique and the order is irrelevant, this will1056        speed up later lookups.1057 1058        In the event of an empty set, raises a :exception:`ValueError`, as this1059        results in an empty rrule.1060        """1061 1062        cset = set()1063 1064        # Support a single byxxx value.1065        if isinstance(byxxx, integer_types):1066            byxxx = (byxxx, )1067 1068        for num in byxxx:1069            i_gcd = gcd(self._interval, base)1070            # Use divmod rather than % because we need to wrap negative nums.1071            if i_gcd == 1 or divmod(num - start, i_gcd)[1] == 0:1072                cset.add(num)1073 1074        if len(cset) == 0:1075            raise ValueError("Invalid rrule byxxx generates an empty set.")1076 1077        return cset1078 1079    def __mod_distance(self, value, byxxx, base):1080        """1081        Calculates the next value in a sequence where the `FREQ` parameter is1082        specified along with a `BYXXX` parameter at the same "level"1083        (e.g. `HOURLY` specified with `BYHOUR`).1084 1085        :param value:1086            The old value of the component.1087        :param byxxx:1088            The `BYXXX` set, which should have been generated by1089            `rrule._construct_byset`, or something else which checks that a1090            valid rule is present.1091        :param base:1092            The largest allowable value for the specified frequency (e.g.1093            24 hours, 60 minutes).1094 1095        If a valid value is not found after `base` iterations (the maximum1096        number before the sequence would start to repeat), this raises a1097        :exception:`ValueError`, as no valid values were found.1098 1099        This returns a tuple of `divmod(n*interval, base)`, where `n` is the1100        smallest number of `interval` repetitions until the next specified1101        value in `byxxx` is found.1102        """1103        accumulator = 01104        for ii in range(1, base + 1):1105            # Using divmod() over % to account for negative intervals1106            div, value = divmod(value + self._interval, base)1107            accumulator += div1108            if value in byxxx:1109                return (accumulator, value)1110 1111 1112class _iterinfo(object):1113    __slots__ = ["rrule", "lastyear", "lastmonth",1114                 "yearlen", "nextyearlen", "yearordinal", "yearweekday",1115                 "mmask", "mrange", "mdaymask", "nmdaymask",1116                 "wdaymask", "wnomask", "nwdaymask", "eastermask"]1117 1118    def __init__(self, rrule):1119        for attr in self.__slots__:1120            setattr(self, attr, None)1121        self.rrule = rrule1122 1123    def rebuild(self, year, month):1124        # Every mask is 7 days longer to handle cross-year weekly periods.1125        rr = self.rrule1126        if year != self.lastyear:1127            self.yearlen = 365 + calendar.isleap(year)1128            self.nextyearlen = 365 + calendar.isleap(year + 1)1129            firstyday = datetime.date(year, 1, 1)1130            self.yearordinal = firstyday.toordinal()1131            self.yearweekday = firstyday.weekday()1132 1133            wday = datetime.date(year, 1, 1).weekday()1134            if self.yearlen == 365:1135                self.mmask = M365MASK1136                self.mdaymask = MDAY365MASK1137                self.nmdaymask = NMDAY365MASK1138                self.wdaymask = WDAYMASK[wday:]1139                self.mrange = M365RANGE1140            else:1141                self.mmask = M366MASK1142                self.mdaymask = MDAY366MASK1143                self.nmdaymask = NMDAY366MASK1144                self.wdaymask = WDAYMASK[wday:]1145                self.mrange = M366RANGE1146 1147            if not rr._byweekno:1148                self.wnomask = None1149            else:1150                self.wnomask = [0]*(self.yearlen+7)1151                # no1wkst = firstwkst = self.wdaymask.index(rr._wkst)1152                no1wkst = firstwkst = (7-self.yearweekday+rr._wkst) % 71153                if no1wkst >= 4:1154                    no1wkst = 01155                    # Number of days in the year, plus the days we got1156                    # from last year.1157                    wyearlen = self.yearlen+(self.yearweekday-rr._wkst) % 71158                else:1159                    # Number of days in the year, minus the days we1160                    # left in last year.1161                    wyearlen = self.yearlen-no1wkst1162                div, mod = divmod(wyearlen, 7)1163                numweeks = div+mod//41164                for n in rr._byweekno:1165                    if n < 0:1166                        n += numweeks+11167                    if not (0 < n <= numweeks):1168                        continue1169                    if n > 1:1170                        i = no1wkst+(n-1)*71171                        if no1wkst != firstwkst:1172                            i -= 7-firstwkst1173                    else:1174                        i = no1wkst1175                    for j in range(7):1176                        self.wnomask[i] = 11177                        i += 11178                        if self.wdaymask[i] == rr._wkst:1179                            break1180                if 1 in rr._byweekno:1181                    # Check week number 1 of next year as well1182                    # TODO: Check -numweeks for next year.1183                    i = no1wkst+numweeks*71184                    if no1wkst != firstwkst:1185                        i -= 7-firstwkst1186                    if i < self.yearlen:1187                        # If week starts in next year, we1188                        # don't care about it.1189                        for j in range(7):1190                            self.wnomask[i] = 11191                            i += 11192                            if self.wdaymask[i] == rr._wkst:1193                                break1194                if no1wkst:1195                    # Check last week number of last year as1196                    # well. If no1wkst is 0, either the year1197                    # started on week start, or week number 11198                    # got days from last year, so there are no1199                    # days from last year's last week number in1200                    # this year.

Showing the first 1,200 of 1738 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai