# HG changeset patch # User Paul Boddie # Date 1424976060 -3600 # Node ID 7edd0d9f6f13ca3fcdf9ad62feecbf6e858b4518 # Parent f24ecac37a457021c0324e922a854266f28b2a5c Added initial support for RDATE and EXDATE properties. diff -r f24ecac37a45 -r 7edd0d9f6f13 imiptools/data.py --- a/imiptools/data.py Thu Feb 26 19:38:14 2015 +0100 +++ b/imiptools/data.py Thu Feb 26 19:41:00 2015 +0100 @@ -59,6 +59,13 @@ def get_utc_datetime(self, name): return get_utc_datetime(self.details, name) + def get_item_datetimes(self, name): + items = get_item_datetime_items(self.details, name) + return items and [dt for dt, attr in items] + + def get_item_datetime_items(self, name): + return get_item_datetime_items(self.details, name) + def get_datetime(self, name): dt, attr = get_datetime_item(self.details, name) return dt @@ -248,6 +255,23 @@ def get_value(d, name): return get_values(d, name, False) +def get_item_datetime_items(d, name): + + """ + Return datetime items from 'd' having the given 'name', where a single item + yields potentially many datetime values, each employing the attributes given + for the principal item. + """ + + item = get_item(d, name) + if item: + values, attr = item + if not isinstance(values, list): + values = [values] + return [(get_datetime(value, attr), attr) for value in values] + else: + return None + def get_utc_datetime(d, name): t = get_datetime_item(d, name) if not t: @@ -322,40 +346,60 @@ to the given 'window_size' in days starting from the present moment. """ - # NOTE: Need also RDATE and EXDATE support. - rrule = obj.get_value("RRULE") - if not rrule: - return [(obj.get_datetime("DTSTART"), obj.get_datetime("DTEND"))] - # Use localised datetimes. dtstart, start_attr = obj.get_datetime_item("DTSTART") dtend, end_attr = obj.get_datetime_item("DTEND") - tzid = start_attr.get("TZID") or end_attr.get("TZID") or tzid # NOTE: Need also DURATION support. duration = dtend - dtstart - # 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 - # unchecked. + if not rrule: + periods = [(dtstart, dtend)] + else: + # 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 + # unchecked. + + window_end = to_timezone(datetime.now(), tzid) + timedelta(window_size) - window_end = to_timezone(datetime.now(), tzid) + timedelta(window_size) + selector = get_rule(dtstart, rrule) + parameters = get_parameters(rrule) + periods = [] + + for start in selector.materialise(dtstart, window_end, parameters.get("COUNT"), parameters.get("BYSETPOS")): + start = to_timezone(datetime(*start), tzid) + end = start + duration + periods.append((start, end)) + + # Add recurrence dates. - selector = get_rule(dtstart, rrule) - parameters = get_parameters(rrule) - periods = [] + periods = set(periods) + rdates = obj.get_item_datetimes("RDATE") + + if rdates: + for rdate in rdates: + periods.add((rdate, rdate + duration)) + + # Exclude exception dates. + + exdates = obj.get_item_datetimes("EXDATE") - for start in selector.materialise(dtstart, window_end, parameters.get("COUNT"), parameters.get("BYSETPOS")): - start = to_timezone(datetime(*start), tzid) - end = start + duration - periods.append((start, end)) + if exdates: + for exdate in exdates: + period = (exdate, exdate + duration) + if period in periods: + periods.remove(period) + # Return a sorted list of the periods. + + periods = list(periods) + periods.sort() return periods def get_periods_for_freebusy(obj, periods, tzid):