def doIt(self, txn): uid = raw_input("Owner UID/Name: ") start = raw_input("Start Time (UTC YYYYMMDDTHHMMSSZ or YYYYMMDD): ") if len(start) == 8: start += "T000000Z" end = raw_input("End Time (UTC YYYYMMDDTHHMMSSZ or YYYYMMDD): ") if len(end) == 8: end += "T000000Z" try: start = DateTime.parseText(start) except ValueError: print("Invalid start value") returnValue(None) try: end = DateTime.parseText(end) except ValueError: print("Invalid end value") returnValue(None) timerange = caldavxml.TimeRange(start=start.getText(), end=end.getText()) home = yield txn.calendarHomeWithUID(uid) if home is None: print("Could not find calendar home") returnValue(None) yield self.eventsForEachCalendar(home, uid, timerange)
def test_query_extended(self): """ Basic query test with time range """ filter = caldavxml.Filter( caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter( *[ caldavxml.TimeRange(**{ "start": "20060605T160000Z", }) ], **{"name": ("VEVENT")}), caldavxml.ComponentFilter(**{"name": ("VTODO")}), ], **{ "name": "VCALENDAR", "test": "anyof" })) filter = Filter(filter) filter.child.settzinfo(Timezone(tzid="America/New_York")) j = filter.serialize() self.assertEqual(j["type"], "Filter") f = FilterBase.deserialize(j) self.assertTrue(isinstance(f, Filter)) self.assertEqual(len(f.child.filters), 2) self.assertTrue(isinstance(f.child.filters[0].qualifier, TimeRange))
def test_calendar_query_bogus_timezone_id(self): """ Partial retrieval of events by time range. (CalDAV-access-09, section 7.6.1) """ TimezoneCache.create() self.addCleanup(TimezoneCache.clear) calendar_properties = ( davxml.GETETag(), caldavxml.CalendarData(), ) query_timerange = caldavxml.TimeRange( start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ), end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ), ) query = caldavxml.CalendarQuery( davxml.PropertyContainer(*calendar_properties), caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( query_timerange, name="VEVENT", ), name="VCALENDAR", ), ), caldavxml.TimeZoneID.fromString("bogus"), ) result = yield self.calendar_query( query, got_xml=None, expected_code=responsecode.FORBIDDEN) self.assertTrue("valid-timezone" in result)
def test_one_event(self): """ Test when the calendar is empty. """ data = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:1234-5678 DTSTAMP:20080601T000000Z DTSTART:%s DTEND:%s END:VEVENT END:VCALENDAR """ % (self.now_12H.getText(), self.now_13H.getText(),) yield self._createCalendarObject(data, "user01", "test.ics") calendar = (yield self.calendarUnderTest(home="user01", name="calendar_1")) fbinfo = [[], [], [], ] matchtotal = 0 timerange = caldavxml.TimeRange(start=self.now.getText(), end=self.now_1D.getText()) result = (yield generateFreeBusyInfo(calendar, fbinfo, timerange, matchtotal)) self.assertEqual(result, 1) self.assertEqual(fbinfo[0], [Period.parseText("%s/%s" % (self.now_12H.getText(), self.now_13H.getText(),)), ]) self.assertEqual(len(fbinfo[1]), 0) self.assertEqual(len(fbinfo[2]), 0)
def test_timerange_query(self): """ Basic query test with time range """ filter = caldavxml.Filter( caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter( *[ caldavxml.TimeRange( **{ "start": "20060605T160000Z", "end": "20060605T170000Z" }) ], **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")}) ], **{"name": "VCALENDAR"})) filter = Filter(filter) filter.child.settzinfo(Timezone(tzid="America/New_York")) j = filter.serialize() self.assertEqual(j["type"], "Filter") f = FilterBase.deserialize(j) self.assertTrue(isinstance(f, Filter)) self.assertTrue(isinstance(f.child.filters[0].qualifier, TimeRange)) self.assertTrue( isinstance(f.child.filters[0].qualifier.tzinfo, Timezone)) self.assertEqual(f.child.filters[0].qualifier.tzinfo.getTimezoneID(), "America/New_York")
def checkForFreeBusy(self): if not hasattr(self, "isfreebusy"): if (self.calendar.propertyValue("METHOD") == "REQUEST") and (self.calendar.mainType() == "VFREEBUSY"): # Extract time range from VFREEBUSY object vfreebusies = [v for v in self.calendar.subcomponents() if v.name() == "VFREEBUSY"] if len(vfreebusies) != 1: log.error( "iTIP data is not valid for a VFREEBUSY request: {cal}", cal=str(self.calendar), ) raise HTTPError(self.errorResponse( responsecode.FORBIDDEN, self.errorElements["invalid-scheduling-message"], "iTIP data is not valid for a VFREEBUSY request", )) dtstart = vfreebusies[0].getStartDateUTC() dtend = vfreebusies[0].getEndDateUTC() if dtstart is None or dtend is None: log.error( "VFREEBUSY start/end not valid: {cal}", cal=str(self.calendar), ) raise HTTPError(self.errorResponse( responsecode.FORBIDDEN, self.errorElements["invalid-scheduling-message"], "VFREEBUSY start/end not valid", )) # Some clients send floating instead of UTC - coerce to UTC if not dtstart.utc() or not dtend.utc(): log.error( "VFREEBUSY start or end not UTC: {cal}", cal=self.calendar, ) raise HTTPError(self.errorResponse( responsecode.FORBIDDEN, self.errorElements["invalid-scheduling-message"], "VFREEBUSY start or end not UTC", )) self.timeRange = caldavxml.TimeRange(start=dtstart.getText(), end=dtend.getText()) self.timeRange.start = dtstart self.timeRange.end = dtend # Look for masked UID self.excludeUID = self.calendar.getMaskUID() # Do free busy operation self.isfreebusy = True else: # Do regular invite (fan-out) self.isfreebusy = False return self.isfreebusy
def simple_free_busy_query(self, cal_uri, start, end): query_timerange = caldavxml.TimeRange( start=start, end=end, ) query = caldavxml.FreeBusyQuery(query_timerange,) def got_calendar(calendar): pass return self.free_busy_query(cal_uri, query, got_calendar)
def test_no_events(self): """ Test when the calendar is empty. """ calendar = (yield self.calendarUnderTest(home="user01", name="calendar_1")) fbinfo = [[], [], [], ] matchtotal = 0 timerange = caldavxml.TimeRange(start=self.now.getText(), end=self.now_1D.getText()) result = (yield generateFreeBusyInfo(calendar, fbinfo, timerange, matchtotal)) self.assertEqual(result, 0) self.assertEqual(len(fbinfo[0]), 0) self.assertEqual(len(fbinfo[1]), 0) self.assertEqual(len(fbinfo[2]), 0)
def test_calendar_query_timezone(self): """ Partial retrieval of events by time range. (CalDAV-access-09, section 7.6.1) """ TimezoneCache.create() self.addCleanup(TimezoneCache.clear) tzid1 = "Etc/GMT+1" tz1 = Component(None, pycalendar=readVTZ(tzid1)) calendar_properties = ( davxml.GETETag(), caldavxml.CalendarData(), ) query_timerange = caldavxml.TimeRange( start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ), end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ), ) query = caldavxml.CalendarQuery( davxml.PropertyContainer(*calendar_properties), caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( query_timerange, name="VEVENT", ), name="VCALENDAR", ), ), caldavxml.TimeZone.fromCalendar(tz1), ) def got_xml(doc): if not isinstance(doc.root_element, davxml.MultiStatus): self.fail( "REPORT response XML root element is not multistatus: %r" % (doc.root_element, )) return self.calendar_query(query, got_xml)
def test_query_freebusy(self): """ Basic query test - with time range """ filter = caldavxml.Filter( caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter( *[ caldavxml.TimeRange( **{ "start": "20060605T160000Z", "end": "20060605T170000Z" }) ], **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")}) ], **{"name": "VCALENDAR"})) filter = Filter(filter) filter.child.settzinfo(Timezone(tzid="America/New_York")) expression = buildExpression(filter, self._queryFields) sql = CalDAVSQLQueryGenerator(expression, self, 1234, "user01", True) select, args, usedtimerange = sql.generate() self.assertEqual( select.toSQL(), SQLFragment( "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE, ORGANIZER, FLOATING, coalesce(ADJUSTED_START_DATE, START_DATE), coalesce(ADJUSTED_END_DATE, END_DATE), FBTYPE, TIME_RANGE.TRANSPARENT, PERUSER.TRANSPARENT from CALENDAR_OBJECT, TIME_RANGE left outer join PERUSER on INSTANCE_ID = TIME_RANGE_INSTANCE_ID and USER_ID = ? where ICALENDAR_TYPE in (?, ?, ?) and (FLOATING = ? and coalesce(ADJUSTED_START_DATE, START_DATE) < ? and coalesce(ADJUSTED_END_DATE, END_DATE) > ? or FLOATING = ? and coalesce(ADJUSTED_START_DATE, START_DATE) < ? and coalesce(ADJUSTED_END_DATE, END_DATE) > ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?", [ 'user01', Parameter('arg1', 3), False, datetime.datetime(2006, 6, 5, 17, 0), datetime.datetime(2006, 6, 5, 16, 0), True, datetime.datetime(2006, 6, 5, 13, 0), datetime.datetime(2006, 6, 5, 12, 0), 1234 ])) self.assertEqual(args, {"arg1": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")}) self.assertEqual(usedtimerange, True)
def test_query_timerange(self): """ Basic query test - with time range """ filter = caldavxml.Filter( caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter( *[ caldavxml.TimeRange( **{ "start": "20060605T160000Z", "end": "20060605T170000Z" }) ], **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")}) ], **{"name": "VCALENDAR"})) filter = Filter(filter) filter.child.settzinfo(Timezone(tzid="America/New_York")) expression = buildExpression(filter, self._queryFields) sql = CalDAVSQLQueryGenerator(expression, self, 1234) select, args, usedtimerange = sql.generate() self.assertEqual( select.toSQL(), SQLFragment( "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT, TIME_RANGE where ICALENDAR_TYPE in (?, ?, ?) and (FLOATING = ? and START_DATE < ? and END_DATE > ? or FLOATING = ? and START_DATE < ? and END_DATE > ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?", [ Parameter('arg1', 3), False, datetime.datetime(2006, 6, 5, 17, 0), datetime.datetime(2006, 6, 5, 16, 0), True, datetime.datetime(2006, 6, 5, 13, 0), datetime.datetime(2006, 6, 5, 12, 0), 1234 ])) self.assertEqual(args, {"arg1": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")}) self.assertEqual(usedtimerange, True)
def test_calendar_query_wrong_timezone_elements(self): """ Partial retrieval of events by time range. (CalDAV-access-09, section 7.6.1) """ TimezoneCache.create() self.addCleanup(TimezoneCache.clear) tzid1 = "Etc/GMT+1" tz1 = Component(None, pycalendar=readVTZ(tzid1)) calendar_properties = ( davxml.GETETag(), caldavxml.CalendarData(), ) query_timerange = caldavxml.TimeRange( start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ), end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ), ) query = caldavxml.CalendarQuery( davxml.PropertyContainer(*calendar_properties), caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( query_timerange, name="VEVENT", ), name="VCALENDAR", ), ), caldavxml.TimeZone.fromCalendar(tz1), ) query.children += (caldavxml.TimeZoneID.fromString(tzid1), ) result = yield self.calendar_query( query, got_xml=None, expected_code=responsecode.BAD_REQUEST) self.assertTrue("Only one of" in result)
def test_query_extended(self): """ Extended query test - two terms with anyof """ filter = caldavxml.Filter( caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter( *[ caldavxml.TimeRange(**{ "start": "20060605T160000Z", }) ], **{"name": ("VEVENT")}), caldavxml.ComponentFilter(**{"name": ("VTODO")}), ], **{ "name": "VCALENDAR", "test": "anyof" })) filter = Filter(filter) filter.child.settzinfo(Timezone(tzid="America/New_York")) expression = buildExpression(filter, self._queryFields) sql = CalDAVSQLQueryGenerator(expression, self, 1234) select, args, usedtimerange = sql.generate() self.assertEqual( select.toSQL(), SQLFragment( "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT, TIME_RANGE where (ICALENDAR_TYPE = ? and (FLOATING = ? and END_DATE > ? or FLOATING = ? and END_DATE > ?) or ICALENDAR_TYPE = ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?", [ 'VEVENT', False, datetime.datetime(2006, 6, 5, 16, 0, tzinfo=tzutc()), True, datetime.datetime(2006, 6, 5, 12, 0, tzinfo=tzutc()), 'VTODO', 1234 ])) self.assertEqual(args, {}) self.assertEqual(usedtimerange, True)
def test_calendar_query_time_range(self): """ Partial retrieval of events by time range. (CalDAV-access-09, section 7.6.1) """ calendar_properties = ( davxml.GETETag(), caldavxml.CalendarData( caldavxml.CalendarComponent( caldavxml.AllProperties(), caldavxml.CalendarComponent( caldavxml.Property(name="X-ABC-GUID"), caldavxml.Property(name="UID"), caldavxml.Property(name="DTSTART"), caldavxml.Property(name="DTEND"), caldavxml.Property(name="DURATION"), caldavxml.Property(name="EXDATE"), caldavxml.Property(name="EXRULE"), caldavxml.Property(name="RDATE"), caldavxml.Property(name="RRULE"), caldavxml.Property(name="LOCATION"), caldavxml.Property(name="SUMMARY"), name="VEVENT", ), caldavxml.CalendarComponent( caldavxml.AllProperties(), caldavxml.AllComponents(), name="VTIMEZONE", ), name="VCALENDAR", ), ), ) query_timerange = caldavxml.TimeRange( start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ), end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ), ) query = caldavxml.CalendarQuery( davxml.PropertyContainer(*calendar_properties), caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( query_timerange, name="VEVENT", ), name="VCALENDAR", ), ), ) def got_xml(doc): if not isinstance(doc.root_element, davxml.MultiStatus): self.fail( "REPORT response XML root element is not multistatus: %r" % (doc.root_element, )) for response in doc.root_element.childrenOfType( davxml.PropertyStatusResponse): properties_to_find = [p.qname() for p in calendar_properties] for propstat in response.childrenOfType(davxml.PropertyStatus): status = propstat.childOfType(davxml.Status) properties = propstat.childOfType( davxml.PropertyContainer).children if status.code != responsecode.OK: self.fail( "REPORT failed (status %s) to locate properties: %r" % (status.code, properties)) for property in properties: qname = property.qname() if qname in properties_to_find: properties_to_find.remove(qname) else: self.fail( "REPORT found property we didn't ask for: %r" % (property, )) if isinstance(property, caldavxml.CalendarData): cal = property.calendar() instances = cal.expandTimeRanges( query_timerange.end) vevents = [ x for x in cal.subcomponents() if x.name() == "VEVENT" ] if not TimeRange(query_timerange).matchinstance( vevents[0], instances): self.fail( "REPORT property %r returned calendar %s outside of request time range %r" % (property, property.calendar, query_timerange)) return self.calendar_query(query, got_xml)
def _cancelEvents(self, txn, uid, cuas): # Anything in the past is left alone whenString = self.when.getText() query_filter = caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( caldavxml.TimeRange(start=whenString,), name=("VEVENT",), ), name="VCALENDAR", ) ) query_filter = Filter(query_filter) count = 0 txn = self.store.newTransaction() storeCalHome = yield txn.calendarHomeWithUID(uid) calendarNames = yield storeCalHome.listCalendars() yield txn.commit() for calendarName in calendarNames: txn = self.store.newTransaction(authz_uid=uid) storeCalHome = yield txn.calendarHomeWithUID(uid) calendar = yield storeCalHome.calendarWithName(calendarName) allChildNames = [] futureChildNames = set() # Only purge owned calendars if calendar.owned(): # all events for childName in (yield calendar.listCalendarObjects()): allChildNames.append(childName) # events matching filter for childName, _ignore_childUid, _ignore_childType in (yield calendar.search(query_filter)): futureChildNames.add(childName) yield txn.commit() for childName in allChildNames: txn = self.store.newTransaction(authz_uid=uid) storeCalHome = yield txn.calendarHomeWithUID(uid) calendar = yield storeCalHome.calendarWithName(calendarName) doScheduling = childName in futureChildNames try: childResource = yield calendar.calendarObjectWithName(childName) uri = "/calendars/__uids__/%s/%s/%s" % (storeCalHome.uid(), calendar.name(), childName) incrementCount = self.dryrun if self.verbose: if self.dryrun: print("Would delete%s: %s" % (" with scheduling" if doScheduling else "", uri,)) else: print("Deleting%s: %s" % (" with scheduling" if doScheduling else "", uri,)) if not self.dryrun: retry = False try: yield childResource.purge(implicitly=doScheduling) incrementCount = True except Exception, e: print("Exception deleting %s: %s" % (uri, str(e))) retry = True if retry and doScheduling: # Try again with implicit scheduling off print("Retrying deletion of %s with scheduling turned off" % (uri,)) try: yield childResource.purge(implicitly=False) incrementCount = True except Exception, e: print("Still couldn't delete %s even with scheduling turned off: %s" % (uri, str(e))) if incrementCount: count += 1 # Commit yield txn.commit() except Exception, e: # Abort yield txn.abort() raise e
def test_simple(self): data = ( ( "#1.1 No busy time", [ [], [], [], ], "20080601T000000Z", "20080602T000000Z", None, None, None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z END:VFREEBUSY END:VCALENDAR """, ), ( "#1.2 No busy time with organizer & attendee", [ [], [], [], ], "20080601T000000Z", "20080602T000000Z", Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ( "#1.3 With single busy time", [ [ Period.parseText("20080601T120000Z/20080601T130000Z"), ], [], [], ], "20080601T000000Z", "20080602T000000Z", Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] FREEBUSY;FBTYPE=BUSY:20080601T120000Z/20080601T130000Z ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ( "#1.4 With multiple busy time", [ [ Period.parseText("20080601T120000Z/20080601T130000Z"), Period.parseText("20080601T140000Z/20080601T150000Z"), ], [], [], ], "20080601T000000Z", "20080602T000000Z", Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] FREEBUSY;FBTYPE=BUSY:20080601T120000Z/20080601T130000Z,20080601T140000Z/20080601T150000Z ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ( "#1.5 With multiple busy time, some overlap", [ [ Period.parseText("20080601T120000Z/20080601T130000Z"), Period.parseText("20080601T123000Z/20080601T133000Z"), Period.parseText("20080601T140000Z/20080601T150000Z"), Period.parseText("20080601T150000Z/20080601T160000Z"), ], [], [], ], "20080601T000000Z", "20080602T000000Z", Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] FREEBUSY;FBTYPE=BUSY:20080601T120000Z/20080601T133000Z,20080601T140000Z/20080601T160000Z ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ( "#1.6 With all busy time types", [ [ Period.parseText("20080601T120000Z/20080601T130000Z"), Period.parseText("20080601T140000Z/20080601T150000Z"), ], [ Period.parseText("20080601T140000Z/20080601T150000Z"), ], [ Period.parseText("20080601T160000Z/20080601T170000Z"), ], ], "20080601T000000Z", "20080602T000000Z", Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] FREEBUSY;FBTYPE=BUSY:20080601T120000Z/20080601T130000Z,20080601T140000Z/20080601T150000Z FREEBUSY;FBTYPE=BUSY-TENTATIVE:20080601T140000Z/20080601T150000Z FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:20080601T160000Z/20080601T170000Z ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ( "#1.7 With single busy time and event details", [ [ Period.parseText("20080601T120000Z/20080601T130000Z"), ], [], [], ], "20080601T000000Z", "20080602T000000Z", Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), [ tuple( Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:1234-5678 DTSTAMP:20080601T000000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z END:VEVENT END:VCALENDAR """).subcomponents())[0], ], """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT DTSTART:20080601T120000Z DTEND:20080601T130000Z END:VEVENT BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] FREEBUSY;FBTYPE=BUSY:20080601T120000Z/20080601T130000Z ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ) for description, fbinfo, dtstart, dtend, organizer, attendee, event_details, calendar in data: timerange = caldavxml.TimeRange(start=dtstart, end=dtend) result = buildFreeBusyResult(fbinfo, timerange, organizer=organizer, attendee=attendee, event_details=event_details) self.assertEqual(normalizeiCalendarText(str(result)), calendar.replace("\n", "\r\n"), msg=description)