# HG changeset patch # User Paul Boddie # Date 1512242120 -3600 # Node ID 4a33f1cb48ac5b83f21e7b7ee72a223eb2d1932e # Parent 592a4701a05f064bd3ba0464bfeb4a4d8b1e72b8 Introduced a selector that ensures the inclusion of the main period in the set of generated occurrences, respecting any COUNT qualifier. Renamed order_qualifiers to make_selectors. diff -r 592a4701a05f -r 4a33f1cb48ac vRecurrence.py --- a/vRecurrence.py Fri Dec 01 23:09:21 2017 +0100 +++ b/vRecurrence.py Sat Dec 02 20:15:20 2017 +0100 @@ -244,7 +244,7 @@ return values -def order_qualifiers(qualifiers): +def make_selectors(qualifiers): """ Obtain 'qualifiers' in order of increasing resolution, producing and @@ -283,6 +283,14 @@ else: return Pattern(freq[qualifier], args, qualifier) +def insert_start_selector(selectors, dt): + + "Return a copy of 'selectors' incorporating 'dt'." + + selectors = selectors + [StartSelector(0, {"start" : dt}, "DTSTART")] + selectors.sort(key=selector_sort_key) + return selectors + def sort_selectors(selectors): "Sort 'selectors' in order of increasing resolution." @@ -309,8 +317,19 @@ # Other BY... qualifiers sort earlier than selectors at the same resolution # even though such things as "FREQ=HOURLY;BYHOUR=10" do not make much sense. - return (selector.level, not selector.qualifier.startswith("BY") and 2 or - selector.qualifier != "BYSETPOS" and 1 or 0) + if not selector.qualifier.startswith("BY"): + sublevel = 2 + elif selector.qualifier != "BYSETPOS": + sublevel = 1 + + # Make DTSTART sort later than COUNT. + + elif selector.qualifier == "DTSTART": + sublevel = 1 + else: + sublevel = 0 + + return (selector.level, sublevel) def get_value_ranges(qualifier): @@ -1031,6 +1050,17 @@ def set_positions(self, positions): self.args["values"] = positions +class StartSelector(Selector): + + "A selector ensuring that the start occurrence is included." + + def materialise_items(self, context, start, end, inclusive=False): + return StartIterator(self, context, start, end, inclusive, + self.get_start()) + + def get_start(self): + return self.args["start"] + special_enum_levels = { "BYDAY" : WeekDayFilter, "BYMONTHDAY" : MonthDayFilter, @@ -1373,6 +1403,39 @@ else: raise +class StartIterator(SelectorIterator): + + "An iterator ensuring that the start occurrence is included." + + def __init__(self, selector, current, start, end, inclusive, start_dt): + SelectorIterator.__init__(self, selector, current, start, end, inclusive) + self.waiting = start_dt + + def next(self): + + "Return the next value, initially the start period." + + while not self.at_limit(): + result = self.next_item(self.start, self.end) + + # Compare with any waiting value. + + if self.waiting: + + # Produce the waiting value, queue the latest result. + + if result != self.waiting: + result, self.waiting = self.waiting, result + + # Remove the waiting value if identical to the latest result. + + else: + self.waiting = None + + return result + + raise StopIteration + def connect_selectors(selectors): """ @@ -1390,7 +1453,7 @@ # Allow selectors within the limit selector to act as if they are first # in the chain and will operate using the supplied datetime context. - first = isinstance(current, LimitSelector) + first = isinstance(current, (LimitSelector, StartSelector)) current = selector current.first = first @@ -1418,6 +1481,7 @@ """ dt = to_tuple(dt) + selectors = insert_start_selector(selectors, dt) return connect_selectors(combine_datetime_with_selectors(dt, selectors)) def get_selectors_for_rule(rule): @@ -1429,6 +1493,6 @@ if not isinstance(rule, tuple): rule = (rule or "").split(";") - return order_qualifiers(get_qualifiers(rule)) + return make_selectors(get_qualifiers(rule)) # vim: tabstop=4 expandtab shiftwidth=4