1.1 --- a/imiptools/data.py Tue Oct 24 01:05:08 2017 +0200
1.2 +++ b/imiptools/data.py Tue Oct 24 18:57:49 2017 +0200
1.3 @@ -451,20 +451,7 @@
1.4 "Return whether this object may recur indefinitely."
1.5
1.6 rrule = self.get_value("RRULE")
1.7 - parameters = rrule and get_parameters(rrule)
1.8 - until = parameters and parameters.get("UNTIL")
1.9 - count = parameters and parameters.get("COUNT")
1.10 -
1.11 - # Non-recurring periods or constrained recurrences.
1.12 -
1.13 - if not rrule or until or count:
1.14 - return False
1.15 -
1.16 - # Unconstrained recurring periods will always lie beyond any specified
1.17 - # datetime.
1.18 -
1.19 - else:
1.20 - return True
1.21 + return rrule and not rule_has_end(rrule)
1.22
1.23 # Modification methods.
1.24
1.25 @@ -1089,6 +1076,13 @@
1.26
1.27 return delegators
1.28
1.29 +def rule_has_end(rrule):
1.30 +
1.31 + "Return whether 'rrule' defines an end."
1.32 +
1.33 + parameters = rrule and get_parameters(rrule)
1.34 + return parameters and parameters.has_key("UNTIL") or parameters.has_key("COUNT")
1.35 +
1.36 def make_rule_period(start, duration, attr, tzid):
1.37
1.38 """
1.39 @@ -1107,43 +1101,67 @@
1.40
1.41 return RecurringPeriod(start, end, tzid, "RRULE", attr)
1.42
1.43 -def get_rule_periods(rrule, main_period, tzid, end, inclusive=False):
1.44 +class RulePeriodCollection:
1.45 +
1.46 + "A collection of rule periods."
1.47 +
1.48 + def __init__(self, rrule, main_period, tzid, end, inclusive=False):
1.49
1.50 - """
1.51 - Return periods for the given 'rrule', employing the 'main_period' and
1.52 - 'tzid'.
1.53 + """
1.54 + Initialise a period collection for the given 'rrule', employing the
1.55 + 'main_period' and 'tzid'.
1.56 +
1.57 + The specified 'end' datetime indicates the end of the window for which
1.58 + periods shall be computed.
1.59
1.60 - The specified 'end' datetime indicates the end of the window for which
1.61 - periods shall be computed.
1.62 + If 'inclusive' is set to a true value, any period occurring at the 'end'
1.63 + will be included.
1.64 + """
1.65
1.66 - If 'inclusive' is set to a true value, any period occurring at the 'end'
1.67 - will be included.
1.68 - """
1.69 + self.rrule = rrule
1.70 + self.start = main_period.get_start()
1.71 + self.attr = main_period.get_start_attr()
1.72 + self.duration = main_period.get_duration()
1.73
1.74 - start = main_period.get_start()
1.75 - attr = main_period.get_start_attr()
1.76 - duration = main_period.get_duration()
1.77 + parameters = rrule and get_parameters(rrule)
1.78 + until = parameters.get("UNTIL")
1.79 +
1.80 + if until:
1.81 + until_dt = to_timezone(get_datetime(until, attr), tzid)
1.82 + end = end and min(until_dt, end) or until_dt
1.83 + inclusive = True
1.84
1.85 - parameters = rrule and get_parameters(rrule)
1.86 - selector = get_rule(start, rrule)
1.87 + self.tzid = tzid
1.88 + self.end = end
1.89 + self.inclusive = inclusive
1.90
1.91 - until = parameters.get("UNTIL")
1.92 + def __iter__(self):
1.93
1.94 - if until:
1.95 - until_dt = to_timezone(get_datetime(until, attr), tzid)
1.96 - end = end and min(until_dt, end) or until_dt
1.97 - inclusive = True
1.98 + """
1.99 + Obtain period instances, starting from the main period. Since counting
1.100 + must start from the first period, filtering from a start date must be
1.101 + done after the instances have been obtained.
1.102 + """
1.103 +
1.104 + selector = get_rule(self.start, self.rrule)
1.105
1.106 - # Obtain period instances, starting from the main period. Since counting
1.107 - # must start from the first period, filtering from a start date must be
1.108 - # done after the instances have been obtained.
1.109 + return RulePeriodIterator(self.duration, self.attr, self.tzid,
1.110 + selector.select(self.start, self.end,
1.111 + self.inclusive))
1.112 +
1.113 +class RulePeriodIterator:
1.114 +
1.115 + "An iterator over rule periods."
1.116
1.117 - periods = []
1.118 + def __init__(self, duration, attr, tzid, iterator):
1.119 + self.duration = duration
1.120 + self.attr = attr
1.121 + self.tzid = tzid
1.122 + self.iterator = iterator
1.123
1.124 - for recurrence_start in selector.materialise(start, end, inclusive):
1.125 - periods.append(make_rule_period(recurrence_start, duration, attr, tzid))
1.126 -
1.127 - return periods
1.128 + def next(self):
1.129 + recurrence_start = self.iterator.next()
1.130 + return make_rule_period(recurrence_start, self.duration, self.attr, self.tzid)
1.131
1.132 def get_periods(obj, tzid, start=None, end=None, inclusive=False):
1.133
1.134 @@ -1161,7 +1179,6 @@
1.135 """
1.136
1.137 rrule = obj.get_value("RRULE")
1.138 - parameters = rrule and get_parameters(rrule)
1.139
1.140 # Use localised datetimes.
1.141
1.142 @@ -1180,22 +1197,14 @@
1.143 # for the agent, with instances outside that period being considered
1.144 # unchecked.
1.145
1.146 - elif end or parameters and parameters.has_key("UNTIL") or parameters.has_key("COUNT"):
1.147 -
1.148 - # Define a selection period with a start point. The end will be handled
1.149 - # in the materialisation process.
1.150 -
1.151 - selection_period = Period(start, None)
1.152 - periods = []
1.153 + elif end or rule_has_end(rrule):
1.154
1.155 - for period in get_rule_periods(rrule, main_period, obj_tzid or tzid,
1.156 - end, inclusive):
1.157 + # Filter periods using a start point. The end will be handled in the
1.158 + # materialisation process.
1.159
1.160 - # Filter out periods before the start.
1.161 -
1.162 - if period.within(selection_period):
1.163 - periods.append(period)
1.164 -
1.165 + periods = filter(Period(start, None).wraps,
1.166 + RulePeriodCollection(rrule, main_period, obj_tzid or
1.167 + tzid, end, inclusive))
1.168 else:
1.169 periods = []
1.170