1.1 --- a/imiptools/data.py Sun Oct 18 23:58:02 2015 +0200
1.2 +++ b/imiptools/data.py Mon Oct 19 14:52:02 2015 +0200
1.3 @@ -129,6 +129,9 @@
1.4 def get_date_value_items(self, name, tzid=None):
1.5 return get_date_value_items(self.details, name, tzid)
1.6
1.7 + def get_date_value_item_periods(self, name, tzid=None):
1.8 + return get_date_value_item_periods(self.details, name, self.get_main_period(tzid).get_duration(), tzid)
1.9 +
1.10 def get_period_values(self, name, tzid=None):
1.11 return get_period_values(self.details, name, tzid)
1.12
1.13 @@ -475,24 +478,27 @@
1.14 self["SEQUENCE"] = [(str(int(sequence) + (increment and 1 or 0)), {})]
1.15 return sequence
1.16
1.17 - def update_exceptions(self, excluded):
1.18 + def update_exceptions(self, excluded, asserted):
1.19
1.20 """
1.21 Update the exceptions to any rule by applying the list of 'excluded'
1.22 - periods.
1.23 + periods. Where 'asserted' periods are provided, exceptions will be
1.24 + removed corresponding to those periods.
1.25 """
1.26
1.27 - to_exclude = set(excluded).difference(self.get_date_values("EXDATE") or [])
1.28 - if not to_exclude:
1.29 - return False
1.30 + old_exdates = self.get_date_value_item_periods("EXDATE")
1.31 + new_exdates = set(old_exdates)
1.32 + new_exdates.update(excluded)
1.33 + new_exdates.difference_update(asserted)
1.34
1.35 - if not self.has_key("EXDATE"):
1.36 + if not new_exdates:
1.37 + del self["EXDATE"]
1.38 + else:
1.39 self["EXDATE"] = []
1.40 + for p in new_exdates:
1.41 + self["EXDATE"].append(get_period_item(p.get_start(), p.get_end()))
1.42
1.43 - for p in to_exclude:
1.44 - self["EXDATE"].append(get_period_item(p.get_start(), p.get_end()))
1.45 -
1.46 - return True
1.47 + return set(old_exdates) != new_exdates
1.48
1.49 def correct_object(self, tzid, permitted_values):
1.50
1.51 @@ -720,6 +726,29 @@
1.52 else:
1.53 return None
1.54
1.55 +def get_date_value_item_periods(d, name, duration, tzid=None):
1.56 +
1.57 + """
1.58 + Obtain items from 'd' having the given 'name', where a single item yields
1.59 + potentially many values. The 'duration' must be provided to define the
1.60 + length of periods having only a start datetime. Return a list of periods
1.61 + corresponding to the property in 'd'.
1.62 + """
1.63 +
1.64 + items = get_date_value_items(d, name, tzid)
1.65 + if not items:
1.66 + return items
1.67 +
1.68 + periods = []
1.69 +
1.70 + for value, attr in items:
1.71 + if isinstance(value, tuple):
1.72 + periods.append(RecurringPeriod(value[0], value[1], tzid, name, attr))
1.73 + else:
1.74 + periods.append(RecurringPeriod(value, value + duration, tzid, name, attr))
1.75 +
1.76 + return periods
1.77 +
1.78 def get_period_values(d, name, tzid=None):
1.79
1.80 """
1.81 @@ -896,10 +925,6 @@
1.82
1.83 dtstart = main_period.get_start()
1.84 dtstart_attr = main_period.get_start_attr()
1.85 - dtend = main_period.get_end()
1.86 - dtend_attr = main_period.get_end_attr()
1.87 -
1.88 - duration = dtend - dtstart
1.89
1.90 # Attempt to get time zone details from the object, using the supplied zone
1.91 # only as a fallback.
1.92 @@ -928,7 +953,7 @@
1.93 for recurrence_start in selector.materialise(dtstart, end, parameters.get("COUNT"), parameters.get("BYSETPOS"), inclusive):
1.94 create = len(recurrence_start) == 3 and date or datetime
1.95 recurrence_start = to_timezone(create(*recurrence_start), obj_tzid)
1.96 - recurrence_end = recurrence_start + duration
1.97 + recurrence_end = recurrence_start + main_period.get_duration()
1.98 periods.append(RecurringPeriod(recurrence_start, recurrence_end, tzid, "RRULE", dtstart_attr))
1.99
1.100 else:
1.101 @@ -936,14 +961,9 @@
1.102
1.103 # Add recurrence dates.
1.104
1.105 - rdates = obj.get_date_value_items("RDATE", tzid)
1.106 -
1.107 + rdates = obj.get_date_value_item_periods("RDATE", tzid)
1.108 if rdates:
1.109 - for rdate, rdate_attr in rdates:
1.110 - if isinstance(rdate, tuple):
1.111 - periods.append(RecurringPeriod(rdate[0], rdate[1], tzid, "RDATE", rdate_attr))
1.112 - else:
1.113 - periods.append(RecurringPeriod(rdate, rdate + duration, tzid, "RDATE", rdate_attr))
1.114 + periods += rdates
1.115
1.116 # Return a sorted list of the periods.
1.117
1.118 @@ -951,14 +971,10 @@
1.119
1.120 # Exclude exception dates.
1.121
1.122 - exdates = obj.get_date_value_items("EXDATE", tzid)
1.123 + exdates = obj.get_date_value_item_periods("EXDATE", tzid)
1.124
1.125 if exdates:
1.126 - for exdate, exdate_attr in exdates:
1.127 - if isinstance(exdate, tuple):
1.128 - period = RecurringPeriod(exdate[0], exdate[1], tzid, "EXDATE", exdate_attr)
1.129 - else:
1.130 - period = RecurringPeriod(exdate, exdate + duration, tzid, "EXDATE", exdate_attr)
1.131 + for period in exdates:
1.132 i = bisect_left(periods, period)
1.133 while i < len(periods) and periods[i] == period:
1.134 del periods[i]