# HG changeset patch # User Paul Boddie # Date 1425165617 -3600 # Node ID 1774e82340427af3b2d465465ad9b263f683e0e2 # Parent 43aded34a75fa2ae0ec2a921f5bfe8f9af0292eb Added support for inclusive end points when materialising instances. diff -r 43aded34a75f -r 1774e8234042 vRecurrence.py --- a/vRecurrence.py Sat Feb 28 22:30:52 2015 +0100 +++ b/vRecurrence.py Sun Mar 01 00:20:17 2015 +0100 @@ -405,23 +405,26 @@ 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, setpos=None): + def materialise(self, start, end, count=None, setpos=None, inclusive=False): """ Starting at 'start', materialise instances up to but not including any at 'end' or later, returning at most 'count' if specified, and returning only the occurrences indicated by 'setpos' if specified. A list of instances is returned. + + If 'inclusive' is specified, the selection of instances will include the + end of the search period if present in the results. """ start = to_tuple(start) end = to_tuple(end) counter = count and [0, count] - results = self.materialise_items(self.context, start, end, counter, setpos) + results = self.materialise_items(self.context, start, end, counter, setpos, inclusive) results.sort() return results[:count] - def materialise_item(self, current, earliest, next, counter, setpos=None): + def materialise_item(self, current, earliest, next, counter, setpos=None, inclusive=False): """ Given the 'current' instance, the 'earliest' acceptable instance, the @@ -432,7 +435,7 @@ """ if self.selecting: - return self.selecting.materialise_items(current, earliest, next, counter, setpos) + return self.selecting.materialise_items(current, earliest, next, counter, setpos, inclusive) elif earliest <= current: return [current] else: @@ -459,16 +462,19 @@ l += results[lower:upper] return l - def filter_by_period(self, results, start, end): + def filter_by_period(self, results, start, end, inclusive): """ Filter 'results' so that only those at or after 'start' and before 'end' are returned. + + If 'inclusive' is specified, the selection of instances will include the + end of the search period if present in the results. """ l = [] for result in results: - if start <= result < end: + if start <= result and (inclusive and result <= end or result < end): l.append(result) return l @@ -476,7 +482,7 @@ "A selector of instances according to a repeating pattern." - def materialise_items(self, context, start, end, counter, setpos=None): + def materialise_items(self, context, start, end, counter, setpos=None, inclusive=False): first = scale(self.context[self.pos], self.pos) # Define the step between items. @@ -492,10 +498,10 @@ current = combine(context, first) results = [] - while current < end and (counter is None or counter[0] < counter[1]): + while (inclusive and current <= end or current < end) and (counter is None or counter[0] < counter[1]): next = update(current, step) current_end = update(current, unit_step) - interval_results = self.materialise_item(current, max(current, start), min(current_end, end), counter, setpos) + interval_results = self.materialise_item(current, max(current, start), min(current_end, end), counter, setpos, inclusive) if counter is not None: counter[0] += len(interval_results) results += interval_results @@ -507,7 +513,7 @@ "A selector of instances specified in terms of day numbers." - def materialise_items(self, context, start, end, counter, setpos=None): + def materialise_items(self, context, start, end, counter, setpos=None, inclusive=False): step = scale(1, 2) results = [] @@ -530,10 +536,10 @@ current = context values = [value for (value, index) in self.args["values"]] - while current < end: + while (inclusive and current <= end or current < end): next = update(current, step) if date(*current).isoweekday() in values: - results += self.materialise_item(current, max(current, start), min(next, end), counter) + results += self.materialise_item(current, max(current, start), min(next, end), counter, inclusive=inclusive) current = next if setpos: @@ -557,7 +563,7 @@ # To support setpos, only current and next bound the search, not # the period in addition. - results += self.materialise_item(current, current, next, counter) + results += self.materialise_item(current, current, next, counter, inclusive=inclusive) else: if index < 0: @@ -573,7 +579,7 @@ # To support setpos, only current and next bound the search, not # the period in addition. - results += self.materialise_item(current, current, next, counter) + results += self.materialise_item(current, current, next, counter, inclusive=inclusive) current = to_tuple(direction(date(*current), timedelta(7)), 3) # Extract selected positions and remove out-of-period instances. @@ -581,10 +587,10 @@ if setpos: results = self.select_positions(results, setpos) - return self.filter_by_period(results, start, end) + return self.filter_by_period(results, start, end, inclusive) class Enum(Selector): - def materialise_items(self, context, start, end, counter, setpos=None): + def materialise_items(self, context, start, end, counter, setpos=None, inclusive=False): step = scale(1, self.pos) results = [] for value in self.args["values"]: @@ -594,17 +600,17 @@ # To support setpos, only current and next bound the search, not # the period in addition. - results += self.materialise_item(current, current, next, counter, setpos) + results += self.materialise_item(current, current, next, counter, setpos, inclusive) # 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) + return self.filter_by_period(results, start, end, inclusive) class MonthDayFilter(Enum): - def materialise_items(self, context, start, end, counter, setpos=None): + def materialise_items(self, context, start, end, counter, setpos=None, inclusive=False): last_day = monthrange(context[0], context[1])[1] step = scale(1, self.pos) results = [] @@ -617,17 +623,17 @@ # To support setpos, only current and next bound the search, not # the period in addition. - results += self.materialise_item(current, current, next, counter) + results += self.materialise_item(current, current, next, counter, inclusive=inclusive) # 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) + return self.filter_by_period(results, start, end, inclusive) class YearDayFilter(Enum): - def materialise_items(self, context, start, end, counter, setpos=None): + def materialise_items(self, context, start, end, counter, setpos=None, inclusive=False): first_day = date(context[0], 1, 1) next_first_day = date(context[0] + 1, 1, 1) year_length = (next_first_day - first_day).days @@ -642,14 +648,14 @@ # To support setpos, only current and next bound the search, not # the period in addition. - results += self.materialise_item(current, current, next, counter) + results += self.materialise_item(current, current, next, counter, inclusive=inclusive) # 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) + return self.filter_by_period(results, start, end, inclusive) special_enum_levels = { "BYDAY" : WeekDayFilter,