1.1 --- a/imiptools/period.py Tue Oct 27 17:17:46 2015 +0100
1.2 +++ b/imiptools/period.py Tue Oct 27 18:44:26 2015 +0100
1.3 @@ -87,6 +87,9 @@
1.4 def __nonzero__(self):
1.5 return False
1.6
1.7 + def __hash__(self):
1.8 + return 0
1.9 +
1.10 class EndOfTime(PointInTime):
1.11
1.12 "A special value that compares later than other values."
1.13 @@ -103,10 +106,39 @@
1.14 def __nonzero__(self):
1.15 return False
1.16
1.17 + def __hash__(self):
1.18 + return 0
1.19 +
1.20 +class Endless:
1.21 +
1.22 + "A special value indicating an endless period."
1.23 +
1.24 + def __cmp__(self, other):
1.25 + if isinstance(other, Endless):
1.26 + return 0
1.27 + else:
1.28 + return 1
1.29 +
1.30 + def __rcmp__(self, other):
1.31 + return -self.__cmp__(other)
1.32 +
1.33 + def __nonzero__(self):
1.34 + return True
1.35 +
1.36 class PeriodBase:
1.37
1.38 "A basic period abstraction."
1.39
1.40 + def __init__(self, start, end):
1.41 + if isinstance(start, (date, PointInTime)):
1.42 + self.start = start
1.43 + else:
1.44 + self.start = get_datetime(start) or StartOfTime()
1.45 + if isinstance(end, (date, PointInTime)):
1.46 + self.end = end
1.47 + else:
1.48 + self.end = get_datetime(end) or EndOfTime()
1.49 +
1.50 def as_tuple(self):
1.51 return self.start, self.end
1.52
1.53 @@ -133,6 +165,14 @@
1.54 return Comparable(ifnone(self.get_start_point(), StartOfTime())) >= Comparable(ifnone(other.get_start_point(), StartOfTime())) and \
1.55 Comparable(ifnone(self.get_end_point(), EndOfTime())) <= Comparable(ifnone(other.get_end_point(), EndOfTime()))
1.56
1.57 + def common(self, other):
1.58 + start = max(Comparable(ifnone(self.get_start_point(), StartOfTime())), Comparable(ifnone(other.get_start_point(), StartOfTime())))
1.59 + end = min(Comparable(ifnone(self.get_end_point(), EndOfTime())), Comparable(ifnone(other.get_end_point(), EndOfTime())))
1.60 + if start <= end:
1.61 + return self.make_corrected(start.dt, end.dt)
1.62 + else:
1.63 + return None
1.64 +
1.65 def get_key(self):
1.66 return self.get_start(), self.get_end()
1.67
1.68 @@ -163,7 +203,12 @@
1.69 return self.end
1.70
1.71 def get_duration(self):
1.72 - return self.get_end_point() - self.get_start_point()
1.73 + start = self.get_start_point()
1.74 + end = self.get_end_point()
1.75 + if start and end:
1.76 + return end - start
1.77 + else:
1.78 + return Endless()
1.79
1.80 class Period(PeriodBase):
1.81
1.82 @@ -179,10 +224,7 @@
1.83 dates/datetimes.
1.84 """
1.85
1.86 - if isinstance(start, (date, PointInTime)): self.start = start
1.87 - else: self.start = get_datetime(start) or StartOfTime()
1.88 - if isinstance(end, (date, PointInTime)): self.end = end
1.89 - else: self.end = get_datetime(end) or EndOfTime()
1.90 + PeriodBase.__init__(self, start, end)
1.91 self.tzid = tzid
1.92 self.origin = origin
1.93
1.94 @@ -303,8 +345,7 @@
1.95 event proposals.
1.96 """
1.97
1.98 - self.start = isinstance(start, datetime) and start or get_datetime(start)
1.99 - self.end = isinstance(end, datetime) and end or get_datetime(end)
1.100 + PeriodBase.__init__(self, start, end)
1.101 self.uid = uid
1.102 self.transp = transp
1.103 self.recurrenceid = recurrenceid
1.104 @@ -350,6 +391,9 @@
1.105 def __repr__(self):
1.106 return "FreeBusyPeriod%r" % (self.as_tuple(),)
1.107
1.108 + def get_tzid(self):
1.109 + return "UTC"
1.110 +
1.111 # Period and event recurrence logic.
1.112
1.113 def is_replaced(self, recurrences):
1.114 @@ -375,6 +419,11 @@
1.115
1.116 return recurrence and self.get_start_point() == recurrence
1.117
1.118 + # Value correction methods.
1.119 +
1.120 + def make_corrected(self, start, end):
1.121 + return self.__class__(start, end)
1.122 +
1.123 class RecurringPeriod(Period):
1.124
1.125 """
1.126 @@ -627,18 +676,59 @@
1.127 "Return the free periods from 'freebusy'."
1.128
1.129 if not freebusy:
1.130 - return None
1.131 + return [FreeBusyPeriod(None, None)]
1.132 +
1.133 + # Coalesce periods that overlap or are adjacent.
1.134
1.135 fb = coalesce_freebusy(freebusy)
1.136 free = []
1.137 +
1.138 + # Add a start-of-time period if appropriate.
1.139 +
1.140 + first = fb[0].get_start_point()
1.141 + if first:
1.142 + free.append(FreeBusyPeriod(None, first))
1.143 +
1.144 start = fb[0].get_end_point()
1.145
1.146 for period in fb[1:]:
1.147 free.append(FreeBusyPeriod(start, period.get_start_point()))
1.148 start = period.get_end_point()
1.149
1.150 + # Add an end-of-time period if appropriate.
1.151 +
1.152 + if start:
1.153 + free.append(FreeBusyPeriod(start, None))
1.154 +
1.155 return free
1.156
1.157 +def get_common_periods(all_freebusy):
1.158 +
1.159 + "Return common periods to all collections in the 'all_freebusy' list."
1.160 +
1.161 + if not all_freebusy:
1.162 + return None
1.163 +
1.164 + common = all_freebusy[0]
1.165 +
1.166 + # Intersect periods with the current common set.
1.167 +
1.168 + for freebusy in all_freebusy[1:]:
1.169 + new_common = []
1.170 +
1.171 + # Find the overlapping periods and then obtain the regions that
1.172 + # are common to both sets.
1.173 +
1.174 + for period in freebusy:
1.175 + for overlapping in get_overlapping(common, period):
1.176 + p = period.common(overlapping)
1.177 + if p:
1.178 + new_common.append(p)
1.179 +
1.180 + common = new_common
1.181 +
1.182 + return common
1.183 +
1.184 # Period layout.
1.185
1.186 def get_scale(periods, tzid, view_period=None):