def _doTest(): result = None if test[0] == '@': if '=' in test: attr, value = test[1:].split('=') value = value[1:-1] else: attr = test[1:] value = None if attr not in node.keys(): result = " Missing attribute returned in XML for %s\n" % (path,) if value is not None and node.get(attr) != value: result = " Incorrect attribute value returned in XML for %s\n" % (path,) elif test[0] == '=': if node.text != test[1:]: result = " Incorrect value returned in XML for %s\n" % (path,) elif test[0] == '!': if node.text == test[1:]: result = " Incorrect value returned in XML for %s\n" % (path,) elif test[0] == '*': if node.text is None or node.text.find(test[1:]) == -1: result = " Incorrect value returned in XML for %s\n" % (path,) elif test[0] == '$': if node.text is None or node.text.find(test[1:]) != -1: result = " Incorrect value returned in XML for %s\n" % (path,) elif test[0] == '+': if node.text is None or not node.text.startswith(test[1:]): result = " Incorrect value returned in XML for %s\n" % (path,) elif test[0] == '^': if "=" in test: element, value = test[1:].split("=", 1) else: element = test[1:] value = None for child in node.getchildren(): if child.tag == element and (value is None or child.text == value): break else: result = " Missing child returned in XML for %s\n" % (path,) # Try to parse as iCalendar elif test == 'icalendar': try: Calendar.parseText(node.text) except: result = " Incorrect value returned in iCalendar for %s\n" % (path,) # Try to parse as JSON elif test == 'json': try: json.loads(node.text) except: result = " Incorrect value returned in XML for %s\n" % (path,) return result
def _getTimezoneFromServer(self, tzinfo): # List all from the server url = "%s?action=get&tzid=%s" % (self.uri, tzinfo.tzid) log.debug("Getting timezone from secondary server: %s" % (url,)) response = (yield getURL(url)) if response is None or response.code / 100 != 2: returnValue(None) ct = response.headers.getRawHeaders("content-type", ("bogus/type",))[0] ct = ct.split(";", 1) ct = ct[0] if ct not in ("text/calendar",): log.error("Invalid content-type '%s' for tzid : %s" % (ct, tzinfo.tzid)) returnValue(None) ical = response.data try: calendar = Calendar.parseText(ical) except InvalidData: log.error("Invalid calendar data for tzid: %s" % (tzinfo.tzid,)) returnValue(None) ical = calendar.getText() tzinfo.md5 = hashlib.md5(ical).hexdigest() try: tzpath = os.path.join(self.basepath, tzinfo.tzid) + ".ics" if not os.path.exists(os.path.dirname(tzpath)): os.makedirs(os.path.dirname(tzpath)) f = open(tzpath, "w") f.write(ical) f.close() except IOError, e: log.error("Unable to write calendar file for %s: %s" % (tzinfo.tzid, str(e)))
def _getTimezoneFromServer(self, tzinfo): # List all from the server url = "%s?action=get&tzid=%s" % (self.uri, tzinfo.tzid,) log.debug("Getting timezone from secondary server: %s" % (url,)) response = (yield getURL(url)) if response is None or response.code / 100 != 2: returnValue(None) ct = response.headers.getRawHeaders("content-type", ("bogus/type",))[0] ct = ct.split(";", 1) ct = ct[0] if ct not in ("text/calendar",): log.error("Invalid content-type '%s' for tzid : %s" % (ct, tzinfo.tzid,)) returnValue(None) ical = response.data try: calendar = Calendar.parseText(ical) except InvalidData: log.error("Invalid calendar data for tzid: %s" % (tzinfo.tzid,)) returnValue(None) ical = calendar.getText() tzinfo.md5 = hashlib.md5(ical).hexdigest() try: tzpath = os.path.join(self.basepath, tzinfo.tzid) + ".ics" if not os.path.exists(os.path.dirname(tzpath)): os.makedirs(os.path.dirname(tzpath)) f = open(tzpath, "w") f.write(ical) f.close() except IOError, e: log.error("Unable to write calendar file for %s: %s" % (tzinfo.tzid, str(e),))
def _doRoundtrip(caldata, jcaldata): cal1 = Calendar.parseText(caldata) test1 = cal1.getText() cal2 = Calendar.parseJSONData(jcaldata) test2 = cal2.getText() self.assertEqual( test1, test2, "\n".join( difflib.unified_diff( str(test1).splitlines(), test2.splitlines())))
def _doRoundtrip(caldata, resultdata): test1 = json.dumps(json.loads(resultdata), indent=2, separators=(',', ':'), sort_keys=True) cal = Calendar.parseText(caldata) test2 = cal.getTextJSON(sort_keys=True) self.assertEqual( test1, test2, "\n".join(difflib.unified_diff(str(test1).splitlines(), test2.splitlines())) )
def _doRoundtrip(caldata, jcaldata): cal1 = Calendar.parseText(caldata) test1 = cal1.getText() cal2 = Calendar.parseJSONData(jcaldata) test2 = cal2.getText() self.assertEqual( test1, test2, "\n".join(difflib.unified_diff(str(test1).splitlines(), test2.splitlines())) )
def _doRoundtrip(caldata, resultdata): test1 = json.dumps(json.loads(resultdata), indent=2, separators=(',', ':'), sort_keys=True) cal = Calendar.parseText(caldata) test2 = cal.getTextJSON(sort_keys=True) self.assertEqual( test1, test2, "\n".join( difflib.unified_diff( str(test1).splitlines(), test2.splitlines())))
def validate(fname): """ Check whether the contents of the specified file is valid iCalendar or vCard data. """ with open(fname) as f: data = f.read() ParserContext.allRaise() if data.find("BEGIN:VCALENDAR") != -1: try: cal = Calendar.parseText(data) except ErrorBase as e: print("Failed to parse iCalendar: {}: {}".format( e.mReason, e.mData, )) sys.exit(1) elif data.find("BEGIN:VCARD") != -1: try: cal = Card.parseText(data) except ErrorBase as e: print("Failed to parse vCard: {}: {}".format( e.mReason, e.mData, )) sys.exit(1) else: print("Failed to find valid iCalendar or vCard data") sys.exit(1) _ignore_fixed, unfixed = cal.validate(doFix=False, doRaise=False) if unfixed: print("List of problems: {}".format(unfixed, )) else: print("No problems") # Control character check - only HTAB, CR, LF allowed for characters in the range 0x00-0x1F s = str(data) if len( s.translate( None, "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F" )) != len(s): for ctr, i in enumerate(data): if i in "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F": print("Control character {} at position {}".format( ord(i), ctr, ))
def validate(fname): """ Check whether the contents of the specified file is valid iCalendar or vCard data. """ data = open(fname).read() ParserContext.allRaise() if data.find("BEGIN:VCALENDAR") != -1: try: cal = Calendar.parseText(data) except ErrorBase, e: print "Failed to parse iCalendar: %r" % (e, ) sys.exit(1)
def validate(fname): """ Check whether the contents of the specified file is valid iCalendar or vCard data. """ with open(fname) as f: data = f.read() ParserContext.allRaise() if data.find("BEGIN:VCALENDAR") != -1: try: cal = Calendar.parseText(data) except ErrorBase, e: print "Failed to parse iCalendar: %r" % (e,) sys.exit(1)
def testjCalExample1(self): jcaldata = """["vcalendar", [ ["calscale", {}, "text", "GREGORIAN"], ["prodid", {}, "text", "-//Example Inc.//Example Calendar//EN"], ["version", {}, "text", "2.0"] ], [ ["vevent", [ ["dtstamp", {}, "date-time", "2008-02-05T19:12:24Z"], ["dtstart", {}, "date", "2008-10-06"], ["summary", {}, "text", "Planning meeting"], ["uid", {}, "text", "4088E990AD89CB3DBB484909"] ], [] ] ] ] """ icaldata = """BEGIN:VCALENDAR CALSCALE:GREGORIAN PRODID:-//Example Inc.//Example Calendar//EN VERSION:2.0 BEGIN:VEVENT DTSTAMP:20080205T191224Z DTSTART;VALUE=DATE:20081006 SUMMARY:Planning meeting UID:4088E990AD89CB3DBB484909 END:VEVENT END:VCALENDAR """.replace("\n", "\r\n") cal1 = Calendar.parseText(icaldata) test1 = cal1.getText() cal2 = Calendar.parseJSONData(jcaldata) test2 = cal2.getText() self.assertEqual( test1, test2, "\n".join(difflib.unified_diff(str(test1).splitlines(), test2.splitlines())) )
def testjCalExample1(self): jcaldata = """["vcalendar", [ ["calscale", {}, "text", "GREGORIAN"], ["prodid", {}, "text", "-//Example Inc.//Example Calendar//EN"], ["version", {}, "text", "2.0"] ], [ ["vevent", [ ["dtstamp", {}, "date-time", "2008-02-05T19:12:24Z"], ["dtstart", {}, "date", "2008-10-06"], ["summary", {}, "text", "Planning meeting"], ["uid", {}, "text", "4088E990AD89CB3DBB484909"] ], [] ] ] ] """ icaldata = """BEGIN:VCALENDAR CALSCALE:GREGORIAN PRODID:-//Example Inc.//Example Calendar//EN VERSION:2.0 BEGIN:VEVENT DTSTAMP:20080205T191224Z DTSTART;VALUE=DATE:20081006 SUMMARY:Planning meeting UID:4088E990AD89CB3DBB484909 END:VEVENT END:VCALENDAR """.replace("\n", "\r\n") cal1 = Calendar.parseText(icaldata) test1 = cal1.getText() cal2 = Calendar.parseJSONData(jcaldata) test2 = cal2.getText() self.assertEqual( test1, test2, "\n".join( difflib.unified_diff( str(test1).splitlines(), test2.splitlines())))
def _calProperty(self, propertyname, respdata): try: cal = Calendar.parseText(respdata) except Exception: return None # propname is a path consisting of component names and the last one a property name # e.g. VEVENT/ATTACH bits = propertyname.split("/") components = bits[:-1] prop = bits[-1] bits = prop.split("$") pname = bits[0] pvalue = bits[1] if len(bits) > 1 else None while components: for c in cal.getComponents(): if c.getType() == components[0]: cal = c components = components[1:] break else: break if components: return None props = cal.getProperties(pname) if pvalue: for prop in props: if prop.getValue().getValue() == pvalue: return prop else: return None else: return props[0] if props else None
def testOffsets(self): data = ( ( """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN BEGIN:VTIMEZONE TZID:America/New_York X-LIC-LOCATION:America/New_York BEGIN:STANDARD DTSTART:18831118T120358 RDATE:18831118T120358 TZNAME:EST TZOFFSETFROM:-045602 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19180331T020000 RRULE:FREQ=YEARLY;UNTIL=19190330T070000Z;BYDAY=-1SU;BYMONTH=3 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19181027T020000 RRULE:FREQ=YEARLY;UNTIL=19191026T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:STANDARD DTSTART:19200101T000000 RDATE:19200101T000000 RDATE:19420101T000000 RDATE:19460101T000000 RDATE:19670101T000000 TZNAME:EST TZOFFSETFROM:-0500 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19200328T020000 RDATE:19200328T020000 RDATE:19740106T020000 RDATE:19750223T020000 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19201031T020000 RDATE:19201031T020000 RDATE:19450930T020000 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19210424T020000 RRULE:FREQ=YEARLY;UNTIL=19410427T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19210925T020000 RRULE:FREQ=YEARLY;UNTIL=19410928T060000Z;BYDAY=-1SU;BYMONTH=9 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19420209T020000 RDATE:19420209T020000 TZNAME:EWT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19450814T190000 RDATE:19450814T190000 TZNAME:EPT TZOFFSETFROM:-0400 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19460428T020000 RRULE:FREQ=YEARLY;UNTIL=19660424T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19460929T020000 RRULE:FREQ=YEARLY;UNTIL=19540926T060000Z;BYDAY=-1SU;BYMONTH=9 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:STANDARD DTSTART:19551030T020000 RRULE:FREQ=YEARLY;UNTIL=19661030T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19670430T020000 RRULE:FREQ=YEARLY;UNTIL=19730429T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19671029T020000 RRULE:FREQ=YEARLY;UNTIL=20061029T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19760425T020000 RRULE:FREQ=YEARLY;UNTIL=19860427T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19870405T020000 RRULE:FREQ=YEARLY;UNTIL=20060402T070000Z;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:20070311T020000 RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20071104T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE END:VCALENDAR """, ( (DateTime(1942, 2, 8), False, -5), (DateTime(1942, 2, 10), False, -4), (DateTime(2011, 1, 1), False, -5), (DateTime(2011, 4, 1), False, -4), (DateTime(2011, 10, 24), False, -4), (DateTime(2011, 11, 8), False, -5), (DateTime(2006, 1, 1), False, -5), (DateTime(2006, 4, 1), False, -5), (DateTime(2006, 5, 1), False, -4), (DateTime(2006, 10, 1), False, -4), (DateTime(2006, 10, 24), False, -4), (DateTime(2006, 11, 8), False, -5), (DateTime(2014, 3, 8, 23, 0, 0), False, -5), (DateTime(2014, 3, 9, 0, 0, 0), False, -5), (DateTime(2014, 3, 9, 3, 0, 0), False, -4), (DateTime(2014, 3, 9, 8, 0, 0), False, -4), (DateTime(2014, 3, 8, 23, 0, 0), True, -5), (DateTime(2014, 3, 9, 0, 0, 0), True, -5), (DateTime(2014, 3, 9, 3, 0, 0), True, -5), (DateTime(2014, 3, 9, 8, 0, 0), True, -4), (DateTime(2014, 11, 1, 23, 0, 0), False, -4), (DateTime(2014, 11, 2, 0, 0, 0), False, -4), (DateTime(2014, 11, 2, 3, 0, 0), False, -5), (DateTime(2014, 11, 2, 8, 0, 0), False, -5), (DateTime(2014, 11, 1, 23, 0, 0), True, -4), (DateTime(2014, 11, 2, 0, 0, 0), True, -4), (DateTime(2014, 11, 2, 3, 0, 0), True, -4), (DateTime(2014, 11, 2, 8, 0, 0), True, -5), ) ), ( """BEGIN:VCALENDAR CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:Etc/GMT+8 X-LIC-LOCATION:Etc/GMT+8 BEGIN:STANDARD DTSTART:18000101T000000 RDATE:18000101T000000 TZNAME:GMT+8 TZOFFSETFROM:-0800 TZOFFSETTO:-0800 END:STANDARD END:VTIMEZONE END:VCALENDAR """, ( (DateTime(1942, 2, 8), False, -8), (DateTime(1942, 2, 10), False, -8), (DateTime(2011, 1, 1), False, -8), (DateTime(2011, 4, 1), False, -8), ) ), ) for tzdata, offsets in data: cal = Calendar.parseText(tzdata.replace("\n", "\r\n")) tz = cal.getComponents()[0] for dt, relative_to_utc, offset in offsets: tzoffset = tz.getTimezoneOffsetSeconds(dt, relative_to_utc) self.assertEqual(tzoffset, offset * 60 * 60, "Failed to match offset for %s at %s with caching" % (tz.getID(), dt,)) for dt, relative_to_utc, offset in reversed(offsets): tzoffset = tz.getTimezoneOffsetSeconds(dt, relative_to_utc) self.assertEqual(tzoffset, offset * 60 * 60, "Failed to match offset for %s at %s with caching, reversed" % (tz.getID(), dt,)) for dt, relative_to_utc, offset in offsets: tz.mCachedExpandAllMax = None tzoffset = tz.getTimezoneOffsetSeconds(dt, relative_to_utc) self.assertEqual(tzoffset, offset * 60 * 60, "Failed to match offset for %s at %s without caching" % (tz.getID(), dt,)) for dt, relative_to_utc, offset in reversed(offsets): tz.mCachedExpandAllMax = None tzoffset = tz.getTimezoneOffsetSeconds(dt, relative_to_utc) self.assertEqual(tzoffset, offset * 60 * 60, "Failed to match offset for %s at %s without caching, reversed" % (tz.getID(), dt,))
def verify(self, manager, uri, response, respdata, args): #@UnusedVariable # Must have status 200 if response.status != 200: return False, " HTTP Status Code Wrong: %d" % (response.status,) # Get expected FREEBUSY info busy = args.get("busy", []) tentative = args.get("tentative", []) unavailable = args.get("unavailable", []) duration = args.get("duration") is not None # Parse data as calendar object try: calendar = Calendar.parseText(respdata) # Check for calendar if calendar is None: raise ValueError("Not a calendar: %s" % (respdata,)) # Only one component comps = calendar.getComponents("VFREEBUSY") if len(comps) != 1: raise ValueError("Wrong number or unexpected components in calendar") # Must be VFREEBUSY fb = comps[0] # Extract periods busyp = [] tentativep = [] unavailablep = [] for fp in fb.getProperties("FREEBUSY"): periods = fp.getValue().getValues() # Convert start/duration to start/end for i in range(len(periods)): periods[i].getValue().setUseDuration(duration) # Check param fbtype = "BUSY" if fp.hasParameter("FBTYPE"): fbtype = fp.getParameterValue("FBTYPE") if fbtype == "BUSY": busyp.extend(periods) elif fbtype == "BUSY-TENTATIVE": tentativep.extend(periods) elif fbtype == "BUSY-UNAVAILABLE": unavailablep.extend(periods) else: raise ValueError("Unknown FBTYPE: %s" % (fbtype,)) # Set sizes must match if ( (len(busy) != len(busyp)) or (len(unavailable) != len(unavailablep)) or (len(tentative) != len(tentativep)) ): raise ValueError("Period list sizes do not match.") # Convert to string sets busy = set(busy) busyp = [x.getValue().getText() for x in busyp] busyp = set(busyp) tentative = set(tentative) tentativep = [x.getValue().getText() for x in tentativep] tentativep = set(tentativep) unavailable = set(unavailable) unavailablep = [x.getValue().getText() for x in unavailablep] unavailablep = set(unavailablep) # Compare all periods if len(busyp.symmetric_difference(busy)): raise ValueError("Busy periods do not match: {}".format(busyp.symmetric_difference(busy))) elif len(tentativep.symmetric_difference(tentative)): raise ValueError("Busy-tentative periods do not match") elif len(unavailablep.symmetric_difference(unavailable)): raise ValueError("Busy-unavailable periods do not match") except InvalidData: return False, " HTTP response data is not a calendar" except ValueError, txt: return False, " HTTP response data is invalid: %s" % (txt,)
def verify(self, manager, uri, response, respdata, args): #@UnusedVariable # Get arguments files = args.get("filepath", []) caldata = args.get("data", []) filters = args.get("filter", []) statusCode = args.get("status", ["200", "201", "207"]) doTimezones = args.get("doTimezones", None) if "EMAIL parameter" not in manager.server_info.features: filters.append("ATTENDEE:EMAIL") filters.append("ORGANIZER:EMAIL") filters.append("ATTENDEE:X-CALENDARSERVER-DTSTAMP") filters.append("CALSCALE") filters.append("PRODID") filters.append("DTSTAMP") filters.append("CREATED") filters.append("LAST-MODIFIED") filters.append("X-WR-CALNAME") for afilter in tuple(filters): if afilter[0] == "!" and afilter[1:] in filters: filters.remove(afilter[1:]) filters = filter(lambda x: x[0] != "!", filters) if doTimezones is None: doTimezones = "timezones-by-reference" not in manager.server_info.features else: doTimezones = doTimezones == "true" # status code must be 200, 201, 207 or explicitly specified code if str(response.status) not in statusCode: return False, " HTTP Status Code Wrong: %d" % (response.status,) # look for response data if not respdata: return False, " No response body" # look for one file if len(files) != 1 and len(caldata) != 1: return False, " No file to compare response to" # read in all data from specified file or use provided data if len(files): fd = open(files[0], "r") try: try: data = fd.read() finally: fd.close() except: data = None else: data = caldata[0] if len(caldata) else None if data is None: return False, " Could not read data file" data = manager.server_info.extrasubs(manager.server_info.subs(data)) def removePropertiesParameters(component): if not doTimezones: for subcomponent in tuple(component.getComponents()): if subcomponent.getType() == "VTIMEZONE": component.removeComponent(subcomponent) for subcomponent in component.getComponents(): removePropertiesParameters(subcomponent) allProps = [] for properties in component.getProperties().itervalues(): allProps.extend(properties) for property in allProps: # Always reset DTSTAMP on these properties if property.getName() in ("ATTENDEE", "X-CALENDARSERVER-ATTENDEE-COMMENT"): if property.hasParameter("X-CALENDARSERVER-DTSTAMP"): property.replaceParameter(Parameter("X-CALENDARSERVER-DTSTAMP", "20080101T000000Z")) for filter in filters: if ":" in filter: propname, parameter = filter.split(":") if property.getName() == propname: if property.hasParameter(parameter): property.removeParameters(parameter) else: if property.getName() == filter: component.removeProperty(property) try: resp_calendar = Calendar.parseText(respdata) removePropertiesParameters(resp_calendar) respdata = resp_calendar.getText() data_calendar = Calendar.parseText(data) removePropertiesParameters(data_calendar) data = data_calendar.getText() result = respdata == data if result: return True, "" else: error_diff = "\n".join([line for line in unified_diff(data.split("\n"), respdata.split("\n"))]) return False, " Response data does not exactly match file data%s" % (error_diff,) except Exception, e: return False, " Response data is not calendar data: %s" % (e,)
def testMasterComponent(self): data = ( ( "1.1 Non-recurring no VTIMEZONE", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110601 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110601 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT """.replace("\n", "\r\n"), ), ( "1.2 Non-recurring with VTIMEZONE", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTIMEZONE TZID:Etc/GMT+1 X-LIC-LOCATION:Etc/GMT+1 BEGIN:STANDARD DTSTART:18000101T000000 RDATE:18000101T000000 TZNAME:GMT+1 TZOFFSETFROM:-0100 TZOFFSETTO:-0100 END:STANDARD END:VTIMEZONE BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;TZID=Etc/GMT+1:20110601T000000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;TZID=Etc/GMT+1:20110601T000000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT """.replace("\n", "\r\n"), ), ( "2.1 Recurring no VTIMEZONE", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110601 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY SUMMARY:New Year's Day END:VEVENT BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;VALUE=DATE:20110602 DTSTART;VALUE=DATE:20110602 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110601 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY SUMMARY:New Year's Day END:VEVENT """.replace("\n", "\r\n"), ), ( "2.2 Recurring with VTIMEZONE", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTIMEZONE TZID:Etc/GMT+1 X-LIC-LOCATION:Etc/GMT+1 BEGIN:STANDARD DTSTART:18000101T000000 RDATE:18000101T000000 TZNAME:GMT+1 TZOFFSETFROM:-0100 TZOFFSETTO:-0100 END:STANDARD END:VTIMEZONE BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;TZID=Etc/GMT+1:20110601T000000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day RRULE:FREQ=DAILY END:VEVENT BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;TZID=Etc/GMT+1:20110602T000000 DTSTART;TZID=Etc/GMT+1:20110602T000000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;TZID=Etc/GMT+1:20110601T000000 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY SUMMARY:New Year's Day END:VEVENT """.replace("\n", "\r\n"), ), ( "3.1 Recurring no master, no VTIMEZONE", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;VALUE=DATE:20110602 DTSTART;VALUE=DATE:20110602 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), "", ), ( "3.2 Recurring no master, with VTIMEZONE", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTIMEZONE TZID:Etc/GMT+1 X-LIC-LOCATION:Etc/GMT+1 BEGIN:STANDARD DTSTART:18000101T000000 RDATE:18000101T000000 TZNAME:GMT+1 TZOFFSETFROM:-0100 TZOFFSETTO:-0100 END:STANDARD END:VTIMEZONE BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;TZID=Etc/GMT+1:20110602T000000 DTSTART;TZID=Etc/GMT+1:20110602T000000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), "", ), ) for title, caldata, result in data: calendar = Calendar.parseText(caldata) master = calendar.masterComponent() if master is None: master = "" self.assertEqual( str(master), result, "Failed in %s: got %s, expected %s" % (title, master, result))
def testGetVEvents(self): data = ( ( "Non-recurring match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110601 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), (DateTime(2011, 6, 1), ), ), ( "Non-recurring no-match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110501 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), (), ), ( "Recurring match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110601 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), ( DateTime(2011, 6, 1), DateTime(2011, 6, 2), ), ), ( "Recurring no match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110501 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), (), ), ( "Recurring with override match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART:20110601T120000 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;VALUE=DATE:20110602T120000 DTSTART;VALUE=DATE:20110602T130000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), ( DateTime(2011, 6, 1, 12, 0, 0), DateTime(2011, 6, 2, 13, 0, 0), ), ), ( "Recurring with override no match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART:20110501T120000 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;VALUE=DATE:20110502T120000 DTSTART;VALUE=DATE:20110502T130000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), (), ), ( "Recurring partial match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110531 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), (DateTime(2011, 6, 1), ), ), ( "Recurring with override partial match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART:20110531T120000 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;VALUE=DATE:20110601T120000 DTSTART;VALUE=DATE:20110601T130000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), (DateTime(2011, 6, 1, 13, 0, 0), ), ), ) for title, caldata, result in data: calendar = Calendar.parseText(caldata) instances = [] calendar.getVEvents( Period( start=DateTime(2011, 6, 1), end=DateTime(2011, 7, 1), ), instances) instances = tuple( [instance.getInstanceStart() for instance in instances]) self.assertEqual( instances, result, "Failed in %s: got %s, expected %s" % (title, instances, result))
def testParseBlank(self): data = ( """ BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTEND;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """ BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTEND;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTEND;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTEND;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTEND;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), ) save = ParserContext.BLANK_LINES_IN_DATA for item in data: ParserContext.BLANK_LINES_IN_DATA = ParserContext.PARSER_RAISE self.assertRaises(InvalidData, Calendar.parseText, item) ParserContext.BLANK_LINES_IN_DATA = ParserContext.PARSER_IGNORE lines = item.split("\r\n") result = "\r\n".join([line for line in lines if line]) + "\r\n" self.assertEqual(str(Calendar.parseText(item)), result) ParserContext.BLANK_LINES_IN_DATA = save
def verify(self, manager, uri, response, respdata, args): # @UnusedVariable # Must have status 200 if response.status != 200: return False, " HTTP Status Code Wrong: %d" % ( response.status, ) # Get expected FREEBUSY info busy = args.get("busy", []) tentative = args.get("tentative", []) unavailable = args.get("unavailable", []) duration = args.get("duration") is not None # Parse data as calendar object try: calendar = Calendar.parseText(respdata) # Check for calendar if calendar is None: raise ValueError("Not a calendar: %s" % (respdata, )) # Only one component comps = calendar.getComponents("VFREEBUSY") if len(comps) != 1: raise ValueError( "Wrong number or unexpected components in calendar") # Must be VFREEBUSY fb = comps[0] # Extract periods busyp = [] tentativep = [] unavailablep = [] for fp in fb.getProperties("FREEBUSY"): periods = fp.getValue().getValues() # Convert start/duration to start/end for i in range(len(periods)): periods[i].getValue().setUseDuration(duration) # Check param fbtype = "BUSY" if fp.hasParameter("FBTYPE"): fbtype = fp.getParameterValue("FBTYPE") if fbtype == "BUSY": busyp.extend(periods) elif fbtype == "BUSY-TENTATIVE": tentativep.extend(periods) elif fbtype == "BUSY-UNAVAILABLE": unavailablep.extend(periods) else: raise ValueError("Unknown FBTYPE: %s" % (fbtype, )) # Set sizes must match if ((len(busy) != len(busyp)) or (len(unavailable) != len(unavailablep)) or (len(tentative) != len(tentativep))): raise ValueError("Period list sizes do not match.") # Convert to string sets busy = set(busy) busyp = [x.getValue().getText() for x in busyp] busyp = set(busyp) tentative = set(tentative) tentativep = [x.getValue().getText() for x in tentativep] tentativep = set(tentativep) unavailable = set(unavailable) unavailablep = [x.getValue().getText() for x in unavailablep] unavailablep = set(unavailablep) # Compare all periods if len(busyp.symmetric_difference(busy)): raise ValueError("Busy periods do not match: {}".format( busyp.symmetric_difference(busy))) elif len(tentativep.symmetric_difference(tentative)): raise ValueError("Busy-tentative periods do not match") elif len(unavailablep.symmetric_difference(unavailable)): raise ValueError("Busy-unavailable periods do not match") except InvalidData: return False, " HTTP response data is not a calendar" except ValueError, txt: return False, " HTTP response data is invalid: %s" % (txt, )
def testNode(cls, node, node_path, test): result = None if test[0] == '@': if '=' in test: attr, value = test[1:].split('=') value = value[1:-1] else: attr = test[1:] value = None if attr not in node.keys(): result = " Missing attribute returned in XML for %s\n" % ( node_path, ) if value is not None and node.get(attr) != value: result = " Incorrect attribute value returned in XML for %s\n" % ( node_path, ) elif test[0] == '=': if node.text != test[1:]: result = " Incorrect value returned in XML for %s\n" % ( node_path, ) elif test[0] == '!': if node.text == test[1:]: result = " Incorrect value returned in XML for %s\n" % ( node_path, ) elif test[0] == '*': if node.text is None or node.text.find(test[1:]) == -1: result = " Incorrect value returned in XML for %s\n" % ( node_path, ) elif test[0] == '$': if node.text is None or node.text.find(test[1:]) != -1: result = " Incorrect value returned in XML for %s\n" % ( node_path, ) elif test[0] == '+': if node.text is None or not node.text.startswith(test[1:]): result = " Incorrect value returned in XML for %s\n" % ( node_path, ) elif test[0] == '^': if "=" in test: element, value = test[1:].split("=", 1) else: element = test[1:] value = None for child in node.getchildren(): if child.tag == element and (value is None or child.text == value): break else: result = " Missing child returned in XML for %s\n" % ( node_path, ) elif test[0] == '|': if len(test) == 2 and test[1] == "|": if node.text is None and len(node.getchildren()) == 0: result = " Empty element returned in XML for %s\n" % ( node_path, ) else: if node.text is not None or len(node.getchildren()) != 0: result = " Non-empty element returned in XML for %s\n" % ( node_path, ) # Try to parse as iCalendar elif test == 'icalendar': try: Calendar.parseText(node.text) except: result = " Incorrect value returned in iCalendar for %s\n" % ( node_path, ) # Try to parse as JSON elif test == 'json': try: json.loads(node.text) except: result = " Incorrect value returned in XML for %s\n" % ( node_path, ) return result
def testConversions(self): tzdata = """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN BEGIN:VTIMEZONE TZID:America/New_York X-LIC-LOCATION:America/New_York BEGIN:STANDARD DTSTART:18831118T120358 RDATE:18831118T120358 TZNAME:EST TZOFFSETFROM:-045602 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19180331T020000 RRULE:FREQ=YEARLY;UNTIL=19190330T070000Z;BYDAY=-1SU;BYMONTH=3 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19181027T020000 RRULE:FREQ=YEARLY;UNTIL=19191026T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:STANDARD DTSTART:19200101T000000 RDATE:19200101T000000 RDATE:19420101T000000 RDATE:19460101T000000 RDATE:19670101T000000 TZNAME:EST TZOFFSETFROM:-0500 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19200328T020000 RDATE:19200328T020000 RDATE:19740106T020000 RDATE:19750223T020000 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19201031T020000 RDATE:19201031T020000 RDATE:19450930T020000 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19210424T020000 RRULE:FREQ=YEARLY;UNTIL=19410427T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19210925T020000 RRULE:FREQ=YEARLY;UNTIL=19410928T060000Z;BYDAY=-1SU;BYMONTH=9 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19420209T020000 RDATE:19420209T020000 TZNAME:EWT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19450814T190000 RDATE:19450814T190000 TZNAME:EPT TZOFFSETFROM:-0400 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19460428T020000 RRULE:FREQ=YEARLY;UNTIL=19660424T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19460929T020000 RRULE:FREQ=YEARLY;UNTIL=19540926T060000Z;BYDAY=-1SU;BYMONTH=9 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:STANDARD DTSTART:19551030T020000 RRULE:FREQ=YEARLY;UNTIL=19661030T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19670430T020000 RRULE:FREQ=YEARLY;UNTIL=19730429T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19671029T020000 RRULE:FREQ=YEARLY;UNTIL=20061029T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19760425T020000 RRULE:FREQ=YEARLY;UNTIL=19860427T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19870405T020000 RRULE:FREQ=YEARLY;UNTIL=20060402T070000Z;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:20070311T020000 RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20071104T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE BEGIN:VTIMEZONE TZID:America/Los_Angeles X-LIC-LOCATION:America/Los_Angeles BEGIN:STANDARD DTSTART:18831118T120702 RDATE:18831118T120702 TZNAME:PST TZOFFSETFROM:-075258 TZOFFSETTO:-0800 END:STANDARD BEGIN:DAYLIGHT DTSTART:19180331T020000 RRULE:FREQ=YEARLY;UNTIL=19190330T100000Z;BYDAY=-1SU;BYMONTH=3 TZNAME:PDT TZOFFSETFROM:-0800 TZOFFSETTO:-0700 END:DAYLIGHT BEGIN:STANDARD DTSTART:19181027T020000 RRULE:FREQ=YEARLY;UNTIL=19191026T090000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:PST TZOFFSETFROM:-0700 TZOFFSETTO:-0800 END:STANDARD BEGIN:DAYLIGHT DTSTART:19420209T020000 RDATE:19420209T020000 TZNAME:PWT TZOFFSETFROM:-0800 TZOFFSETTO:-0700 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19450814T160000 RDATE:19450814T160000 TZNAME:PPT TZOFFSETFROM:-0700 TZOFFSETTO:-0700 END:DAYLIGHT BEGIN:STANDARD DTSTART:19450930T020000 RDATE:19450930T020000 RDATE:19490101T020000 TZNAME:PST TZOFFSETFROM:-0700 TZOFFSETTO:-0800 END:STANDARD BEGIN:STANDARD DTSTART:19460101T000000 RDATE:19460101T000000 RDATE:19670101T000000 TZNAME:PST TZOFFSETFROM:-0800 TZOFFSETTO:-0800 END:STANDARD BEGIN:DAYLIGHT DTSTART:19480314T020000 RDATE:19480314T020000 RDATE:19740106T020000 RDATE:19750223T020000 TZNAME:PDT TZOFFSETFROM:-0800 TZOFFSETTO:-0700 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19500430T020000 RRULE:FREQ=YEARLY;UNTIL=19660424T100000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:PDT TZOFFSETFROM:-0800 TZOFFSETTO:-0700 END:DAYLIGHT BEGIN:STANDARD DTSTART:19500924T020000 RRULE:FREQ=YEARLY;UNTIL=19610924T090000Z;BYDAY=-1SU;BYMONTH=9 TZNAME:PST TZOFFSETFROM:-0700 TZOFFSETTO:-0800 END:STANDARD BEGIN:STANDARD DTSTART:19621028T020000 RRULE:FREQ=YEARLY;UNTIL=19661030T090000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:PST TZOFFSETFROM:-0700 TZOFFSETTO:-0800 END:STANDARD BEGIN:DAYLIGHT DTSTART:19670430T020000 RRULE:FREQ=YEARLY;UNTIL=19730429T100000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:PDT TZOFFSETFROM:-0800 TZOFFSETTO:-0700 END:DAYLIGHT BEGIN:STANDARD DTSTART:19671029T020000 RRULE:FREQ=YEARLY;UNTIL=20061029T090000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:PST TZOFFSETFROM:-0700 TZOFFSETTO:-0800 END:STANDARD BEGIN:DAYLIGHT DTSTART:19760425T020000 RRULE:FREQ=YEARLY;UNTIL=19860427T100000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:PDT TZOFFSETFROM:-0800 TZOFFSETTO:-0700 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19870405T020000 RRULE:FREQ=YEARLY;UNTIL=20060402T100000Z;BYDAY=1SU;BYMONTH=4 TZNAME:PDT TZOFFSETFROM:-0800 TZOFFSETTO:-0700 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:20070311T020000 RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 TZNAME:PDT TZOFFSETFROM:-0800 TZOFFSETTO:-0700 END:DAYLIGHT BEGIN:STANDARD DTSTART:20071104T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 TZNAME:PST TZOFFSETFROM:-0700 TZOFFSETTO:-0800 END:STANDARD END:VTIMEZONE END:VCALENDAR """ data = ( ( DateTime(2014, 3, 8, 23, 0, 0, Timezone(tzid="America/New_York")), DateTime(2014, 3, 8, 20, 0, 0, Timezone(tzid="America/Los_Angeles")), ), ( DateTime(2014, 3, 9, 3, 0, 0, Timezone(utc=True)), DateTime(2014, 3, 8, 19, 0, 0, Timezone(tzid="America/Los_Angeles")), ), ( DateTime(2014, 3, 9, 13, 0, 0, Timezone(utc=True)), DateTime(2014, 3, 9, 6, 0, 0, Timezone(tzid="America/Los_Angeles")), ), ) Calendar.parseText(tzdata.replace("\n", "\r\n")) for dtfrom, dtto in data: self.assertEqual(dtfrom, dtto) newdtfrom = dtfrom.duplicate() newdtfrom.adjustTimezone(dtto.getTimezone()) self.assertEqual(newdtfrom, dtto) self.assertEqual(newdtfrom.getHours(), dtto.getHours())
def verify(self, manager, uri, response, respdata, args): #@UnusedVariable # Must have status 200 if response.status != 200: return False, " HTTP Status Code Wrong: %d" % (response.status,) # Get expected FREEBUSY info users = args.get("attendee", []) busy = args.get("busy", []) tentative = args.get("tentative", []) unavailable = args.get("unavailable", []) # Extract each calendar-data object try: tree = ElementTree(file=StringIO.StringIO(respdata)) except ExpatError: return False, " Could not parse proper XML response\n" for calendar in tree.findall("./{urn:ietf:params:xml:ns:caldav}response/{urn:ietf:params:xml:ns:caldav}calendar-data"): # Parse data as calendar object try: calendar = Calendar.parseText(calendar.text) # Check for calendar if calendar is None: raise ValueError("Not a calendar: %s" % (calendar,)) # Only one component comps = calendar.getComponents("VFREEBUSY") if len(comps) != 1: raise ValueError("Wrong number or unexpected components in calendar") # Must be VFREEBUSY fb = comps[0] # Check for attendee value for attendee in fb.getProperties("ATTENDEE"): if attendee.getValue().getValue() in users: users.remove(attendee.getValue().getValue()) break else: continue # Extract periods busyp = [] tentativep = [] unavailablep = [] for fp in fb.getProperties("FREEBUSY"): periods = fp.getValue().getValues() # Convert start/duration to start/end for i in range(len(periods)): periods[i].getValue().setUseDuration(False) # Check param fbtype = "BUSY" if fp.hasParameter("FBTYPE"): fbtype = fp.getParameterValue("FBTYPE") if fbtype == "BUSY": busyp.extend(periods) elif fbtype == "BUSY-TENTATIVE": tentativep.extend(periods) elif fbtype == "BUSY-UNAVAILABLE": unavailablep.extend(periods) else: raise ValueError("Unknown FBTYPE: %s" % (fbtype,)) # Set sizes must match if ((len(busy) != len(busyp)) or (len(unavailable) != len(unavailablep)) or (len(tentative) != len(tentativep))): raise ValueError("Period list sizes do not match.") # Convert to string sets busy = set(busy) busyp = [x.getValue().getText() for x in busyp] busyp = set(busyp) tentative = set(tentative) tentativep = [x.getValue().getText() for x in tentativep] tentativep = set(tentativep) unavailable = set(unavailable) unavailablep = [x.getValue().getText() for x in unavailablep] unavailablep = set(unavailablep) # Compare all periods if len(busyp.symmetric_difference(busy)): raise ValueError("Busy periods do not match") elif len(tentativep.symmetric_difference(tentative)): raise ValueError("Busy-tentative periods do not match") elif len(unavailablep.symmetric_difference(unavailable)): raise ValueError("Busy-unavailable periods do not match") break except InvalidData: return False, " HTTP response data is not a calendar" except ValueError, txt: return False, " HTTP response data is invalid: %s" % (txt,)
def testGetVEvents(self): data = ( ( "Non-recurring match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110601 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), (DateTime(2011, 6, 1),), ), ( "Non-recurring no-match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110501 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), (), ), ( "Recurring match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110601 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), ( DateTime(2011, 6, 1), DateTime(2011, 6, 2), ), ), ( "Recurring no match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110501 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), (), ), ( "Recurring with override match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART:20110601T120000 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;VALUE=DATE:20110602T120000 DTSTART;VALUE=DATE:20110602T130000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), ( DateTime(2011, 6, 1, 12, 0, 0), DateTime(2011, 6, 2, 13, 0, 0), ), ), ( "Recurring with override no match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART:20110501T120000 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;VALUE=DATE:20110502T120000 DTSTART;VALUE=DATE:20110502T130000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), (), ), ( "Recurring partial match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110531 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), ( DateTime(2011, 6, 1), ), ), ( "Recurring with override partial match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART:20110531T120000 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;VALUE=DATE:20110601T120000 DTSTART;VALUE=DATE:20110601T130000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), ( DateTime(2011, 6, 1, 13, 0, 0), ), ), ) for title, caldata, result in data: calendar = Calendar.parseText(caldata) instances = [] calendar.getVEvents( Period( start=DateTime(2011, 6, 1), end=DateTime(2011, 7, 1), ), instances ) instances = tuple([instance.getInstanceStart() for instance in instances]) self.assertEqual(instances, result, "Failed in %s: got %s, expected %s" % (title, instances, result))
def testjCalExample2(self): jcaldata = """["vcalendar", [ ["prodid", {}, "text", "-//Example Corp.//Example Client//EN"], ["version", {}, "text", "2.0"] ], [ ["vtimezone", [ ["last-modified", {}, "date-time", "2004-01-10T03:28:45Z"], ["tzid", {}, "text", "US/Eastern"] ], [ ["daylight", [ ["dtstart", {}, "date-time", "2000-04-04T02:00:00"], ["rrule", {}, "recur", { "freq": "YEARLY", "byday": "1SU", "bymonth": 4 } ], ["tzname", {}, "text", "EDT"], ["tzoffsetfrom", {}, "utc-offset", "-05:00"], ["tzoffsetto", {}, "utc-offset", "-04:00"] ], [] ], ["standard", [ ["dtstart", {}, "date-time", "2000-10-26T02:00:00"], ["rrule", {}, "recur", { "freq": "YEARLY", "byday": "-1SU", "bymonth": 10 } ], ["tzname", {}, "text", "EST"], ["tzoffsetfrom", {}, "utc-offset", "-04:00:00"], ["tzoffsetto", {}, "utc-offset", "-05:00:00"] ], [] ] ] ], ["vevent", [ ["dtstamp", {}, "date-time", "2006-02-06T00:11:21Z"], ["dtstart", { "tzid": "US/Eastern" }, "date-time", "2006-01-02T12:00:00" ], ["duration", {}, "duration", "PT1H"], ["rrule", {}, "recur", { "freq": "DAILY", "count": 5 } ], ["rdate", { "tzid": "US/Eastern" }, "period", ["2006-01-02T15:00:00", "PT2H"] ], ["summary", {}, "text", "Event #2"], ["description", {}, "text", "We are having a meeting all this week at 12 pm for one hour, with an additional meeting on the first day 2 hours long.\\nPlease bring your own lunch for the 12 pm meetings." ], ["uid", {}, "text", "*****@*****.**"] ], [] ], ["vevent", [ ["dtstamp", {}, "date-time", "2006-02-06T00:11:21Z"], ["dtstart", { "tzid": "US/Eastern" }, "date-time", "2006-01-04T14:00:00" ], ["duration", {}, "duration", "PT1H"], ["recurrence-id", { "tzid": "US/Eastern" }, "date-time", "2006-01-04T12:00:00" ], ["summary", {}, "text", "Event #2 bis"], ["uid", {}, "text", "*****@*****.**"] ], [] ] ] ] """ icaldata = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Example Corp.//Example Client//EN BEGIN:VTIMEZONE LAST-MODIFIED:20040110T032845Z TZID:US/Eastern BEGIN:DAYLIGHT DTSTART:20000404T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20001026T020000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE BEGIN:VEVENT DTSTAMP:20060206T001121Z DTSTART;TZID=US/Eastern:20060102T120000 DURATION:PT1H RRULE:FREQ=DAILY;COUNT=5 RDATE;TZID=US/Eastern;VALUE=PERIOD:20060102T150000/PT2H SUMMARY:Event #2 DESCRIPTION:We are having a meeting all this week at 12 pm fo r one hour\\, with an additional meeting on the first day 2 h ours long.\\nPlease bring your own lunch for the 12 pm meetin gs. UID:[email protected] END:VEVENT BEGIN:VEVENT DTSTAMP:20060206T001121Z DTSTART;TZID=US/Eastern:20060104T140000 DURATION:PT1H RECURRENCE-ID;TZID=US/Eastern:20060104T120000 SUMMARY:Event #2 bis UID:[email protected] END:VEVENT END:VCALENDAR """.replace("\n", "\r\n") cal1 = Calendar.parseText(icaldata) test1 = cal1.getText() cal2 = Calendar.parseJSONData(jcaldata) test2 = cal2.getText() self.assertEqual( test1, test2, "\n".join( difflib.unified_diff( str(test1).splitlines(), test2.splitlines())))
def testCachePreserveOnAdjustment(self): # UTC first dt = DateTime(2012, 6, 7, 12, 0, 0, Timezone(tzid="utc")) dt.getPosixTime() # check existing cache is complete self.assertTrue(dt.mPosixTimeCached) self.assertNotEqual(dt.mPosixTime, 0) self.assertEqual(dt.mTZOffset, None) # duplicate preserves cache details dt2 = dt.duplicate() self.assertTrue(dt2.mPosixTimeCached) self.assertEqual(dt2.mPosixTime, dt.mPosixTime) self.assertEqual(dt2.mTZOffset, dt.mTZOffset) # adjust preserves cache details dt2.adjustToUTC() self.assertTrue(dt2.mPosixTimeCached) self.assertEqual(dt2.mPosixTime, dt.mPosixTime) self.assertEqual(dt2.mTZOffset, dt.mTZOffset) # Now timezone tzdata = """BEGIN:VCALENDAR CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:America/Pittsburgh BEGIN:STANDARD DTSTART:18831118T120358 RDATE:18831118T120358 TZNAME:EST TZOFFSETFROM:-045602 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19180331T020000 RRULE:FREQ=YEARLY;UNTIL=19190330T070000Z;BYDAY=-1SU;BYMONTH=3 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19181027T020000 RRULE:FREQ=YEARLY;UNTIL=19191026T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:STANDARD DTSTART:19200101T000000 RDATE:19200101T000000 RDATE:19420101T000000 RDATE:19460101T000000 RDATE:19670101T000000 TZNAME:EST TZOFFSETFROM:-0500 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19200328T020000 RDATE:19200328T020000 RDATE:19740106T020000 RDATE:19750223T020000 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19201031T020000 RDATE:19201031T020000 RDATE:19450930T020000 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19210424T020000 RRULE:FREQ=YEARLY;UNTIL=19410427T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19210925T020000 RRULE:FREQ=YEARLY;UNTIL=19410928T060000Z;BYDAY=-1SU;BYMONTH=9 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19420209T020000 RDATE:19420209T020000 TZNAME:EWT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19450814T190000 RDATE:19450814T190000 TZNAME:EPT TZOFFSETFROM:-0400 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19460428T020000 RRULE:FREQ=YEARLY;UNTIL=19660424T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19460929T020000 RRULE:FREQ=YEARLY;UNTIL=19540926T060000Z;BYDAY=-1SU;BYMONTH=9 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:STANDARD DTSTART:19551030T020000 RRULE:FREQ=YEARLY;UNTIL=19661030T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19670430T020000 RRULE:FREQ=YEARLY;UNTIL=19730429T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19671029T020000 RRULE:FREQ=YEARLY;UNTIL=20061029T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19760425T020000 RRULE:FREQ=YEARLY;UNTIL=19860427T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19870405T020000 RRULE:FREQ=YEARLY;UNTIL=20060402T070000Z;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:20070311T020000 RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20071104T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE END:VCALENDAR """.replace("\n", "\r\n") Calendar.parseText(tzdata) dt = DateTime(2012, 6, 7, 12, 0, 0, Timezone(tzid="America/Pittsburgh")) dt.getPosixTime() # check existing cache is complete self.assertTrue(dt.mPosixTimeCached) self.assertNotEqual(dt.mPosixTime, 0) self.assertEqual(dt.mTZOffset, -14400) # duplicate preserves cache details dt2 = dt.duplicate() self.assertTrue(dt2.mPosixTimeCached) self.assertEqual(dt2.mPosixTime, dt.mPosixTime) self.assertEqual(dt2.mTZOffset, dt.mTZOffset) # adjust preserves cache details dt2.adjustToUTC() self.assertTrue(dt2.mPosixTimeCached) self.assertEqual(dt2.mPosixTime, dt.mPosixTime) self.assertEqual(dt2.mTZOffset, 0)
def testMasterComponent(self): data = ( ( "1.1 Non-recurring no VTIMEZONE", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110601 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110601 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT """.replace("\n", "\r\n"), ), ( "1.2 Non-recurring with VTIMEZONE", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTIMEZONE TZID:Etc/GMT+1 X-LIC-LOCATION:Etc/GMT+1 BEGIN:STANDARD DTSTART:18000101T000000 RDATE:18000101T000000 TZNAME:GMT+1 TZOFFSETFROM:-0100 TZOFFSETTO:-0100 END:STANDARD END:VTIMEZONE BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;TZID=Etc/GMT+1:20110601T000000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;TZID=Etc/GMT+1:20110601T000000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT """.replace("\n", "\r\n"), ), ( "2.1 Recurring no VTIMEZONE", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110601 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY SUMMARY:New Year's Day END:VEVENT BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;VALUE=DATE:20110602 DTSTART;VALUE=DATE:20110602 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20110601 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY SUMMARY:New Year's Day END:VEVENT """.replace("\n", "\r\n"), ), ( "2.2 Recurring with VTIMEZONE", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTIMEZONE TZID:Etc/GMT+1 X-LIC-LOCATION:Etc/GMT+1 BEGIN:STANDARD DTSTART:18000101T000000 RDATE:18000101T000000 TZNAME:GMT+1 TZOFFSETFROM:-0100 TZOFFSETTO:-0100 END:STANDARD END:VTIMEZONE BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;TZID=Etc/GMT+1:20110601T000000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day RRULE:FREQ=DAILY END:VEVENT BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;TZID=Etc/GMT+1:20110602T000000 DTSTART;TZID=Etc/GMT+1:20110602T000000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;TZID=Etc/GMT+1:20110601T000000 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY SUMMARY:New Year's Day END:VEVENT """.replace("\n", "\r\n"), ), ( "3.1 Recurring no master, no VTIMEZONE", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;VALUE=DATE:20110602 DTSTART;VALUE=DATE:20110602 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), "", ), ( "3.2 Recurring no master, with VTIMEZONE", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTIMEZONE TZID:Etc/GMT+1 X-LIC-LOCATION:Etc/GMT+1 BEGIN:STANDARD DTSTART:18000101T000000 RDATE:18000101T000000 TZNAME:GMT+1 TZOFFSETFROM:-0100 TZOFFSETTO:-0100 END:STANDARD END:VTIMEZONE BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;TZID=Etc/GMT+1:20110602T000000 DTSTART;TZID=Etc/GMT+1:20110602T000000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), "", ), ) for title, caldata, result in data: calendar = Calendar.parseText(caldata) master = calendar.masterComponent() if master is None: master = "" self.assertEqual(str(master), result, "Failed in %s: got %s, expected %s" % (title, master, result))
def testjCalExample2(self): jcaldata = """["vcalendar", [ ["prodid", {}, "text", "-//Example Corp.//Example Client//EN"], ["version", {}, "text", "2.0"] ], [ ["vtimezone", [ ["last-modified", {}, "date-time", "2004-01-10T03:28:45Z"], ["tzid", {}, "text", "US/Eastern"] ], [ ["daylight", [ ["dtstart", {}, "date-time", "2000-04-04T02:00:00"], ["rrule", {}, "recur", { "freq": "YEARLY", "byday": "1SU", "bymonth": 4 } ], ["tzname", {}, "text", "EDT"], ["tzoffsetfrom", {}, "utc-offset", "-05:00"], ["tzoffsetto", {}, "utc-offset", "-04:00"] ], [] ], ["standard", [ ["dtstart", {}, "date-time", "2000-10-26T02:00:00"], ["rrule", {}, "recur", { "freq": "YEARLY", "byday": "-1SU", "bymonth": 10 } ], ["tzname", {}, "text", "EST"], ["tzoffsetfrom", {}, "utc-offset", "-04:00:00"], ["tzoffsetto", {}, "utc-offset", "-05:00:00"] ], [] ] ] ], ["vevent", [ ["dtstamp", {}, "date-time", "2006-02-06T00:11:21Z"], ["dtstart", { "tzid": "US/Eastern" }, "date-time", "2006-01-02T12:00:00" ], ["duration", {}, "duration", "PT1H"], ["rrule", {}, "recur", { "freq": "DAILY", "count": 5 } ], ["rdate", { "tzid": "US/Eastern" }, "period", ["2006-01-02T15:00:00", "PT2H"] ], ["summary", {}, "text", "Event #2"], ["description", {}, "text", "We are having a meeting all this week at 12 pm for one hour, with an additional meeting on the first day 2 hours long.\\nPlease bring your own lunch for the 12 pm meetings." ], ["uid", {}, "text", "*****@*****.**"] ], [] ], ["vevent", [ ["dtstamp", {}, "date-time", "2006-02-06T00:11:21Z"], ["dtstart", { "tzid": "US/Eastern" }, "date-time", "2006-01-04T14:00:00" ], ["duration", {}, "duration", "PT1H"], ["recurrence-id", { "tzid": "US/Eastern" }, "date-time", "2006-01-04T12:00:00" ], ["summary", {}, "text", "Event #2 bis"], ["uid", {}, "text", "*****@*****.**"] ], [] ] ] ] """ icaldata = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Example Corp.//Example Client//EN BEGIN:VTIMEZONE LAST-MODIFIED:20040110T032845Z TZID:US/Eastern BEGIN:DAYLIGHT DTSTART:20000404T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20001026T020000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE BEGIN:VEVENT DTSTAMP:20060206T001121Z DTSTART;TZID=US/Eastern:20060102T120000 DURATION:PT1H RRULE:FREQ=DAILY;COUNT=5 RDATE;TZID=US/Eastern;VALUE=PERIOD:20060102T150000/PT2H SUMMARY:Event #2 DESCRIPTION:We are having a meeting all this week at 12 pm fo r one hour\\, with an additional meeting on the first day 2 h ours long.\\nPlease bring your own lunch for the 12 pm meetin gs. UID:[email protected] END:VEVENT BEGIN:VEVENT DTSTAMP:20060206T001121Z DTSTART;TZID=US/Eastern:20060104T140000 DURATION:PT1H RECURRENCE-ID;TZID=US/Eastern:20060104T120000 SUMMARY:Event #2 bis UID:[email protected] END:VEVENT END:VCALENDAR """.replace("\n", "\r\n") cal1 = Calendar.parseText(icaldata) test1 = cal1.getText() cal2 = Calendar.parseJSONData(jcaldata) test2 = cal2.getText() self.assertEqual( test1, test2, "\n".join(difflib.unified_diff(str(test1).splitlines(), test2.splitlines())) )
def anonymizeData(directoryMap, data): try: pyobj = Calendar.parseText(data) except Exception, e: print("Failed to parse (%s): %s" % (e, data)) return None
def verify(self, manager, uri, response, respdata, args): # @UnusedVariable # Must have status 200 if response.status != 200: return False, " HTTP Status Code Wrong: %d" % (response.status,) # Get expected FREEBUSY info users = args.get("attendee", []) busy = args.get("busy", []) tentative = args.get("tentative", []) unavailable = args.get("unavailable", []) # Extract each calendar-data object try: tree = ElementTree(file=StringIO.StringIO(respdata)) except ExpatError: return False, " Could not parse proper XML response\n" for calendar in tree.findall("./{urn:ietf:params:xml:ns:caldav}response/{urn:ietf:params:xml:ns:caldav}calendar-data"): # Parse data as calendar object try: calendar = Calendar.parseText(calendar.text) # Check for calendar if calendar is None: raise ValueError("Not a calendar: %s" % (calendar,)) # Only one component comps = calendar.getComponents("VFREEBUSY") if len(comps) != 1: raise ValueError("Wrong number or unexpected components in calendar") # Must be VFREEBUSY fb = comps[0] # Check for attendee value for attendee in fb.getProperties("ATTENDEE"): if attendee.getValue().getValue() in users: users.remove(attendee.getValue().getValue()) break else: continue # Extract periods busyp = [] tentativep = [] unavailablep = [] for fp in fb.getProperties("FREEBUSY"): periods = fp.getValue().getValues() # Convert start/duration to start/end for i in range(len(periods)): periods[i].getValue().setUseDuration(False) # Check param fbtype = "BUSY" if fp.hasParameter("FBTYPE"): fbtype = fp.getParameterValue("FBTYPE") if fbtype == "BUSY": busyp.extend(periods) elif fbtype == "BUSY-TENTATIVE": tentativep.extend(periods) elif fbtype == "BUSY-UNAVAILABLE": unavailablep.extend(periods) else: raise ValueError("Unknown FBTYPE: %s" % (fbtype,)) # Set sizes must match if ( (len(busy) != len(busyp)) or (len(unavailable) != len(unavailablep)) or (len(tentative) != len(tentativep)) ): raise ValueError("Period list sizes do not match.") # Convert to string sets busy = set(busy) busyp = [x.getValue().getText() for x in busyp] busyp = set(busyp) tentative = set(tentative) tentativep = [x.getValue().getText() for x in tentativep] tentativep = set(tentativep) unavailable = set(unavailable) unavailablep = [x.getValue().getText() for x in unavailablep] unavailablep = set(unavailablep) # Compare all periods if len(busyp.symmetric_difference(busy)): raise ValueError("Busy periods do not match") elif len(tentativep.symmetric_difference(tentative)): raise ValueError("Busy-tentative periods do not match") elif len(unavailablep.symmetric_difference(unavailable)): raise ValueError("Busy-unavailable periods do not match") break except InvalidData: return False, " HTTP response data is not a calendar" except ValueError, txt: return False, " HTTP response data is invalid: %s" % (txt,) except Exception, e: return False, " Response data is not calendar data: %s" % (e,)
def testOffsets(self): data = ( ("""BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN BEGIN:VTIMEZONE TZID:America/New_York X-LIC-LOCATION:America/New_York BEGIN:STANDARD DTSTART:18831118T120358 RDATE:18831118T120358 TZNAME:EST TZOFFSETFROM:-045602 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19180331T020000 RRULE:FREQ=YEARLY;UNTIL=19190330T070000Z;BYDAY=-1SU;BYMONTH=3 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19181027T020000 RRULE:FREQ=YEARLY;UNTIL=19191026T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:STANDARD DTSTART:19200101T000000 RDATE:19200101T000000 RDATE:19420101T000000 RDATE:19460101T000000 RDATE:19670101T000000 TZNAME:EST TZOFFSETFROM:-0500 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19200328T020000 RDATE:19200328T020000 RDATE:19740106T020000 RDATE:19750223T020000 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19201031T020000 RDATE:19201031T020000 RDATE:19450930T020000 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19210424T020000 RRULE:FREQ=YEARLY;UNTIL=19410427T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19210925T020000 RRULE:FREQ=YEARLY;UNTIL=19410928T060000Z;BYDAY=-1SU;BYMONTH=9 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19420209T020000 RDATE:19420209T020000 TZNAME:EWT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19450814T190000 RDATE:19450814T190000 TZNAME:EPT TZOFFSETFROM:-0400 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19460428T020000 RRULE:FREQ=YEARLY;UNTIL=19660424T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19460929T020000 RRULE:FREQ=YEARLY;UNTIL=19540926T060000Z;BYDAY=-1SU;BYMONTH=9 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:STANDARD DTSTART:19551030T020000 RRULE:FREQ=YEARLY;UNTIL=19661030T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19670430T020000 RRULE:FREQ=YEARLY;UNTIL=19730429T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19671029T020000 RRULE:FREQ=YEARLY;UNTIL=20061029T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19760425T020000 RRULE:FREQ=YEARLY;UNTIL=19860427T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19870405T020000 RRULE:FREQ=YEARLY;UNTIL=20060402T070000Z;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:20070311T020000 RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20071104T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE END:VCALENDAR """, ( (DateTime(1942, 2, 8), False, -5), (DateTime(1942, 2, 10), False, -4), (DateTime(2011, 1, 1), False, -5), (DateTime(2011, 4, 1), False, -4), (DateTime(2011, 10, 24), False, -4), (DateTime(2011, 11, 8), False, -5), (DateTime(2006, 1, 1), False, -5), (DateTime(2006, 4, 1), False, -5), (DateTime(2006, 5, 1), False, -4), (DateTime(2006, 10, 1), False, -4), (DateTime(2006, 10, 24), False, -4), (DateTime(2006, 11, 8), False, -5), (DateTime(2014, 3, 8, 23, 0, 0), False, -5), (DateTime(2014, 3, 9, 0, 0, 0), False, -5), (DateTime(2014, 3, 9, 3, 0, 0), False, -4), (DateTime(2014, 3, 9, 8, 0, 0), False, -4), (DateTime(2014, 3, 8, 23, 0, 0), True, -5), (DateTime(2014, 3, 9, 0, 0, 0), True, -5), (DateTime(2014, 3, 9, 3, 0, 0), True, -5), (DateTime(2014, 3, 9, 8, 0, 0), True, -4), (DateTime(2014, 11, 1, 23, 0, 0), False, -4), (DateTime(2014, 11, 2, 0, 0, 0), False, -4), (DateTime(2014, 11, 2, 3, 0, 0), False, -5), (DateTime(2014, 11, 2, 8, 0, 0), False, -5), (DateTime(2014, 11, 1, 23, 0, 0), True, -4), (DateTime(2014, 11, 2, 0, 0, 0), True, -4), (DateTime(2014, 11, 2, 3, 0, 0), True, -4), (DateTime(2014, 11, 2, 8, 0, 0), True, -5), )), ("""BEGIN:VCALENDAR CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:Etc/GMT+8 X-LIC-LOCATION:Etc/GMT+8 BEGIN:STANDARD DTSTART:18000101T000000 RDATE:18000101T000000 TZNAME:GMT+8 TZOFFSETFROM:-0800 TZOFFSETTO:-0800 END:STANDARD END:VTIMEZONE END:VCALENDAR """, ( (DateTime(1942, 2, 8), False, -8), (DateTime(1942, 2, 10), False, -8), (DateTime(2011, 1, 1), False, -8), (DateTime(2011, 4, 1), False, -8), )), ) for tzdata, offsets in data: cal = Calendar.parseText(tzdata.replace("\n", "\r\n")) tz = cal.getComponents()[0] for dt, relative_to_utc, offset in offsets: tzoffset = tz.getTimezoneOffsetSeconds(dt, relative_to_utc) self.assertEqual( tzoffset, offset * 60 * 60, "Failed to match offset for %s at %s with caching" % ( tz.getID(), dt, )) for dt, relative_to_utc, offset in reversed(offsets): tzoffset = tz.getTimezoneOffsetSeconds(dt, relative_to_utc) self.assertEqual( tzoffset, offset * 60 * 60, "Failed to match offset for %s at %s with caching, reversed" % ( tz.getID(), dt, )) for dt, relative_to_utc, offset in offsets: tz.mCachedExpandAllMax = None tzoffset = tz.getTimezoneOffsetSeconds(dt, relative_to_utc) self.assertEqual( tzoffset, offset * 60 * 60, "Failed to match offset for %s at %s without caching" % ( tz.getID(), dt, )) for dt, relative_to_utc, offset in reversed(offsets): tz.mCachedExpandAllMax = None tzoffset = tz.getTimezoneOffsetSeconds(dt, relative_to_utc) self.assertEqual( tzoffset, offset * 60 * 60, "Failed to match offset for %s at %s without caching, reversed" % ( tz.getID(), dt, ))