1.1 --- a/imiptools/data.py Sun Oct 15 23:30:38 2017 +0200
1.2 +++ b/imiptools/data.py Mon Oct 16 18:37:33 2017 +0200
1.3 @@ -78,6 +78,11 @@
1.4 self.objtype, (self.details, self.attr) = fragment.items()[0]
1.5 self.set_tzid(tzid)
1.6
1.7 + # Modify the object with separate recurrences.
1.8 +
1.9 + self.modifying = []
1.10 + self.cancelling = []
1.11 +
1.12 def set_tzid(self, tzid):
1.13
1.14 """
1.15 @@ -87,7 +92,34 @@
1.16
1.17 self.tzid = tzid
1.18
1.19 + def set_modifying(self, modifying):
1.20 +
1.21 + """
1.22 + Set the 'modifying' objects affecting the periods provided by this
1.23 + object. Such modifications can only be performed on a parent object, not
1.24 + a specific recurrence object.
1.25 + """
1.26 +
1.27 + if not self.get_recurrenceid():
1.28 + self.modifying = modifying
1.29 +
1.30 + def set_cancelling(self, cancelling):
1.31 +
1.32 + """
1.33 + Set the 'cancelling' objects affecting the periods provided by this
1.34 + object. Such cancellations can only be performed on a parent object, not
1.35 + a specific recurrence object.
1.36 + """
1.37 +
1.38 + if not self.get_recurrenceid():
1.39 + self.cancelling = cancelling
1.40 +
1.41 + # Basic object identification.
1.42 +
1.43 def get_uid(self):
1.44 +
1.45 + "Return the universal identifier."
1.46 +
1.47 return self.get_value("UID")
1.48
1.49 def get_recurrenceid(self):
1.50 @@ -106,6 +138,7 @@
1.51
1.52 if not self.has_key("RECURRENCE-ID"):
1.53 return None
1.54 +
1.55 dt, attr = self.get_datetime_item("RECURRENCE-ID")
1.56
1.57 # Coerce any date to a UTC datetime if TZID was specified.
1.58 @@ -113,6 +146,7 @@
1.59 tzid = attr.get("TZID")
1.60 if tzid:
1.61 dt = to_timezone(to_datetime(dt, tzid), "UTC")
1.62 +
1.63 return format_datetime(dt)
1.64
1.65 def get_recurrence_start_point(self, recurrenceid):
1.66 @@ -327,35 +361,87 @@
1.67
1.68 return False
1.69
1.70 - def get_active_periods(self, recurrenceids, start=None, end=None):
1.71 + def get_updated_periods(self, start=None, end=None):
1.72
1.73 """
1.74 - Return all periods specified by this object that are not replaced by
1.75 - those defined by 'recurrenceids', using the fallback time zone to
1.76 - convert floating dates and datetimes, and using 'start' and 'end' to
1.77 - respectively indicate the start and end of the time window within which
1.78 - periods are considered.
1.79 + Return pairs of periods specified by this object and any modifying or
1.80 + cancelling objects, providing correspondences between the original
1.81 + period definitions and those applying after modifications and
1.82 + cancellations have been considered.
1.83 +
1.84 + The fallback time zone is used to convert floating dates and datetimes,
1.85 + and 'start' and 'end' respectively indicate the start and end of any
1.86 + time window within which periods are considered.
1.87 """
1.88
1.89 # Specific recurrences yield all specified periods.
1.90
1.91 - periods = self.get_periods(start, end)
1.92 + original = self.get_periods(start, end)
1.93
1.94 if self.get_recurrenceid():
1.95 - return periods
1.96 + return original
1.97
1.98 # Parent objects need to have their periods tested against redefined
1.99 # recurrences.
1.100
1.101 + modified = {}
1.102 +
1.103 + for obj in self.modifying:
1.104 + periods = obj.get_periods(start, end)
1.105 + if periods:
1.106 + modified[obj.get_recurrenceid()] = periods[0]
1.107 +
1.108 + cancelled = set()
1.109 +
1.110 + for obj in self.cancelling:
1.111 + cancelled.add(obj.get_recurrenceid())
1.112 +
1.113 + updated = []
1.114 +
1.115 + for p in original:
1.116 + recurrenceid = p.is_replaced(modified.keys())
1.117 +
1.118 + # Produce an original-to-modified correspondence, setting the origin
1.119 + # to distinguish the period from the main period.
1.120 +
1.121 + if recurrenceid:
1.122 + mp = modified.get(recurrenceid)
1.123 + if mp.origin == "DTSTART" and p.origin != "DTSTART":
1.124 + mp.origin = "DTSTART-RECUR"
1.125 + updated.append((p, mp))
1.126 + break
1.127 +
1.128 + # Produce an original-to-null correspondence where cancellation has
1.129 + # occurred.
1.130 +
1.131 + recurrenceid = p.is_replaced(cancelled)
1.132 +
1.133 + if recurrenceid:
1.134 + updated.append((p, None))
1.135 + break
1.136 +
1.137 + # Produce an identity correspondence where no modification or
1.138 + # cancellation has occurred.
1.139 +
1.140 + updated.append((p, p))
1.141 +
1.142 + return updated
1.143 +
1.144 + def get_active_periods(self, start=None, end=None):
1.145 +
1.146 + """
1.147 + Return all periods specified by this object that are not replaced by
1.148 + those defined by modifying or cancelling objects, using the fallback
1.149 + time zone to convert floating dates and datetimes, and using 'start' and
1.150 + 'end' to respectively indicate the start and end of the time window
1.151 + within which periods are considered.
1.152 + """
1.153 +
1.154 active = []
1.155
1.156 - for p in periods:
1.157 -
1.158 - # Subtract any recurrences from the free/busy details of a
1.159 - # parent object.
1.160 -
1.161 - if not p.is_replaced(recurrenceids):
1.162 - active.append(p)
1.163 + for old, new in self.get_updated_periods(start, end):
1.164 + if new:
1.165 + active.append(new)
1.166
1.167 return active
1.168