1.1 --- a/imiptools/period.py Sat Mar 28 00:52:14 2015 +0100
1.2 +++ b/imiptools/period.py Sat Mar 28 00:55:12 2015 +0100
1.3 @@ -23,6 +23,64 @@
1.4 from datetime import datetime, timedelta
1.5 from imiptools.dates import get_datetime, get_start_of_day, to_timezone
1.6
1.7 +class Period:
1.8 +
1.9 + "A basic period abstraction."
1.10 +
1.11 + def __init__(self, start, end=None):
1.12 + self.start = start
1.13 + self.end = end
1.14 +
1.15 + def as_tuple(self):
1.16 + return self.start, self.end
1.17 +
1.18 + def __hash__(self):
1.19 + return hash((self.start, self.end))
1.20 +
1.21 + def __cmp__(self, other):
1.22 + if isinstance(other, Period):
1.23 + return cmp((self.start, self.end), (other.start, other.end))
1.24 + else:
1.25 + return 1
1.26 +
1.27 + def get_key(self):
1.28 + return self.start, self.end
1.29 +
1.30 + def __repr__(self):
1.31 + return "Period(%r, %r)" % (self.start, self.end)
1.32 +
1.33 +class FreeBusyPeriod(Period):
1.34 +
1.35 + "A free/busy record abstraction."
1.36 +
1.37 + def __init__(self, start, end=None, uid=None, transp=None, recurrenceid=None, summary=None, organiser=None):
1.38 + Period.__init__(self, start, end)
1.39 + self.uid = uid
1.40 + self.transp = transp
1.41 + self.recurrenceid = recurrenceid
1.42 + self.summary = summary
1.43 + self.organiser = organiser
1.44 +
1.45 + def as_tuple(self):
1.46 + return self.start, self.end, self.uid, self.transp, self.recurrenceid, self.summary, self.organiser
1.47 +
1.48 + def __hash__(self):
1.49 + return hash((self.start, self.end, self.uid))
1.50 +
1.51 + def __cmp__(self, other):
1.52 + if isinstance(other, FreeBusyPeriod):
1.53 + return cmp((self.start, self.end, self.uid), (other.start, other.end, other.uid))
1.54 + else:
1.55 + return Period.__cmp__(self, other)
1.56 +
1.57 + def get_key(self):
1.58 + return self.uid, self.recurrenceid, self.start
1.59 +
1.60 + def __repr__(self):
1.61 + return "FreeBusyPeriod(%r, %r, %r, %r, %r, %r, %r)" % (
1.62 + self.start, self.end, self.uid, self.transp, self.recurrenceid,
1.63 + self.summary, self.organiser)
1.64 +
1.65 # Time management with datetime strings in the UTC time zone.
1.66
1.67 def can_schedule(freebusy, periods, uid, recurrenceid):
1.68 @@ -33,8 +91,7 @@
1.69 """
1.70
1.71 for conflict in have_conflict(freebusy, periods, True):
1.72 - start, end, found_uid, found_transp, found_recurrenceid = conflict[:5]
1.73 - if found_uid != uid and found_recurrenceid != recurrenceid:
1.74 + if conflict.uid != uid and conflict.recurrenceid != recurrenceid:
1.75 return False
1.76
1.77 return True
1.78 @@ -47,12 +104,12 @@
1.79 set to a true value.
1.80 """
1.81
1.82 - conflicts = []
1.83 - for start, end in periods:
1.84 - overlapping = period_overlaps(freebusy, (start, end), get_conflicts)
1.85 + conflicts = set()
1.86 + for p in periods:
1.87 + overlapping = period_overlaps(freebusy, p, get_conflicts)
1.88 if overlapping:
1.89 if get_conflicts:
1.90 - conflicts += overlapping
1.91 + conflicts.update(overlapping)
1.92 else:
1.93 return True
1.94
1.95 @@ -76,8 +133,8 @@
1.96
1.97 i = 0
1.98 while i < len(freebusy):
1.99 - t = freebusy[i]
1.100 - if len(t) >= 5 and t[2] == uid and t[4] == recurrenceid:
1.101 + fb = freebusy[i]
1.102 + if fb.uid == uid and fb.recurrenceid == recurrenceid:
1.103 del freebusy[i]
1.104 else:
1.105 i += 1
1.106 @@ -94,10 +151,10 @@
1.107
1.108 i = 0
1.109 while i < len(freebusy):
1.110 - t = freebusy[i]
1.111 - if len(t) >= 5 and t[2] == uid and t[4] and (
1.112 + fb = freebusy[i]
1.113 + if fb.uid == uid and fb.recurrenceid and (
1.114 recurrenceids is None or
1.115 - recurrenceids is not None and t[4] not in recurrenceids
1.116 + recurrenceids is not None and fb.recurrenceid not in recurrenceids
1.117 ):
1.118 del freebusy[i]
1.119 else:
1.120 @@ -112,18 +169,18 @@
1.121 as a reference to the originally-defined occurrence.
1.122 """
1.123
1.124 - found = bisect_left(freebusy, (recurrenceid,))
1.125 + found = bisect_left(freebusy, Period(recurrenceid))
1.126 while found < len(freebusy):
1.127 - start, end, _uid, transp, _recurrenceid = freebusy[found][:5]
1.128 + fb = freebusy[found]
1.129
1.130 # Stop looking if the start no longer matches the recurrence identifier.
1.131
1.132 - if start != recurrenceid:
1.133 + if fb.start != recurrenceid:
1.134 return
1.135
1.136 # If the period belongs to the parent object, remove it and return.
1.137
1.138 - if not _recurrenceid and uid == _uid:
1.139 + if not fb.recurrenceid and uid == fb.uid:
1.140 del freebusy[found]
1.141 break
1.142
1.143 @@ -138,28 +195,25 @@
1.144 'period'.
1.145 """
1.146
1.147 - dtstart, dtend = period[:2]
1.148 -
1.149 # Find the range of periods potentially overlapping the period in the
1.150 # free/busy collection.
1.151
1.152 - last = bisect_right(freebusy, (dtend,))
1.153 + last = bisect_right(freebusy, Period(period.end))
1.154 startpoints = freebusy[:last]
1.155
1.156 # Find the range of periods potentially overlapping the period in a version
1.157 # of the free/busy collection sorted according to end datetimes.
1.158
1.159 - endpoints = [((fb[1], fb[0]) + fb[2:]) for fb in startpoints]
1.160 + endpoints = [(fb.end, fb.start, fb) for fb in startpoints]
1.161 endpoints.sort()
1.162 - first = bisect_left(endpoints, (dtstart,))
1.163 + first = bisect_left(endpoints, (period.start,))
1.164 endpoints = endpoints[first:]
1.165
1.166 overlapping = set()
1.167
1.168 - for fb in endpoints:
1.169 - end, start = fb[:2]
1.170 - if end > dtstart and start < dtend:
1.171 - overlapping.add((start, end) + fb[2:])
1.172 + for end, start, fb in endpoints:
1.173 + if end > period.start and start < period.end:
1.174 + overlapping.add(fb)
1.175
1.176 overlapping = list(overlapping)
1.177 overlapping.sort()
1.178 @@ -207,55 +261,49 @@
1.179
1.180 "Convert 'periods' to use datetime objects employing the given 'tzid'."
1.181
1.182 - l = []
1.183 -
1.184 - for t in periods:
1.185 - start, end = t[:2]
1.186 + for p in periods:
1.187
1.188 # NOTE: This only really works if the datetimes are UTC already.
1.189 # NOTE: Since the periods should originate from the free/busy data,
1.190 # NOTE: and since that data should employ UTC times, this should not be
1.191 # NOTE: an immediate problem.
1.192
1.193 - start = get_datetime(start)
1.194 - end = get_datetime(end)
1.195 + start = get_datetime(p.start)
1.196 + end = get_datetime(p.end)
1.197
1.198 start = isinstance(start, datetime) and to_timezone(start, tzid) or get_start_of_day(start, tzid)
1.199 end = isinstance(end, datetime) and to_timezone(end, tzid) or get_start_of_day(end, tzid)
1.200
1.201 - l.append((start, end) + tuple(t[2:]))
1.202 -
1.203 - return l
1.204 + p.start = start
1.205 + p.end = end
1.206
1.207 def get_scale(periods):
1.208
1.209 """
1.210 - Return an ordered time scale from the given list 'periods', with the first
1.211 - two elements of each tuple being start and end times.
1.212 + Return an ordered time scale from the given list of 'periods'.
1.213
1.214 The given 'tzid' is used to make sure that the times are defined according
1.215 to the chosen time zone.
1.216
1.217 The returned scale is a mapping from time to (starting, ending) tuples,
1.218 - where starting and ending are collections of tuples from 'periods'.
1.219 + where starting and ending are collections of periods.
1.220 """
1.221
1.222 scale = {}
1.223
1.224 - for t in periods:
1.225 - start, end = t[:2]
1.226 + for p in periods:
1.227
1.228 # Add a point and this event to the starting list.
1.229
1.230 - if not scale.has_key(start):
1.231 - scale[start] = [], []
1.232 - scale[start][0].append(t)
1.233 + if not scale.has_key(p.start):
1.234 + scale[p.start] = [], []
1.235 + scale[p.start][0].append(p)
1.236
1.237 # Add a point and this event to the ending list.
1.238
1.239 - if not scale.has_key(end):
1.240 - scale[end] = [], []
1.241 - scale[end][1].append(t)
1.242 + if not scale.has_key(p.end):
1.243 + scale[p.end] = [], []
1.244 + scale[p.end][1].append(p)
1.245
1.246 return scale
1.247
1.248 @@ -307,8 +355,7 @@
1.249 Return an ordered list of time slots from the given 'scale'.
1.250
1.251 Each slot is a tuple containing details of a point in time for the start of
1.252 - the slot, together with a list of parallel event tuples, each tuple
1.253 - containing the original details of an event.
1.254 + the slot, together with a list of parallel event periods.
1.255
1.256 Each point in time is described as a Point representing the actual point in
1.257 time together with an indicator of the nature of the point in time (as a
1.258 @@ -473,41 +520,15 @@
1.259 spans = {}
1.260
1.261 for _point, active in slots:
1.262 - for t in active:
1.263 - if t and len(t) >= 2:
1.264 - start, end, uid, recurrenceid, summary, organiser, key = get_freebusy_details(t)
1.265 -
1.266 - start_slot = bisect_left(points, start)
1.267 - end_slot = bisect_left(points, end)
1.268 + for p in active:
1.269 + if p:
1.270 + key = p.get_key()
1.271 + start_slot = bisect_left(points, p.start)
1.272 + end_slot = bisect_left(points, p.end)
1.273 spans[key] = end_slot - start_slot
1.274
1.275 return spans
1.276
1.277 -def get_freebusy_details(t):
1.278 -
1.279 - """
1.280 - Return a tuple of the form (start, end, uid, recurrenceid, summary,
1.281 - organiser, key) from 't'.
1.282 - """
1.283 -
1.284 - # Handle both complete free/busy details...
1.285 -
1.286 - if len(t) >= 7:
1.287 - start, end, uid, transp, recurrenceid, summary, organiser = t[:7]
1.288 - key = uid, recurrenceid, start
1.289 -
1.290 - # ...and published details without specific event details.
1.291 -
1.292 - else:
1.293 - start, end = t[:2]
1.294 - uid = None
1.295 - recurrenceid = None
1.296 - summary = None
1.297 - organiser = None
1.298 - key = (start, end)
1.299 -
1.300 - return start, end, uid, recurrenceid, summary, organiser, key
1.301 -
1.302 def update_freebusy(freebusy, periods, transp, uid, recurrenceid, summary, organiser):
1.303
1.304 """
1.305 @@ -517,7 +538,7 @@
1.306
1.307 remove_period(freebusy, uid, recurrenceid)
1.308
1.309 - for start, end in periods:
1.310 - insert_period(freebusy, (start, end, uid, transp, recurrenceid, summary, organiser))
1.311 + for p in periods:
1.312 + insert_period(freebusy, FreeBusyPeriod(p.start, p.end, uid, transp, recurrenceid, summary, organiser))
1.313
1.314 # vim: tabstop=4 expandtab shiftwidth=4