1.1 --- a/imiptools/data.py Tue Oct 24 19:13:28 2017 +0200
1.2 +++ b/imiptools/data.py Tue Oct 24 23:21:54 2017 +0200
1.3 @@ -19,7 +19,7 @@
1.4 this program. If not, see <http://www.gnu.org/licenses/>.
1.5 """
1.6
1.7 -from bisect import bisect_left
1.8 +from bisect import bisect_left, insort_left
1.9 from datetime import date, datetime, timedelta
1.10 from email.mime.text import MIMEText
1.11 from imiptools.dates import format_datetime, get_datetime, \
1.12 @@ -31,6 +31,7 @@
1.13 to_timezone, to_utc_datetime
1.14 from imiptools.freebusy import FreeBusyPeriod
1.15 from imiptools.period import Period, RecurringPeriod
1.16 +from itertools import ifilter
1.17 from vCalendar import iterwrite, parse, ParseError, to_dict, to_node
1.18 from vRecurrence import get_parameters, get_rule
1.19 import email.utils
1.20 @@ -1164,6 +1165,66 @@
1.21 recurrence_start = self.iterator.next()
1.22 return make_rule_period(recurrence_start, self.duration, self.attr, self.tzid)
1.23
1.24 +class MergingIterator:
1.25 +
1.26 + "An iterator merging ordered collections."
1.27 +
1.28 + def __init__(self, iterators):
1.29 +
1.30 + "Initialise an iterator merging 'iterators'."
1.31 +
1.32 + self.current = []
1.33 +
1.34 + # Populate an ordered collection of (value, iterator) pairs by obtaining
1.35 + # the first value from each iterator.
1.36 +
1.37 + for iterator in iterators:
1.38 + t = self.get_next(iterator)
1.39 + if t:
1.40 + self.current.append(t)
1.41 +
1.42 + self.current.sort()
1.43 +
1.44 + def __iter__(self):
1.45 + return self
1.46 +
1.47 + def get_next(self, iterator):
1.48 +
1.49 + """
1.50 + Return a (value, iterator) pair for 'iterator' or None if the iterator
1.51 + has been exhausted.
1.52 + """
1.53 +
1.54 + try:
1.55 + return (iterator.next(), iterator)
1.56 + except StopIteration:
1.57 + return None
1.58 +
1.59 + def next(self):
1.60 +
1.61 + """
1.62 + Return the next value in an ordered sequence, choosing it from one of
1.63 + the available iterators.
1.64 + """
1.65 +
1.66 + if not self.current:
1.67 + raise StopIteration
1.68 +
1.69 + # Obtain the current value and remove the (value, iterator) pair,
1.70 + # pending insertion of a new pair for the iterator.
1.71 +
1.72 + current, iterator = self.current[0]
1.73 + del self.current[0]
1.74 +
1.75 + # Get the next value, if any and insert the value and iterator into the
1.76 + # ordered collection.
1.77 +
1.78 + t = self.get_next(iterator)
1.79 + if t:
1.80 + insort_left(self.current, t)
1.81 +
1.82 + return current
1.83 +
1.84 def get_periods(obj, tzid, start=None, end=None, inclusive=False):
1.85
1.86 """
1.87 @@ -1191,7 +1252,7 @@
1.88 obj_tzid = obj.get_tzid()
1.89
1.90 if not rrule:
1.91 - periods = [main_period]
1.92 + rule_periods = iter([main_period])
1.93
1.94 # Recurrence rules create multiple instances to be checked.
1.95 # Conflicts may only be assessed within a period defined by policy
1.96 @@ -1203,21 +1264,21 @@
1.97 # Filter periods using a start point. The end will be handled in the
1.98 # materialisation process.
1.99
1.100 - periods = filter(Period(start, None).wraps,
1.101 - RulePeriodCollection(rrule, main_period, obj_tzid or
1.102 - tzid, end, inclusive))
1.103 + rule_periods = ifilter(Period(start, None).wraps,
1.104 + RulePeriodCollection(rrule, main_period, obj_tzid or
1.105 + tzid, end, inclusive))
1.106 else:
1.107 - periods = []
1.108 + rule_periods = iter([])
1.109
1.110 # Add recurrence dates.
1.111
1.112 rdates = obj.get_date_value_item_periods("RDATE", obj_tzid or tzid)
1.113 if rdates:
1.114 - periods += rdates
1.115 + rdates.sort()
1.116
1.117 # Return a sorted list of the periods.
1.118
1.119 - periods.sort()
1.120 + periods = list(MergingIterator([rule_periods, iter(rdates)]))
1.121
1.122 # Exclude exception dates.
1.123