1.1 --- a/imiptools/data.py Fri Oct 20 23:37:08 2017 +0200
1.2 +++ b/imiptools/data.py Tue Oct 24 20:33:02 2017 +0200
1.3 @@ -567,20 +567,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 @@ -1214,6 +1201,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 @@ -1232,47 +1226,73 @@
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 + The specified 'end' datetime indicates the end of the window for which
1.60 + periods shall be computed.
1.61
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 + If 'inclusive' is set to a true value, any period occurring at the 'end'
1.66 + will be included.
1.67 + """
1.68
1.69 - start = main_period.get_start()
1.70 - attr = main_period.get_start_attr()
1.71 - duration = main_period.get_duration()
1.72 + self.rrule = rrule
1.73 + self.main_period = main_period
1.74 + self.tzid = tzid
1.75
1.76 - parameters = rrule and get_parameters(rrule)
1.77 - selector = get_rule(start, rrule)
1.78 + parameters = rrule and get_parameters(rrule)
1.79 + until = parameters.get("UNTIL")
1.80 +
1.81 + # Any UNTIL qualifier changes the nature of the end of the collection.
1.82
1.83 - until = parameters.get("UNTIL")
1.84 + if until:
1.85 + attr = main_period.get_start_attr()
1.86 + until_dt = to_timezone(get_datetime(until, attr), tzid)
1.87 + self.end = end and min(until_dt, end) or until_dt
1.88 + self.inclusive = True
1.89 + else:
1.90 + self.end = end
1.91 + self.inclusive = inclusive
1.92
1.93 - if until:
1.94 - until_dt = to_timezone(get_datetime(until, attr), tzid)
1.95 - end = end and min(until_dt, end) or until_dt
1.96 - inclusive = True
1.97 + def __iter__(self):
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 + Obtain period instances, starting from the main period. Since counting
1.104 + must start from the first period, filtering from a start date must be
1.105 + done after the instances have been obtained.
1.106 + """
1.107 +
1.108 + start = self.main_period.get_start()
1.109 + selector = get_rule(start, self.rrule)
1.110
1.111 - periods = []
1.112 + return RulePeriodIterator(self.main_period, self.tzid,
1.113 + selector.select(start, self.end, self.inclusive))
1.114 +
1.115 +class RulePeriodIterator:
1.116 +
1.117 + "An iterator over rule periods."
1.118
1.119 - for recurrence_start in selector.materialise(start, end,
1.120 - parameters.get("COUNT"),
1.121 - parameters.get("BYSETPOS"),
1.122 - inclusive):
1.123 + def __init__(self, main_period, tzid, iterator):
1.124 + self.main_period = main_period
1.125 + self.attr = main_period.get_start_attr()
1.126 + self.duration = main_period.get_duration()
1.127 + self.tzid = tzid
1.128 + self.iterator = iterator
1.129
1.130 - periods.append(make_rule_period(recurrence_start, duration, attr, tzid))
1.131 + def next(self):
1.132 + recurrence_start = self.iterator.next()
1.133 + period = make_rule_period(recurrence_start, self.duration, self.attr, self.tzid)
1.134
1.135 - return periods
1.136 + # Use the main period where it occurs.
1.137 +
1.138 + return period == self.main_period and self.main_period or period
1.139
1.140 def get_periods(obj, start=None, end=None, inclusive=False):
1.141
1.142 @@ -1291,7 +1311,6 @@
1.143
1.144 tzid = obj.get_tzid()
1.145 rrule = obj.get_value("RRULE")
1.146 - parameters = rrule and get_parameters(rrule)
1.147
1.148 # Use localised datetimes.
1.149
1.150 @@ -1305,27 +1324,14 @@
1.151 # for the agent, with instances outside that period being considered
1.152 # unchecked.
1.153
1.154 - elif end or parameters and parameters.has_key("UNTIL") or parameters.has_key("COUNT"):
1.155 -
1.156 - # Define a selection period with a start point. The end will be handled
1.157 - # in the materialisation process.
1.158 -
1.159 - selection_period = Period(start, None)
1.160 - periods = []
1.161 -
1.162 - for period in get_rule_periods(rrule, main_period, tzid, end,
1.163 - inclusive):
1.164 + elif end or rule_has_end(rrule):
1.165
1.166 - # Use the main period where it occurs.
1.167 -
1.168 - if period == main_period:
1.169 - period = main_period
1.170 + # Filter periods using a start point. The end will be handled in the
1.171 + # materialisation process.
1.172
1.173 - # Filter out periods before the start.
1.174 -
1.175 - if period.within(selection_period):
1.176 - periods.append(period)
1.177 -
1.178 + periods = filter(Period(start, None).wraps,
1.179 + RulePeriodCollection(rrule, main_period, tzid, end,
1.180 + inclusive))
1.181 else:
1.182 periods = []
1.183