# HG changeset patch # User Paul Boddie # Date 1438463208 -7200 # Node ID 6aca8c28fadc894a1882fcb791203d802bf2c707 # Parent 174fad4efd22c5d5df17aa1dcdadf575ed18d326 Moved period value list retrieval and active period computation to the object abstraction. Introduced a simpler form of get_periods which returns only explicitly-specified periods or those within an explicitly-terminated window or sequence. Fixed string representations of the different period classes. diff -r 174fad4efd22 -r 6aca8c28fadc imiptools/data.py --- a/imiptools/data.py Sat Aug 01 22:24:43 2015 +0200 +++ b/imiptools/data.py Sat Aug 01 23:06:48 2015 +0200 @@ -124,6 +124,9 @@ def get_date_value_items(self, name, tzid=None): return get_date_value_items(self.details, name, tzid) + def get_period_values(self, name, tzid=None, period_type=None): + return get_period_values(self.details, name, tzid, period_type) + def get_datetime(self, name): t = get_datetime_item(self.details, name) if not t: return None @@ -171,16 +174,50 @@ # Computed results. - def get_periods(self, tzid, end): + def get_periods(self, tzid, end=None): """ Return periods defined by this object, employing the given 'tzid' where no time zone information is defined, and limiting the collection to a window of time with the given 'end'. + + If 'end' is omitted, only explicit recurrences and recurrences from + explicitly-terminated rules will be returned. """ return get_periods(self, tzid, end) + def get_active_periods(self, recurrenceids, tzid, end=None): + + """ + Return all periods specified by this object that are not replaced by + those defined by 'recurrenceids', using 'tzid' as a fallback time zone + to convert floating dates and datetimes, and using 'end' to indicate the + end of the time window within which periods are considered. + """ + + # Specific recurrences yield all specified periods. + + periods = self.get_periods(tzid, end) + + if self.get_recurrenceid(): + return periods + + # Parent objects need to have their periods tested against redefined + # recurrences. + + active = [] + + for p in periods: + + # Subtract any recurrences from the free/busy details of a + # parent object. + + if not is_replaced(p, recurrenceids, tzid): + active.append(p) + + return active + def get_tzid(self): """ @@ -191,7 +228,10 @@ if not self.has_key("DTSTART"): return None dtstart, dtstart_attr = self.get_datetime_item("DTSTART") - dtend, dtend_attr = self.get_datetime_item("DTEND") + if self.has_key("DTEND"): + dtend, dtend_attr = self.get_datetime_item("DTEND") + else: + dtend_attr = None return get_tzid(dtstart_attr, dtend_attr) def is_shared(self): @@ -441,6 +481,22 @@ else: return None +def get_period_values(d, name, tzid=None, period_type=None): + + """ + Return period values from 'd' for the given property 'name', using 'tzid' + where specified to indicate the time zone, and 'period_type' to indicate a + particular period type to be used when creating period instances. + """ + + values = [] + for value, attr in get_items(d, name) or []: + if not attr.has_key("TZID") and tzid: + attr["TZID"] = tzid + start, end = get_period(value, attr) + values.append((period_type or Period)(start, end, tzid=tzid)) + return values + def get_utc_datetime(d, name, date_tzid=None): """ @@ -524,15 +580,18 @@ return is_same_sequence and partstat_set or not is_old_sequence -def get_periods(obj, tzid, window_end, inclusive=False): +def get_periods(obj, tzid, end=None, inclusive=False): """ Return periods for the given object 'obj', employing the given 'tzid' where no time zone information is available (for whole day events, for example), - confining materialised periods to before the given 'window_end' datetime. + confining materialised periods to before the given 'end' datetime. - If 'inclusive' is set to a true value, any period occurring at the - 'window_end' will be included. + If 'end' is omitted, only explicit recurrences and recurrences from + explicitly-terminated rules will be returned. + + If 'inclusive' is set to a true value, any period occurring at the 'end' + will be included. """ rrule = obj.get_value("RRULE") @@ -558,7 +617,9 @@ if not rrule: periods = [RecurringPeriod(dtstart, dtend, tzid, "DTSTART", dtstart_attr, dtend_attr)] - else: + + elif end or parameters.has_key("UNTIL") or parameters.has_key("COUNT"): + # Recurrence rules create multiple instances to be checked. # Conflicts may only be assessed within a period defined by policy # for the agent, with instances outside that period being considered @@ -570,14 +631,14 @@ until = parameters.get("UNTIL") if until: - window_end = min(to_timezone(get_datetime(until, dtstart_attr), tzid), window_end) + end = min(to_timezone(get_datetime(until, dtstart_attr), tzid), end) inclusive = True - for start in selector.materialise(dtstart, window_end, parameters.get("COUNT"), parameters.get("BYSETPOS"), inclusive): - create = len(start) == 3 and date or datetime - start = to_timezone(create(*start), tzid) - end = start + duration - periods.append(RecurringPeriod(start, end, tzid, "RRULE")) + for recurrence_start in selector.materialise(dtstart, end, parameters.get("COUNT"), parameters.get("BYSETPOS"), inclusive): + create = len(recurrence_start) == 3 and date or datetime + recurrence_start = to_timezone(create(*recurrence_start), tzid) + recurrence_end = recurrence_start + duration + periods.append(RecurringPeriod(recurrence_start, recurrence_end, tzid, "RRULE")) # Add recurrence dates. diff -r 174fad4efd22 -r 6aca8c28fadc imiptools/dates.py --- a/imiptools/dates.py Sat Aug 01 22:24:43 2015 +0200 +++ b/imiptools/dates.py Sat Aug 01 23:06:48 2015 +0200 @@ -160,7 +160,7 @@ format, using the 'attr' mapping (if specified) to control the conversion. """ - if not value or attr and attr.get("VALUE") != "PERIOD": + if not value or attr and attr.get("VALUE") and attr.get("VALUE") != "PERIOD": return None t = value.split("/") diff -r 174fad4efd22 -r 6aca8c28fadc imiptools/handlers/person.py --- a/imiptools/handlers/person.py Sat Aug 01 22:24:43 2015 +0200 +++ b/imiptools/handlers/person.py Sat Aug 01 23:06:48 2015 +0200 @@ -128,18 +128,7 @@ if not senders: return - freebusy = [] - - for value in self.obj.get_values("FREEBUSY") or []: - if not isinstance(value, list): - value = [value] - for v in value: - try: - start, end = v.split("/", 1) - freebusy.append(FreeBusyPeriod(start, end)) - except ValueError: - pass - + freebusy = self.obj.get_period_values("FREEBUSY", period_type=FreeBusyPeriod) dtstart = self.obj.get_datetime("DTSTART") dtend = self.obj.get_datetime("DTEND") period = Period(dtstart, dtend, self.get_tzid()) diff -r 174fad4efd22 -r 6aca8c28fadc imiptools/period.py --- a/imiptools/period.py Sat Aug 01 22:24:43 2015 +0200 +++ b/imiptools/period.py Sat Aug 01 23:06:48 2015 +0200 @@ -72,7 +72,7 @@ return self.get_start(), self.get_end() def __repr__(self): - return "Period(%r)" % (self.as_tuple(),) + return "Period%r" % (self.as_tuple(),) # Datetime metadata methods. @@ -131,8 +131,8 @@ null = lambda x: (strings_only and [""] or [x])[0] return ( - format_datetime(self.get_start_point()), - format_datetime(self.get_end_point()), + strings_only and format_datetime(self.get_start_point()) or self.start, + strings_only and format_datetime(self.get_end_point()) or self.end, self.uid or null(self.uid), self.transp or strings_only and "OPAQUE" or None, self.recurrenceid or null(self.recurrenceid), @@ -157,7 +157,7 @@ return self.uid, self.recurrenceid, self.get_start() def __repr__(self): - return "FreeBusyPeriod(%r)" % (self.as_tuple(),) + return "FreeBusyPeriod%r" % (self.as_tuple(),) class RecurringPeriod(Period): @@ -181,7 +181,7 @@ return self.start, self.end, self.tzid, self.origin, self.start_attr, self.end_attr def __repr__(self): - return "RecurringPeriod(%r)" % (self.as_tuple(),) + return "RecurringPeriod%r" % (self.as_tuple(),) # Period and event recurrence logic. diff -r 174fad4efd22 -r 6aca8c28fadc imipweb/resource.py --- a/imipweb/resource.py Sat Aug 01 22:24:43 2015 +0200 +++ b/imipweb/resource.py Sat Aug 01 23:06:48 2015 +0200 @@ -128,32 +128,34 @@ return self.requests def _get_request_summary(self): + + "Return a list of periods comprising the request summary." + summary = [] + for uid, recurrenceid in self._get_requests(): obj = self.get_stored_object(uid, recurrenceid) if obj: - periods = obj.get_periods(self.get_tzid(), self.get_window_end()) recurrenceids = self._get_recurrences(uid) - # Convert the periods to more substantial free/busy items. + # Obtain only active periods, not those replaced by redefined + # recurrences. - for p in periods: + for p in obj.get_active_periods(recurrenceids, self.get_tzid(), self.get_window_end()): - # Subtract any recurrences from the free/busy details of a - # parent object. + # Convert the periods to more substantial free/busy items. - if recurrenceid or not self.is_replaced(p, recurrenceids): - summary.append( - FreeBusyPeriod( - p.get_start(), - p.get_end(), - uid, - obj.get_value("TRANSP"), - recurrenceid, - obj.get_value("SUMMARY"), - obj.get_value("ORGANIZER"), - p.get_tzid() - )) + summary.append( + FreeBusyPeriod( + p.get_start(), + p.get_end(), + uid, + obj.get_value("TRANSP"), + recurrenceid, + obj.get_value("SUMMARY"), + obj.get_value("ORGANIZER"), + p.get_tzid() + )) return summary # Period and recurrence testing.