def testEventIndex(self): """Make sure eventsInRange works.""" daysEvents = list(Calendar.eventsInRange(self.view, self.midnight, self.midnight + timedelta(1))) self.assertEqual(daysEvents[0], self.pacificEvent) self.assertEqual(daysEvents[1], self.floatingEvent) self.assertEqual(daysEvents[2], self.hawaiiEvent)
def itemsToFreeBusy(view, start, end, calname = None): """ Create FREEBUSY components corresponding to all events between start and end. """ all = schema.ns("osaf.pim", view).allCollection normal = Calendar.eventsInRange(view, start, end, all) recurring = Calendar.recurringEventsInRange(view, start, end, all) events = Calendar._sortEvents(itertools.chain(normal, recurring), attrName='effectiveStartTime') def toUTC(dt): if dt < start: dt = start elif dt > end: dt = end return translateToTimezone(dt, view.tzinfo.UTC) cal = vobject.iCalendar() if calname is not None: cal.add('x-wr-calname').value = calname vfree = cal.add('vfreebusy') vfree.add('dtstart').value = toUTC(start) vfree.add('dtend').value = toUTC(end) def addFB(event): free = vfree.add('freebusy') free.fbtype_param = transparencyMap[event.transparency] return free free = None for event in events: # ignore anytime events, events with no duration, and fyi events if (event.transparency == 'fyi' or ((event.anyTime or event.duration == datetime.timedelta(0)) and not event.allDay)): continue if free is None or free.fbtype_param != \ transparencyMap[event.transparency]: free = addFB(event) free.value = [[toUTC(event.effectiveStartTime), event.effectiveEndTime]] else: # compress freebusy blocks if possible if event.effectiveStartTime <= free.value[-1][1]: if event.effectiveEndTime > free.value[-1][1]: free.value[-1][1] = event.effectiveEndTime else: free.value.append([toUTC(event.effectiveStartTime), event.effectiveEndTime]) # once upon a time serialize would convert (date, date) to (date, period) # but no longer. So there's no need to serialize # vfree.serialize() return cal
def testReorderFloating(self): """Changes to floating time should cause events to be reindexed.""" self.tzInfoItem.default = self.eastern daysEvents = list(Calendar.eventsInRange(self.view, self.midnight, self.midnight + timedelta(1))) #print [i.startTime for i in daysEvents] self.assertEqual(daysEvents[0], self.easternEvent) self.assertEqual(daysEvents[1], self.floatingEvent) self.assertEqual(daysEvents[2], self.pacificEvent) self.assertEqual(daysEvents[3], self.hawaiiEvent)
def itemsToFreeBusy(view, start, end, calname=None): """ Create FREEBUSY components corresponding to all events between start and end. """ all = schema.ns("osaf.pim", view).allCollection normal = Calendar.eventsInRange(view, start, end, all) recurring = Calendar.recurringEventsInRange(view, start, end, all) events = Calendar._sortEvents(itertools.chain(normal, recurring), attrName="effectiveStartTime") def toUTC(dt): if dt < start: dt = start elif dt > end: dt = end return translateToTimezone(dt, view.tzinfo.UTC) cal = vobject.iCalendar() if calname is not None: cal.add("x-wr-calname").value = calname vfree = cal.add("vfreebusy") vfree.add("dtstart").value = toUTC(start) vfree.add("dtend").value = toUTC(end) def addFB(event): free = vfree.add("freebusy") free.fbtype_param = transparencyMap[event.transparency] return free free = None for event in events: # ignore anytime events, events with no duration, and fyi events if event.transparency == "fyi" or ( (event.anyTime or event.duration == datetime.timedelta(0)) and not event.allDay ): continue if free is None or free.fbtype_param != transparencyMap[event.transparency]: free = addFB(event) free.value = [[toUTC(event.effectiveStartTime), event.effectiveEndTime]] else: # compress freebusy blocks if possible if event.effectiveStartTime <= free.value[-1][1]: if event.effectiveEndTime > free.value[-1][1]: free.value[-1][1] = event.effectiveEndTime else: free.value.append([toUTC(event.effectiveStartTime), event.effectiveEndTime]) # once upon a time serialize would convert (date, date) to (date, period) # but no longer. So there's no need to serialize # vfree.serialize() return cal
def updateFreebusyFromVObject(view, text, busyCollection, activity=None): """ Take a string, create or update freebusy events in busyCollection from that stream. Truncate differing existing freebusy events that overlap the start or end times. Returns (freebusystart, freebusyend, calname). """ newItemParent = view.findPath("//userdata") countNew = 0 countUpdated = 0 freebusystart = freebusyend = None Calendar.ensureIndexed(busyCollection) # iterate over calendars, usually only one, but more are allowed for calendar in vobject.readComponents(text, validate=True, ignoreUnreadable=True): calname = calendar.getChildValue("x_wr_calname") for vfreebusy in calendar.vfreebusy_list: # RPI's server originally didn't put a VERSION:2.0 line in its # freebusy response. vobject's behavior is set when a VERSION is # found. Tolerate servers that export technically illegal but still # readable vfreebusy components if vfreebusy.behavior is None: vfreebusy.behavior = vobject.icalendar.VFreeBusy vfreebusy.transformToNative() start = vfreebusy.getChildValue("dtstart") end = vfreebusy.getChildValue("dtend") if freebusystart is None or freebusystart > start: freebusystart = start if freebusyend is None or freebusyend < end: freebusyend = end # create a list of busy blocks tuples sorted by start time busyblocks = [] for fb in getattr(vfreebusy, "freebusy_list", []): status = getattr(fb, "fbtype_param", "BUSY").upper() for blockstart, duration in fb.value: blockstart = translateToTimezone(blockstart, view.tzinfo.default) bisect.insort(busyblocks, (blockstart, duration, status)) # eventsInRange sorts by start time, recurring events aren't allowed # so we don't bother to fetch them existing = Calendar.eventsInRange(view, start, end, busyCollection) existing = itertools.chain(existing, [None]) oldEvent = existing.next() for blockstart, duration, status in busyblocks: while oldEvent is not None and oldEvent.startTime < blockstart: # this assumes no freebusy blocks overlap freebusystart oldEvent.delete() oldEvent = existing.next() if oldEvent is not None and oldEvent.startTime == blockstart: oldEvent.transparency = reverseTransparencyMap[status] oldEvent.duration = duration countUpdated += 1 oldEvent = existing.next() else: vals = { "startTime": blockstart, "transparency": reverseTransparencyMap[status], "duration": duration, "isFreeBusy": True, "anyTime": False, "summary": "", } eventItem = CalendarEvent(None, newItemParent, **vals) busyCollection.add(eventItem.itsItem) countNew += 1 logger.info("...iCalendar import of %d new freebusy blocks, %d updated", countNew, countUpdated) return freebusystart, freebusyend, calname
def updateFreebusyFromVObject(view, text, busyCollection, activity=None): """ Take a string, create or update freebusy events in busyCollection from that stream. Truncate differing existing freebusy events that overlap the start or end times. Returns (freebusystart, freebusyend, calname). """ newItemParent = view.findPath("//userdata") countNew = 0 countUpdated = 0 freebusystart = freebusyend = None Calendar.ensureIndexed(busyCollection) # iterate over calendars, usually only one, but more are allowed for calendar in vobject.readComponents(text, validate=True, ignoreUnreadable=True): calname = calendar.getChildValue('x_wr_calname') for vfreebusy in calendar.vfreebusy_list: # RPI's server originally didn't put a VERSION:2.0 line in its # freebusy response. vobject's behavior is set when a VERSION is # found. Tolerate servers that export technically illegal but still # readable vfreebusy components if vfreebusy.behavior is None: vfreebusy.behavior = vobject.icalendar.VFreeBusy vfreebusy.transformToNative() start = vfreebusy.getChildValue('dtstart') end = vfreebusy.getChildValue('dtend') if freebusystart is None or freebusystart > start: freebusystart = start if freebusyend is None or freebusyend < end: freebusyend = end # create a list of busy blocks tuples sorted by start time busyblocks = [] for fb in getattr(vfreebusy, 'freebusy_list', []): status = getattr(fb, 'fbtype_param', 'BUSY').upper() for blockstart, duration in fb.value: blockstart = translateToTimezone(blockstart, view.tzinfo.default) bisect.insort(busyblocks, (blockstart, duration, status)) # eventsInRange sorts by start time, recurring events aren't allowed # so we don't bother to fetch them existing = Calendar.eventsInRange(view, start, end, busyCollection) existing = itertools.chain(existing, [None]) oldEvent = existing.next() for blockstart, duration, status in busyblocks: while oldEvent is not None and oldEvent.startTime < blockstart: # this assumes no freebusy blocks overlap freebusystart oldEvent.delete() oldEvent = existing.next() if oldEvent is not None and oldEvent.startTime == blockstart: oldEvent.transparency = reverseTransparencyMap[status] oldEvent.duration = duration countUpdated += 1 oldEvent = existing.next() else: vals = { 'startTime' : blockstart, 'transparency' : reverseTransparencyMap[status], 'duration' : duration, 'isFreeBusy' : True, 'anyTime' : False, 'summary' : '' } eventItem = CalendarEvent(None, newItemParent, **vals) busyCollection.add(eventItem.itsItem) countNew += 1 logger.info("...iCalendar import of %d new freebusy blocks, %d updated", countNew, countUpdated) return freebusystart, freebusyend, calname