# HG changeset patch # User Paul Boddie # Date 1445967866 -3600 # Node ID 17b5b91e95cd40184f1d63c5839c7ea61354a624 # Parent 531b3975a2b20ebe7c6b7e09173082cc54bd0ebe Added before-start and after-end free periods to the invert_freebusy result. Added support for getting common periods from multiple period collections. Support endless durations where the start or end points of a period are not defined. Introduced an initialiser method to PeriodBase. Made start- and end-of-time instances hashable. Added a get_tzid method to FreeBusyPeriod. diff -r 531b3975a2b2 -r 17b5b91e95cd imiptools/period.py --- a/imiptools/period.py Tue Oct 27 17:17:46 2015 +0100 +++ b/imiptools/period.py Tue Oct 27 18:44:26 2015 +0100 @@ -87,6 +87,9 @@ def __nonzero__(self): return False + def __hash__(self): + return 0 + class EndOfTime(PointInTime): "A special value that compares later than other values." @@ -103,10 +106,39 @@ def __nonzero__(self): return False + def __hash__(self): + return 0 + +class Endless: + + "A special value indicating an endless period." + + def __cmp__(self, other): + if isinstance(other, Endless): + return 0 + else: + return 1 + + def __rcmp__(self, other): + return -self.__cmp__(other) + + def __nonzero__(self): + return True + class PeriodBase: "A basic period abstraction." + def __init__(self, start, end): + if isinstance(start, (date, PointInTime)): + self.start = start + else: + self.start = get_datetime(start) or StartOfTime() + if isinstance(end, (date, PointInTime)): + self.end = end + else: + self.end = get_datetime(end) or EndOfTime() + def as_tuple(self): return self.start, self.end @@ -133,6 +165,14 @@ return Comparable(ifnone(self.get_start_point(), StartOfTime())) >= Comparable(ifnone(other.get_start_point(), StartOfTime())) and \ Comparable(ifnone(self.get_end_point(), EndOfTime())) <= Comparable(ifnone(other.get_end_point(), EndOfTime())) + def common(self, other): + start = max(Comparable(ifnone(self.get_start_point(), StartOfTime())), Comparable(ifnone(other.get_start_point(), StartOfTime()))) + end = min(Comparable(ifnone(self.get_end_point(), EndOfTime())), Comparable(ifnone(other.get_end_point(), EndOfTime()))) + if start <= end: + return self.make_corrected(start.dt, end.dt) + else: + return None + def get_key(self): return self.get_start(), self.get_end() @@ -163,7 +203,12 @@ return self.end def get_duration(self): - return self.get_end_point() - self.get_start_point() + start = self.get_start_point() + end = self.get_end_point() + if start and end: + return end - start + else: + return Endless() class Period(PeriodBase): @@ -179,10 +224,7 @@ dates/datetimes. """ - if isinstance(start, (date, PointInTime)): self.start = start - else: self.start = get_datetime(start) or StartOfTime() - if isinstance(end, (date, PointInTime)): self.end = end - else: self.end = get_datetime(end) or EndOfTime() + PeriodBase.__init__(self, start, end) self.tzid = tzid self.origin = origin @@ -303,8 +345,7 @@ event proposals. """ - self.start = isinstance(start, datetime) and start or get_datetime(start) - self.end = isinstance(end, datetime) and end or get_datetime(end) + PeriodBase.__init__(self, start, end) self.uid = uid self.transp = transp self.recurrenceid = recurrenceid @@ -350,6 +391,9 @@ def __repr__(self): return "FreeBusyPeriod%r" % (self.as_tuple(),) + def get_tzid(self): + return "UTC" + # Period and event recurrence logic. def is_replaced(self, recurrences): @@ -375,6 +419,11 @@ return recurrence and self.get_start_point() == recurrence + # Value correction methods. + + def make_corrected(self, start, end): + return self.__class__(start, end) + class RecurringPeriod(Period): """ @@ -627,18 +676,59 @@ "Return the free periods from 'freebusy'." if not freebusy: - return None + return [FreeBusyPeriod(None, None)] + + # Coalesce periods that overlap or are adjacent. fb = coalesce_freebusy(freebusy) free = [] + + # Add a start-of-time period if appropriate. + + first = fb[0].get_start_point() + if first: + free.append(FreeBusyPeriod(None, first)) + start = fb[0].get_end_point() for period in fb[1:]: free.append(FreeBusyPeriod(start, period.get_start_point())) start = period.get_end_point() + # Add an end-of-time period if appropriate. + + if start: + free.append(FreeBusyPeriod(start, None)) + return free +def get_common_periods(all_freebusy): + + "Return common periods to all collections in the 'all_freebusy' list." + + if not all_freebusy: + return None + + common = all_freebusy[0] + + # Intersect periods with the current common set. + + for freebusy in all_freebusy[1:]: + new_common = [] + + # Find the overlapping periods and then obtain the regions that + # are common to both sets. + + for period in freebusy: + for overlapping in get_overlapping(common, period): + p = period.common(overlapping) + if p: + new_common.append(p) + + common = new_common + + return common + # Period layout. def get_scale(periods, tzid, view_period=None):