# HG changeset patch # User Paul Boddie # Date 1508864269 -7200 # Node ID c32240f8019d2cf160069dac38445f98bd7b5d26 # Parent 34e3f316f28f2d595d56f5d64230681a8a2fe27d Introduced rule period collection abstractions. Consolidated rule limit tests. diff -r 34e3f316f28f -r c32240f8019d imiptools/data.py --- a/imiptools/data.py Tue Oct 24 01:05:08 2017 +0200 +++ b/imiptools/data.py Tue Oct 24 18:57:49 2017 +0200 @@ -451,20 +451,7 @@ "Return whether this object may recur indefinitely." rrule = self.get_value("RRULE") - parameters = rrule and get_parameters(rrule) - until = parameters and parameters.get("UNTIL") - count = parameters and parameters.get("COUNT") - - # Non-recurring periods or constrained recurrences. - - if not rrule or until or count: - return False - - # Unconstrained recurring periods will always lie beyond any specified - # datetime. - - else: - return True + return rrule and not rule_has_end(rrule) # Modification methods. @@ -1089,6 +1076,13 @@ return delegators +def rule_has_end(rrule): + + "Return whether 'rrule' defines an end." + + parameters = rrule and get_parameters(rrule) + return parameters and parameters.has_key("UNTIL") or parameters.has_key("COUNT") + def make_rule_period(start, duration, attr, tzid): """ @@ -1107,43 +1101,67 @@ return RecurringPeriod(start, end, tzid, "RRULE", attr) -def get_rule_periods(rrule, main_period, tzid, end, inclusive=False): +class RulePeriodCollection: + + "A collection of rule periods." + + def __init__(self, rrule, main_period, tzid, end, inclusive=False): - """ - Return periods for the given 'rrule', employing the 'main_period' and - 'tzid'. + """ + Initialise a period collection for the given 'rrule', employing the + 'main_period' and 'tzid'. + + The specified 'end' datetime indicates the end of the window for which + periods shall be computed. - The specified 'end' datetime indicates the end of the window for which - periods shall be computed. + If 'inclusive' is set to a true value, any period occurring at the 'end' + will be included. + """ - If 'inclusive' is set to a true value, any period occurring at the 'end' - will be included. - """ + self.rrule = rrule + self.start = main_period.get_start() + self.attr = main_period.get_start_attr() + self.duration = main_period.get_duration() - start = main_period.get_start() - attr = main_period.get_start_attr() - duration = main_period.get_duration() + parameters = rrule and get_parameters(rrule) + until = parameters.get("UNTIL") + + if until: + until_dt = to_timezone(get_datetime(until, attr), tzid) + end = end and min(until_dt, end) or until_dt + inclusive = True - parameters = rrule and get_parameters(rrule) - selector = get_rule(start, rrule) + self.tzid = tzid + self.end = end + self.inclusive = inclusive - until = parameters.get("UNTIL") + def __iter__(self): - if until: - until_dt = to_timezone(get_datetime(until, attr), tzid) - end = end and min(until_dt, end) or until_dt - inclusive = True + """ + Obtain period instances, starting from the main period. Since counting + must start from the first period, filtering from a start date must be + done after the instances have been obtained. + """ + + selector = get_rule(self.start, self.rrule) - # Obtain period instances, starting from the main period. Since counting - # must start from the first period, filtering from a start date must be - # done after the instances have been obtained. + return RulePeriodIterator(self.duration, self.attr, self.tzid, + selector.select(self.start, self.end, + self.inclusive)) + +class RulePeriodIterator: + + "An iterator over rule periods." - periods = [] + def __init__(self, duration, attr, tzid, iterator): + self.duration = duration + self.attr = attr + self.tzid = tzid + self.iterator = iterator - for recurrence_start in selector.materialise(start, end, inclusive): - periods.append(make_rule_period(recurrence_start, duration, attr, tzid)) - - return periods + def next(self): + recurrence_start = self.iterator.next() + return make_rule_period(recurrence_start, self.duration, self.attr, self.tzid) def get_periods(obj, tzid, start=None, end=None, inclusive=False): @@ -1161,7 +1179,6 @@ """ rrule = obj.get_value("RRULE") - parameters = rrule and get_parameters(rrule) # Use localised datetimes. @@ -1180,22 +1197,14 @@ # for the agent, with instances outside that period being considered # unchecked. - elif end or parameters and parameters.has_key("UNTIL") or parameters.has_key("COUNT"): - - # Define a selection period with a start point. The end will be handled - # in the materialisation process. - - selection_period = Period(start, None) - periods = [] + elif end or rule_has_end(rrule): - for period in get_rule_periods(rrule, main_period, obj_tzid or tzid, - end, inclusive): + # Filter periods using a start point. The end will be handled in the + # materialisation process. - # Filter out periods before the start. - - if period.within(selection_period): - periods.append(period) - + periods = filter(Period(start, None).wraps, + RulePeriodCollection(rrule, main_period, obj_tzid or + tzid, end, inclusive)) else: periods = [] diff -r 34e3f316f28f -r c32240f8019d imiptools/period.py --- a/imiptools/period.py Tue Oct 24 01:05:08 2017 +0200 +++ b/imiptools/period.py Tue Oct 24 18:57:49 2017 +0200 @@ -178,6 +178,10 @@ return start_point(self) >= start_point(other) and \ end_point(self) <= end_point(other) + def wraps(self, other): + return start_point(self) <= start_point(other) and \ + end_point(self) >= end_point(other) + def common(self, other): start = max(start_point(self), start_point(other)) end = min(end_point(self), end_point(other))