# HG changeset patch # User Paul Boddie # Date 1412785091 -7200 # Node ID 5f249406b86133cfde305484734846263bcd9e2a # Parent 2328724eb6d3c5d195c987b263fdeb92b18d6ba3 Introduced BYSETPOS support, counting instances only in patterns, and filtering instances according to the period of interest only after having selected the indicated set positions. diff -r 2328724eb6d3 -r 5f249406b861 tests/qualifiers.py --- a/tests/qualifiers.py Mon Oct 06 16:19:16 2014 +0200 +++ b/tests/qualifiers.py Wed Oct 08 18:18:11 2014 +0200 @@ -711,4 +711,46 @@ print l[-1] == (2004, 11, 2, 9, 0, 0), (2004, 11, 2, 9, 0, 0), l[-1] print +qualifiers = [ + ("MONTHLY", {"interval" : 1}), + ("BYDAY", {"values" : [(2, None), (3, None), (4, None)]}) + ] + +l = order_qualifiers(qualifiers) +show(l) +dt = (1997, 9, 4, 9, 0, 0) +l = get_datetime_structure(dt) +show(l) +l = combine_datetime_with_qualifiers(dt, qualifiers) +show(l) + +s = process(l) +l = s.materialise(dt, (1997, 12, 24, 0, 0, 0), 3, [3]) +print len(l) == 3, 3, len(l) +print l[0] == (1997, 9, 4, 9, 0, 0), (1997, 9, 4, 9, 0, 0), l[0] +print l[-1] == (1997, 11, 6, 9, 0, 0), (1997, 11, 6, 9, 0, 0), l[-1] +print + +l2 = l + +qualifiers = [ + ("MONTHLY", {"interval" : 1}), + ("BYDAY", {"values" : [(1, None), (2, None), (3, None), (4, None), (5, None)]}) + ] + +l = order_qualifiers(qualifiers) +show(l) +dt = (1997, 9, 29, 9, 0, 0) +l = get_datetime_structure(dt) +show(l) +l = combine_datetime_with_qualifiers(dt, qualifiers) +show(l) + +s = process(l) +l = s.materialise(dt, (1998, 4, 1, 0, 0, 0), None, [-2]) +print len(l) == 7, 7, len(l) +print l[0] == (1997, 9, 29, 9, 0, 0), (1997, 9, 29, 9, 0, 0), l[0] +print l[-1] == (1998, 3, 30, 9, 0, 0), (1998, 3, 30, 9, 0, 0), l[-1] +print + # vim: tabstop=4 expandtab shiftwidth=4 diff -r 2328724eb6d3 -r 5f249406b861 vRecurrence.py --- a/vRecurrence.py Mon Oct 06 16:19:16 2014 +0200 +++ b/vRecurrence.py Wed Oct 08 18:18:11 2014 +0200 @@ -176,7 +176,7 @@ else: if not have_q: if isinstance(from_q, Enum) and level > 0: - repeat = Pattern(level - 1, {"interval" : 1}, "REPEAT") + repeat = Pattern(level - 1, {"interval" : 1}, None) repeat.context = tuple(context) l.append(repeat) have_q = True @@ -198,7 +198,7 @@ while from_q: if not have_q: if isinstance(from_q, Enum) and level > 0: - repeat = Pattern(level - 1, {"interval" : 1}, "REPEAT") + repeat = Pattern(level - 1, {"interval" : 1}, None) repeat.context = tuple(context) l.append(repeat) have_q = True @@ -293,24 +293,44 @@ def __repr__(self): return "%s(%r, %r, %r, %r)" % (self.__class__.__name__, self.level, self.args, self.qualifier, self.context) - def materialise(self, start, end, count=None): + def materialise(self, start, end, count=None, setpos=None): counter = count and [0, count] - results = self.materialise_items(self.context, start, end, counter) + results = self.materialise_items(self.context, start, end, counter, setpos) results.sort() return results[:count] - def materialise_item(self, current, last, next, counter): - if counter is None or counter[0] < counter[1]: - if self.selecting: - return self.selecting.materialise_items(current, last, next, counter) - elif last <= current: - if counter is not None: - counter[0] += 1 - return [current] - return [] + def materialise_item(self, current, last, next, counter, setpos=None): + if self.selecting: + return self.selecting.materialise_items(current, last, next, counter, setpos) + elif last <= current: + return [current] + else: + return [] + + def convert_positions(self, setpos): + l = [] + for pos in setpos: + lower = pos < 0 and pos or pos - 1 + upper = pos > 0 and pos or pos < -1 and pos + 1 or None + l.append((lower, upper)) + return l + + def select_positions(self, results, setpos): + results.sort() + l = [] + for lower, upper in self.convert_positions(setpos): + l += results[lower:upper] + return l + + def filter_by_period(self, results, start, end): + l = [] + for result in results: + if start <= result < end: + l.append(result) + return l class Pattern(Selector): - def materialise_items(self, context, start, end, counter): + def materialise_items(self, context, start, end, counter, setpos=None): first = scale(self.context[self.pos], self.pos) # Define the step between items. @@ -329,13 +349,16 @@ while current < end and (counter is None or counter[0] < counter[1]): next = update(current, step) current_end = update(current, unit_step) - results += self.materialise_item(current, max(current, start), min(current_end, end), counter) + interval_results = self.materialise_item(current, max(current, start), min(current_end, end), counter, setpos) + if counter is not None: + counter[0] += len(interval_results) + results += interval_results current = next return results class WeekDayFilter(Selector): - def materialise_items(self, context, start, end, counter): + def materialise_items(self, context, start, end, counter, setpos=None): step = scale(1, 2) results = [] @@ -358,12 +381,16 @@ current = context values = [value for (value, index) in self.args["values"]] - while current < end and (counter is None or counter[0] < counter[1]): + while current < end: next = update(current, step) if date(*current).isoweekday() in values: results += self.materialise_item(current, max(current, start), min(next, end), counter) current = next - return results + + if setpos: + return self.select_positions(results, setpos) + else: + return results # Find each of the given days. @@ -376,9 +403,12 @@ else: current = to_tuple(get_first_day(first_day, value) + offset, 3) - if current < end: - next = update(current, step) - results += self.materialise_item(current, max(current, start), min(next, end), counter) + next = update(current, step) + + # To support setpos, only current and next bound the search, not + # the period in addition. + + results += self.materialise_item(current, current, next, counter) else: if index < 0: @@ -389,26 +419,43 @@ direction = operator.add while first_day <= current <= last_day: - if current < end: - next = update(current, step) - results += self.materialise_item(current, max(current, start), min(next, end), counter) + next = update(current, step) + + # To support setpos, only current and next bound the search, not + # the period in addition. + + results += self.materialise_item(current, current, next, counter) current = to_tuple(direction(date(*current), timedelta(7)), 3) - return results + # Extract selected positions and remove out-of-period instances. + + if setpos: + results = self.select_positions(results, setpos) + + return self.filter_by_period(results, start, end) class Enum(Selector): - def materialise_items(self, context, start, end, counter): + def materialise_items(self, context, start, end, counter, setpos=None): step = scale(1, self.pos) results = [] for value in self.args["values"]: current = combine(context, scale(value, self.pos)) - if current < end: - next = update(current, step) - results += self.materialise_item(current, max(current, start), min(next, end), counter) - return results + next = update(current, step) + + # To support setpos, only current and next bound the search, not + # the period in addition. + + results += self.materialise_item(current, current, next, counter, setpos) + + # Extract selected positions and remove out-of-period instances. + + if setpos: + results = self.select_positions(results, setpos) + + return self.filter_by_period(results, start, end) class MonthDayFilter(Enum): - def materialise_items(self, context, start, end, counter): + def materialise_items(self, context, start, end, counter, setpos=None): last_day = monthrange(context[0], context[1])[1] step = scale(1, self.pos) results = [] @@ -416,13 +463,22 @@ if value < 0: value = last_day + 1 + value current = combine(context, scale(value, self.pos)) - if current < end: - next = update(current, step) - results += self.materialise_item(current, max(current, start), min(next, end), counter) - return results + next = update(current, step) + + # To support setpos, only current and next bound the search, not + # the period in addition. + + results += self.materialise_item(current, current, next, counter) + + # Extract selected positions and remove out-of-period instances. + + if setpos: + results = self.select_positions(results, setpos) + + return self.filter_by_period(results, start, end) class YearDayFilter(Enum): - def materialise_items(self, context, start, end, counter): + def materialise_items(self, context, start, end, counter, setpos=None): first_day = date(context[0], 1, 1) next_first_day = date(context[0] + 1, 1, 1) year_length = (next_first_day - first_day).days @@ -432,10 +488,19 @@ if value < 0: value = year_length + 1 + value current = to_tuple(first_day + timedelta(value - 1), 3) - if current < end: - next = update(current, step) - results += self.materialise_item(current, max(current, start), min(next, end), counter) - return results + next = update(current, step) + + # To support setpos, only current and next bound the search, not + # the period in addition. + + results += self.materialise_item(current, current, next, counter) + + # Extract selected positions and remove out-of-period instances. + + if setpos: + results = self.select_positions(results, setpos) + + return self.filter_by_period(results, start, end) def process(selectors): current = selectors[0]