diff -Nru pycalendar-2.0~svn188/debian/changelog pycalendar-2.0~svn13177/debian/changelog --- pycalendar-2.0~svn188/debian/changelog 2012-06-08 10:39:26.000000000 +0000 +++ pycalendar-2.0~svn13177/debian/changelog 2014-04-15 21:17:50.000000000 +0000 @@ -1,3 +1,21 @@ +pycalendar (2.0~svn13177-1) unstable; urgency=high + + * Imported upstream svn branch CalendarServer-5.2 revision 13177 + + -- Rahul Amaram Mon, 14 Apr 2014 06:53:35 +0530 + +pycalendar (2.0~svn11458-1) unstable; urgency=medium + + * Imported upstream svn trunk revision 11458 + + -- Rahul Amaram Sun, 02 Mar 2014 18:26:02 +0530 + +pycalendar (2.0~svn11005-1) unstable; urgency=low + + * Imported upstream svn trunk revision 11005 + + -- Rahul Amaram Fri, 01 Nov 2013 20:55:00 +0530 + pycalendar (2.0~svn188-1) unstable; urgency=low * Initial release (Closes: #675018) diff -Nru pycalendar-2.0~svn188/debian/compat pycalendar-2.0~svn13177/debian/compat --- pycalendar-2.0~svn188/debian/compat 2012-06-08 10:39:26.000000000 +0000 +++ pycalendar-2.0~svn13177/debian/compat 2013-11-01 15:26:16.000000000 +0000 @@ -1 +1 @@ -8 +9 diff -Nru pycalendar-2.0~svn188/debian/control pycalendar-2.0~svn13177/debian/control --- pycalendar-2.0~svn188/debian/control 2012-06-08 10:39:26.000000000 +0000 +++ pycalendar-2.0~svn13177/debian/control 2014-04-14 02:38:12.000000000 +0000 @@ -2,10 +2,9 @@ Section: python Priority: optional Maintainer: Rahul Amaram -Build-Depends: debhelper (>= 8.0.0), python (>= 2.6.6-3~) -Standards-Version: 3.9.3 -Homepage: http://svn.mulberrymail.com/repos/PyCalendar/ -Vcs-Svn: http://svn.mulberrymail.com/repos/PyCalendar/branches/server +Build-Depends: debhelper (>= 9), python +Standards-Version: 3.9.5 +Homepage: http://trac.calendarserver.org/wiki/PyCalendar Package: python-pycalendar Architecture: all @@ -17,3 +16,6 @@ The library includes options to validate input data, and also output data in XML format. The package also includes a tool to parse Olson timezone database information to generate iCalendar compatible timezone definitions. + . + For compatibility with calendarserver 5.2, this revision is exported from the + branch ^/PyCalendar/branches/CalendarServer-5.2. diff -Nru pycalendar-2.0~svn188/debian/copyright pycalendar-2.0~svn13177/debian/copyright --- pycalendar-2.0~svn188/debian/copyright 2012-06-08 10:39:26.000000000 +0000 +++ pycalendar-2.0~svn13177/debian/copyright 2013-11-01 15:44:02.000000000 +0000 @@ -1,13 +1,13 @@ Format: http://dep.debian.net/deps/dep5 Upstream-Name: PyCalendar -Source: http://svn.mulberrymail.com/repos/PyCalendar/branches/server +Source: http://svn.calendarserver.org/repository/calendarserver/PyCalendar/trunk Files: * Copyright: 2007-2011 Cyrus Daboo License: Apache-2.0 Files: debian/* -Copyright: 2012 Rahul Amaram +Copyright: 2012-2013 Rahul Amaram License: Apache-2.0 License: Apache-2.0 diff -Nru pycalendar-2.0~svn188/.pydevproject pycalendar-2.0~svn13177/.pydevproject --- pycalendar-2.0~svn188/.pydevproject 2009-07-21 19:38:12.000000000 +0000 +++ pycalendar-2.0~svn13177/.pydevproject 2014-02-19 19:56:40.000000000 +0000 @@ -1,10 +1,8 @@ - - - -python 2.5 + +python 2.7 -/PyCalendar/src +/${PROJECT_DIR_NAME}/src Default diff -Nru pycalendar-2.0~svn188/src/pycalendar/adr.py pycalendar-2.0~svn13177/src/pycalendar/adr.py --- pycalendar-2.0~svn188/src/pycalendar/adr.py 2011-04-27 15:38:06.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/adr.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -38,68 +38,90 @@ def __init__(self, pobox="", extended="", street="", locality="", region="", postalcode="", country=""): self.mValue = (pobox, extended, street, locality, region, postalcode, country) + def duplicate(self): return Adr(*self.mValue) + def __hash__(self): return hash(self.mValue) + def __repr__(self): return "ADR %s" % (self.getText(),) - def __eq__( self, comp ): + + def __eq__(self, comp): return self.mValue == comp.mValue + def getPobox(self): return self.mValue[Adr.POBOX] - + + def setPobox(self, value): self.mValue[Adr.POBOX] = value + def getExtended(self): return self.mValue[Adr.EXTENDED] - + + def setExtended(self, value): self.mValue[Adr.EXTENDED] = value + def getStreet(self): return self.mValue[Adr.STREET] - + + def setStreet(self, value): self.mValue[Adr.STREET] = value + def getLocality(self): return self.mValue[Adr.LOCALITY] - + + def setLocality(self, value): self.mValue[Adr.LOCALITY] = value + def getRegion(self): return self.mValue[Adr.REGION] - + + def setRegion(self, value): self.mValue[Adr.REGION] = value + def getPostalCode(self): return self.mValue[Adr.POSTALCODE] - + + def setPostalCode(self, value): self.mValue[Adr.POSTALCODE] = value + def getCountry(self): return self.mValue[Adr.COUNTRY] - + + def setCountry(self, value): self.mValue[Adr.COUNTRY] = value + def parse(self, data): self.mValue = utils.parseDoubleNestedList(data, Adr.MAXITEMS) + def generate(self, os): utils.generateDoubleNestedList(os, self.mValue) + def getValue(self): return self.mValue + def setValue(self, value): self.mValue = value diff -Nru pycalendar-2.0~svn188/src/pycalendar/adrvalue.py pycalendar-2.0~svn13177/src/pycalendar/adrvalue.py --- pycalendar-2.0~svn188/src/pycalendar/adrvalue.py 2011-05-31 19:32:49.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/adrvalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,26 +19,32 @@ from pycalendar.adr import Adr from pycalendar.value import PyCalendarValue -class AdrValue( PyCalendarValue ): +class AdrValue(PyCalendarValue): - def __init__(self, value = None): + def __init__(self, value=None): self.mValue = value if value is not None else Adr() + def duplicate(self): return AdrValue(self.mValue.duplicate()) + def getType(self): return PyCalendarValue.VALUETYPE_ADR + def parse(self, data): self.mValue.parse(data) + def generate(self, os): self.mValue.generate(os) + def getValue(self): return self.mValue + def setValue(self, value): self.mValue = value diff -Nru pycalendar-2.0~svn188/src/pycalendar/attribute.py pycalendar-2.0~svn13177/src/pycalendar/attribute.py --- pycalendar-2.0~svn188/src/pycalendar/attribute.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/attribute.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,12 +19,14 @@ The attribute can consist of one or more values, all string. """ -import xml.etree.cElementTree as XML + from pycalendar import xmldefs +from pycalendar.utils import encodeParameterValue +import xml.etree.cElementTree as XML class PyCalendarAttribute(object): - def __init__(self, name, value = None): + def __init__(self, name, value=None): self.mName = name if value is None: self.mValues = [] @@ -33,70 +35,91 @@ else: self.mValues = value + def duplicate(self): - other = PyCalendarAttribute(self.mName, [i for i in self.mValues]) + other = PyCalendarAttribute(self.mName) other.mValues = self.mValues[:] return other + def __hash__(self): return hash((self.mName.upper(), tuple(self.mValues))) - def __ne__(self, other): return not self.__eq__(other) + + def __ne__(self, other): + return not self.__eq__(other) + + def __eq__(self, other): - if not isinstance(other, PyCalendarAttribute): return False + if not isinstance(other, PyCalendarAttribute): + return False return self.mName.upper() == other.mName.upper() and self.mValues == other.mValues + def getName(self): return self.mName + def setName(self, name): self.mName = name + def getFirstValue(self): return self.mValues[0] + def getValues(self): return self.mValues + def setValues(self, values): self.mValues = values + def addValue(self, value): self.mValues.append(value) + def removeValue(self, value): self.mValues.remove(value) return len(self.mValues) + def generate(self, os): try: os.write(self.mName) - + # To support vCard 2.1 syntax we allow parameters without values if self.mValues: os.write("=") - + first = True for s in self.mValues: if first: first = False else: os.write(",") - + # Write with quotation if required self.generateValue(os, s) except: # We ignore errors pass - + + def generateValue(self, os, str): + + # ^-escaping + str = encodeParameterValue(str) + # Look for quoting if str.find(":") != -1 or str.find(";") != -1 or str.find(",") != -1: os.write("\"%s\"" % (str,)) else: os.write(str) + def writeXML(self, node, namespace): param = XML.SubElement(node, xmldefs.makeTag(namespace, self.getName())) for value in self.getValues(): diff -Nru pycalendar-2.0~svn188/src/pycalendar/available.py pycalendar-2.0~svn13177/src/pycalendar/available.py --- pycalendar-2.0~svn188/src/pycalendar/available.py 2011-06-30 16:03:45.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/available.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -45,19 +45,22 @@ def __init__(self, parent=None): super(PyCalendarAvailable, self).__init__(parent=parent) + def duplicate(self, parent=None): return super(PyCalendarAvailable, self).duplicate(parent=parent) + def getType(self): return definitions.cICalComponent_AVAILABLE + def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems, else raise. If loggedProblems is not None it must be a C{list} and problem descriptions are appended - to that. + to that. """ - + fixed, unfixed = super(PyCalendarAvailable, self).validate(doFix) # Extra constraint: only one of DTEND or DURATION @@ -73,9 +76,10 @@ fixed.append(logProblem) else: unfixed.append(logProblem) - + return fixed, unfixed - + + def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, diff -Nru pycalendar-2.0~svn188/src/pycalendar/binaryvalue.py pycalendar-2.0~svn13177/src/pycalendar/binaryvalue.py --- pycalendar-2.0~svn188/src/pycalendar/binaryvalue.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/binaryvalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,11 +20,9 @@ from pycalendar.plaintextvalue import PyCalendarPlainTextValue from pycalendar.value import PyCalendarValue -class PyCalendarBinaryValue( PyCalendarPlainTextValue ): +class PyCalendarBinaryValue(PyCalendarPlainTextValue): def getType(self): return PyCalendarValue.VALUETYPE_BINARY PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_BINARY, PyCalendarBinaryValue, xmldefs.value_binary) - - \ No newline at end of file diff -Nru pycalendar-2.0~svn188/src/pycalendar/caladdressvalue.py pycalendar-2.0~svn13177/src/pycalendar/caladdressvalue.py --- pycalendar-2.0~svn188/src/pycalendar/caladdressvalue.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/caladdressvalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,9 +20,9 @@ from pycalendar.plaintextvalue import PyCalendarPlainTextValue from pycalendar.value import PyCalendarValue -class PyCalendarCalAddressValue( PyCalendarPlainTextValue ): +class PyCalendarCalAddressValue(PyCalendarPlainTextValue): - def getType( self ): + def getType(self): return PyCalendarValue.VALUETYPE_CALADDRESS PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_CALADDRESS, PyCalendarCalAddressValue, xmldefs.value_cal_address) diff -Nru pycalendar-2.0~svn188/src/pycalendar/calendar.py pycalendar-2.0~svn13177/src/pycalendar/calendar.py --- pycalendar-2.0~svn188/src/pycalendar/calendar.py 2011-06-30 16:03:45.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/calendar.py 2014-04-03 02:27:44.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -21,7 +21,7 @@ from pycalendar.componentexpanded import PyCalendarComponentExpanded from pycalendar.componentrecur import PyCalendarComponentRecur from pycalendar.datetime import PyCalendarDateTime -from pycalendar.exceptions import PyCalendarInvalidData,\ +from pycalendar.exceptions import PyCalendarInvalidData, \ PyCalendarValidationError from pycalendar.freebusy import PyCalendarFreeBusy from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS @@ -58,6 +58,7 @@ def setPRODID(prodid): PyCalendar.sProdID = prodid + @staticmethod def setDomain(domain): PyCalendar.sDomain = domain @@ -79,58 +80,66 @@ self.mName = "" self.mDescription = "" - self.mMasterComponentsByTypeAndUID = collections.defaultdict(lambda:collections.defaultdict(list)) + self.mMasterComponentsByTypeAndUID = collections.defaultdict(lambda: collections.defaultdict(list)) self.mOverriddenComponentsByUID = collections.defaultdict(list) - + if add_defaults: self.addDefaultProperties() - + + def duplicate(self): other = super(PyCalendar, self).duplicate() other.mName = self.mName other.mDescription = self.mDescription return other + def getType(self): return definitions.cICalComponent_VCALENDAR + def getName(self): return self.mName - + + def setName(self, name): self.mName = name + def editName(self, name): if self.mName != name: # Updated cached value self.mName = name - + # Remove existing items self.removeProperties(definitions.cICalProperty_XWRCALNAME) - + # Now create properties if len(name): self.ddProperty(PyCalendarProperty(definitions.cICalProperty_XWRCALNAME, name)) - + def getDescription(self): return self.mDescription - + + def setDescription(self, description): self.mDescription = description + def editDescription(self, description): if self.mDescription != description: # Updated cached value self.mDescription = description - + # Remove existing items self.removeProperties(definitions.cICalProperty_XWRCALDESC) - + # Now create properties if len(description): self.addProperty(PyCalendarProperty(definitions.cICalProperty_XWRCALDESC, description)) + def getMethod(self): result = "" if self.hasProperty(definitions.cICalProperty_METHOD): @@ -138,19 +147,47 @@ return result + def changeUID(self, oldUID, newUID): + """ + Change the UID of all components with a matching UID to a new value. We need to + do this at the calendar level because this object maintains mappings based on UID + which need to be updated whenever the UID changes. + + @param oldUID: the old value to match + @type oldUID: C{str} + @param newUID: the new value to match + @type newUID: C{str} + """ + + # Each component + for component in self.mComponents: + if component.getUID() == oldUID: + component.setUID(newUID) + + # Maps + if oldUID in self.mOverriddenComponentsByUID: + self.mOverriddenComponentsByUID[newUID] = self.mOverriddenComponentsByUID[oldUID] + del self.mOverriddenComponentsByUID[oldUID] + for ctype in self.mMasterComponentsByTypeAndUID: + if oldUID in self.mMasterComponentsByTypeAndUID[ctype]: + self.mMasterComponentsByTypeAndUID[ctype][newUID] = self.mMasterComponentsByTypeAndUID[ctype][oldUID] + del self.mMasterComponentsByTypeAndUID[ctype][oldUID] + + def finalise(self): # Get calendar name if present - + # Get X-WR-CALNAME temps = self.loadValueString(definitions.cICalProperty_XWRCALNAME) if temps is not None: self.mName = temps - + # Get X-WR-CALDESC temps = self.loadValueString(definitions.cICalProperty_XWRCALDESC) if temps is not None: self.mDescription = temps + def validate(self, doFix=False, doRaise=False): """ Validate the data in this component and optionally fix any problems. Return @@ -158,13 +195,14 @@ second problems that were not fixed. Caller can then decide what to do with unfixed issues. """ - + # Optional raise behavior fixed, unfixed = super(PyCalendar, self).validate(doFix) if doRaise and unfixed: raise PyCalendarValidationError(";".join(unfixed)) return fixed, unfixed + def sortedComponentNames(self): return ( definitions.cICalComponent_VTIMEZONE, @@ -175,6 +213,7 @@ definitions.cICalComponent_VAVAILABILITY, ) + def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_VERSION, @@ -183,34 +222,36 @@ definitions.cICalProperty_PRODID, ) + @staticmethod def parseText(data): - + cal = PyCalendar(add_defaults=False) if cal.parse(StringIO(data)): return cal else: return None + def parse(self, ins): result = False - + self.setProperties({}) - + LOOK_FOR_VCALENDAR = 0 GET_PROPERTY_OR_COMPONENT = 1 - + state = LOOK_FOR_VCALENDAR - + # Get lines looking for start of calendar lines = [None, None] comp = self compend = None componentstack = [] - + while readFoldedLine(ins, lines): - + line = lines[0] if state == LOOK_FOR_VCALENDAR: @@ -219,7 +260,7 @@ if line == self.getBeginDelimiter(): # Next state state = GET_PROPERTY_OR_COMPONENT - + # Indicate success at this point result = True @@ -228,7 +269,7 @@ # Raise if requested, otherwise just ignore if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE: raise PyCalendarInvalidData("iCalendar data has blank lines") - + # Unrecognized data else: raise PyCalendarInvalidData("iCalendar data not recognized", line) @@ -250,7 +291,7 @@ # Finalise the current calendar self.finalise() - + # Change state state = LOOK_FOR_VCALENDAR @@ -259,11 +300,11 @@ # Finalise the component (this caches data from the properties) comp.finalise() - + # Embed component in parent and reset to use parent componentstack[-1][0].addComponent(comp) comp, compend = componentstack.pop() - + # Blank line elif len(line) == 0: # Raise if requested, otherwise just ignore @@ -285,38 +326,39 @@ comp.addProperty(prop) else: comp.addProperty(prop) - + # Check for truncated data if state != LOOK_FOR_VCALENDAR: raise PyCalendarInvalidData("iCalendar data not complete") - + # We need to store all timezones in the static object so they can be accessed by any date object from timezonedb import PyCalendarTimezoneDatabase PyCalendarTimezoneDatabase.mergeTimezones(self, self.getComponents(definitions.cICalComponent_VTIMEZONE)) - + # Validate some things if result and not self.hasProperty("VERSION"): raise PyCalendarInvalidData("iCalendar missing VERSION") return result - + + def parseComponent(self, ins): - + result = None - + LOOK_FOR_VCALENDAR = 0 GET_PROPERTY_OR_COMPONENT = 1 GOT_VCALENDAR = 4 - + state = LOOK_FOR_VCALENDAR - + # Get lines looking for start of calendar lines = [None, None] comp = self compend = None componentstack = [] got_timezone = False - + while readFoldedLine(ins, lines): if state == LOOK_FOR_VCALENDAR: @@ -331,7 +373,7 @@ # Raise if requested, otherwise just ignore if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE: raise PyCalendarInvalidData("iCalendar data has blank lines") - + # Unrecognized data else: raise PyCalendarInvalidData("iCalendar data not recognized", lines[0]) @@ -347,11 +389,11 @@ # Start a new component comp = PyCalendar.makeComponent(lines[0][6:], comp) compend = comp.getEndDelimiter() - + # Cache as result - but only the first one, we ignore the rest if result is None: result = comp - + # Look for timezone component to trigger timezone merge only if one is present if comp.getType() == definitions.cICalComponent_VTIMEZONE: got_timezone = True @@ -366,11 +408,11 @@ # Finalise the component (this caches data from the properties) comp.finalise() - + # Embed component in parent and reset to use parent componentstack[-1][0].addComponent(comp) comp, compend = componentstack.pop() - + # Blank line elif len(lines[0]) == 0: # Raise if requested, otherwise just ignore @@ -391,25 +433,26 @@ # Check for valid property if comp is not self: comp.addProperty(prop) - + # Exit if we have one - ignore all the rest if state == GOT_VCALENDAR: break - + # We need to store all timezones in the static object so they can be accessed by any date object # Only do this if we read in a timezone if got_timezone: from timezonedb import PyCalendarTimezoneDatabase PyCalendarTimezoneDatabase.mergeTimezones(self, self.getComponents(definitions.cICalComponent_VTIMEZONE)) - + return result - + + def addComponent(self, component): """ Override to track components by UID. """ super(PyCalendar, self).addComponent(component) - + if isinstance(component, PyCalendarComponentRecur): uid = component.getUID() rid = component.getRecurrenceID() @@ -418,6 +461,7 @@ else: self.mMasterComponentsByTypeAndUID[component.getType()][uid] = component + def removeComponent(self, component): """ Override to track components by UID. @@ -432,51 +476,57 @@ else: del self.mMasterComponentsByTypeAndUID[component.getType()][uid] + def getText(self, includeTimezones=False): s = StringIO() self.generate(s, includeTimezones=includeTimezones) return s.getvalue() + def generate(self, os, includeTimezones=False): # Make sure all required timezones are in this object if includeTimezones: self.includeTimezones() super(PyCalendar, self).generate(os) - + + def getTextXML(self, includeTimezones=False): node = self.writeXML(includeTimezones) return xmldefs.toString(node) + def writeXML(self, includeTimezones=False): # Make sure all required timezones are in this object if includeTimezones: self.includeTimezones() - + # Root node structure root = XML.Element(xmldefs.makeTag(xmldefs.iCalendar20_namespace, xmldefs.icalendar)) super(PyCalendar, self).writeXML(root, xmldefs.iCalendar20_namespace) return root - + + # Get expanded components - def getVEvents(self, period, list, all_day_at_top = True): + def getVEvents(self, period, list, all_day_at_top=True): # Look at each VEvent for vevent in self.getComponents(definitions.cICalComponent_VEVENT): vevent.expandPeriod(period, list) - + if (all_day_at_top): list.sort(PyCalendarComponentExpanded.sort_by_dtstart_allday) else: list.sort(PyCalendarComponentExpanded.sort_by_dtstart) - + + def getVToDos(self, only_due, all_dates, upto_due_date, list): # Get current date-time less one day to test for completed events during the last day minusoneday = PyCalendarDateTime() minusoneday.setNowUTC() minusoneday.offsetDay(-1) - + today = PyCalendarDateTime() today.setToday() - + # Look at each VToDo for vtodo in self.getComponents(definitions.cICalComponent_VTODO): @@ -487,31 +537,35 @@ elif ((vtodo.getStatus() == definitions.eStatus_VToDo_Completed) and (not vtodo.hasCompleted() or (vtodo.getCompleted() < minusoneday))): continue - + # Filter out those with end after chosen date if required if not all_dates: if vtodo.hasEnd() and (vtodo.getEnd() > upto_due_date): continue elif not vtodo.hasEnd() and (today > upto_due_date): continue - + # TODO: fix this #list.append(PyCalendarComponentExpandedShared(PyCalendarComponentExpanded(vtodo, None))) - + + def getRecurrenceInstancesItems(self, type, uid, items): # Get instances from list items.extend(self.mOverriddenComponentsByUID.get(uid, ())) + def getRecurrenceInstancesIds(self, type, uid, ids): # Get instances from list ids.extend([comp.getRecurrenceID() for comp in self.mOverriddenComponentsByUID.get(uid, ())]) + # Freebusy generation def getVFreeBusyList(self, period, list): # Look at each VFreeBusy for vfreebusy in self.getComponents(definitions.cICalComponent_VFREEBUSY): vfreebusy.expandPeriod(period, list) - + + def getVFreeBusyFB(self, period, fb): # First create expanded set # TODO: fix this @@ -519,7 +573,7 @@ self.getVEvents(period, list) if len(list) == 0: return - + # Get start/end list for each non-all-day expanded components dtstart = [] dtend = [] @@ -528,19 +582,19 @@ # Ignore if all-day if dt.getInstanceStart().isDateOnly(): continue - + # Ignore if transparent to free-busy transp = "" if dt.getOwner().getProperty(definitions.cICalProperty_TRANSP, transp) and (transp == definitions.cICalProperty_TRANSPARENT): continue - + # Add start/end to list dtstart.append(dt.getInstanceStart()) dtend.append(dt.getInstanceEnd()) - + # No longer need the expanded items list.clear() - + # Create non-overlapping periods as properties in the freebusy component temp = PyCalendarPeriod(dtstart.front(), dtend.front()) dtstart_iter = dtstart.iter() @@ -554,37 +608,38 @@ # Current period is complete fb.addProperty(PyCalendarProperty(definitions.cICalProperty_FREEBUSY, temp)) - + # Reset period to new range temp = PyCalendarPeriod(dtstart_iter, dtend_iter) - + # They overlap - check for extended end if dtend_iter > temp.getEnd(): # Extend the end temp = PyCalendarPeriod(temp.getStart(), dtend_iter) - + # Add remaining period as property fb.addProperty(PyCalendarProperty(definitions.cICalProperty_FREEBUSY, temp)) + def getFreeBusy(self, period, fb): # First create expanded set list = [] self.getVEvents(period, list) - + # Get start/end list for each non-all-day expanded components for comp in list: # Ignore if all-day if comp.getInstanceStart().isDateOnly(): continue - + # Ignore if transparent to free-busy transp = "" if comp.getOwner().getProperty(definitions.cICalProperty_TRANSP, transp) and (transp == definitions.cICalProperty_TRANSPARENT): continue - + # Add free busy item to list status = comp.getMaster().getStatus() if status in (definitions.eStatus_VEvent_None, definitions.eStatus_VEvent_Confirmed): @@ -599,26 +654,29 @@ # Now get the VFREEBUSY info list2 = [] self.getVFreeBusy(period, list2) - + # Get start/end list for each free-busy for comp in list2: # Expand component and add free busy info to list comp.expandPeriod(period, fb) - + # Add remaining period as property PyCalendarFreeBusy.resolveOverlaps(fb) - - def getTimezoneOffsetSeconds(self, tzid, dt): + + + def getTimezoneOffsetSeconds(self, tzid, dt, relative_to_utc=False): # Find timezone that matches the name (which is the same as the map key) timezone = self.getTimezone(tzid) - return timezone.getTimezoneOffsetSeconds(dt) if timezone else 0 + return timezone.getTimezoneOffsetSeconds(dt, relative_to_utc) if timezone else 0 + def getTimezoneDescriptor(self, tzid, dt): # Find timezone that matches the name (which is the same as the map key) timezone = self.getTimezone(tzid) return timezone.getTimezoneDescriptor(dt) if timezone else "" + def getTimezone(self, tzid): # Find timezone that matches the name (which is the same as the map key) for timezone in self.getComponents(definitions.cICalComponent_VTIMEZONE): @@ -627,11 +685,13 @@ else: return None + def addDefaultProperties(self): self.addProperty(PyCalendarProperty(definitions.cICalProperty_PRODID, PyCalendar.sProdID)) self.addProperty(PyCalendarProperty(definitions.cICalProperty_VERSION, "2.0")) self.addProperty(PyCalendarProperty(definitions.cICalProperty_CALSCALE, "GREGORIAN")) - + + def validProperty(self, prop): if prop.getName() == definitions.cICalProperty_VERSION: @@ -646,17 +706,19 @@ return False return True - + + def ignoreProperty(self, prop): return False #prop.getName() in (definitions.cICalProperty_VERSION, definitions.cICalProperty_CALSCALE, definitions.cICalProperty_PRODID) + def includeTimezones(self): # Get timezone names from each component tzids = set() for component in self.mComponents: if component.getType() != definitions.cICalComponent_VTIMEZONE: component.getTimezones(tzids) - + # Make sure each timezone is in current calendar from timezonedb import PyCalendarTimezoneDatabase for tzid in tzids: @@ -667,23 +729,24 @@ if tz is not None: dup = tz.duplicate() self.addComponent(dup) - + + @staticmethod def makeComponent(compname, parent): mapper = { - definitions.cICalComponent_VEVENT:PyCalendarVEvent, - definitions.cICalComponent_VTODO:PyCalendarVToDo, - definitions.cICalComponent_VJOURNAL:PyCalendarVJournal, - definitions.cICalComponent_VFREEBUSY:PyCalendarVFreeBusy, - definitions.cICalComponent_VTIMEZONE:PyCalendarVTimezone, - definitions.cICalComponent_VAVAILABILITY:PyCalendarVAvailability, - definitions.cICalComponent_VALARM:PyCalendarVAlarm, - definitions.cICalComponent_AVAILABLE:PyCalendarAvailable, - definitions.cICalComponent_STANDARD:PyCalendarVTimezoneStandard, - definitions.cICalComponent_DAYLIGHT:PyCalendarVTimezoneDaylight, + definitions.cICalComponent_VEVENT: PyCalendarVEvent, + definitions.cICalComponent_VTODO: PyCalendarVToDo, + definitions.cICalComponent_VJOURNAL: PyCalendarVJournal, + definitions.cICalComponent_VFREEBUSY: PyCalendarVFreeBusy, + definitions.cICalComponent_VTIMEZONE: PyCalendarVTimezone, + definitions.cICalComponent_VAVAILABILITY: PyCalendarVAvailability, + definitions.cICalComponent_VALARM: PyCalendarVAlarm, + definitions.cICalComponent_AVAILABLE: PyCalendarAvailable, + definitions.cICalComponent_STANDARD: PyCalendarVTimezoneStandard, + definitions.cICalComponent_DAYLIGHT: PyCalendarVTimezoneDaylight, } - + try: cls = mapper[compname] return cls(parent=parent) diff -Nru pycalendar-2.0~svn188/src/pycalendar/componentbase.py pycalendar-2.0~svn13177/src/pycalendar/componentbase.py --- pycalendar-2.0~svn188/src/pycalendar/componentbase.py 2011-10-26 20:50:37.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/componentbase.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -30,9 +30,11 @@ propertyCardinality_1_Fix_Empty = () # Must be present but can be fixed by adding an empty value propertyCardinality_0_1 = () # 0 or 1 only propertyCardinality_1_More = () # 1 or more - + propertyValueChecks = None # Either iCalendar or vCard validation + sortSubComponents = True + def __init__(self, parent=None): self.mParentComponent = parent self.mComponents = [] @@ -46,9 +48,10 @@ self.check_cardinality_1_More, ) + def duplicate(self, **args): other = self.__class__(**args) - + for component in self.mComponents: other.addComponent(component.duplicate(parent=other)) @@ -56,37 +59,50 @@ for propname, props in self.mProperties.iteritems(): other.mProperties[propname] = [i.duplicate() for i in props] return other - + + def __str__(self): return self.getText() - def __ne__(self, other): return not self.__eq__(other) + + def __ne__(self, other): + return not self.__eq__(other) + + def __eq__(self, other): - if not isinstance(other, PyCalendarComponentBase): return False + if not isinstance(other, PyCalendarComponentBase): + return False return self.getType() == other.getType() and self.compareProperties(other) and self.compareComponents(other) + def getType(self): raise NotImplementedError + def getBeginDelimiter(self): return "BEGIN:" + self.getType() + def getEndDelimiter(self): return "END:" + self.getType() + def getSortKey(self): return "" + def getParentComponent(self): return self.mParentComponent - + + def setParentComponent(self, parent): self.mParentComponent = parent + def compareComponents(self, other): mine = set(self.mComponents) theirs = set(other.mComponents) - + for item in mine: for another in theirs: if item == another: @@ -96,17 +112,20 @@ return False return len(theirs) == 0 + def getComponents(self, compname=None): compname = compname.upper() if compname else None return [component for component in self.mComponents if compname is None or component.getType().upper() == compname] - + + def getComponentByKey(self, key): for component in self.mComponents: if component.getMapKey() == key: return component else: return None - + + def removeComponentByKey(self, key): for component in self.mComponents: @@ -114,18 +133,23 @@ self.removeComponent(component) return + def addComponent(self, component): self.mComponents.append(component) + def hasComponent(self, compname): return self.countComponents(compname) != 0 + def countComponents(self, compname): return len(self.getComponents(compname)) + def removeComponent(self, component): self.mComponents.remove(component) + def removeAllComponent(self, compname=None): if compname: compname = compname.upper() @@ -135,9 +159,11 @@ else: self.mComponents = [] + def sortedComponentNames(self): return () + def compareProperties(self, other): mine = set() for props in self.mProperties.values(): @@ -147,46 +173,59 @@ theirs.update(props) return mine == theirs + def getProperties(self, propname=None): return self.mProperties.get(propname.upper(), []) if propname else self.mProperties + def setProperties(self, props): self.mProperties = props + def addProperty(self, prop): self.mProperties.setdefault(prop.getName().upper(), []).append(prop) + def hasProperty(self, propname): - return self.mProperties.has_key(propname.upper()) + return propname.upper() in self.mProperties + def countProperty(self, propname): return len(self.mProperties.get(propname.upper(), [])) + def findFirstProperty(self, propname): return self.mProperties.get(propname.upper(), [None])[0] + def removeProperty(self, prop): - if self.mProperties.has_key(prop.getName().upper()): + if prop.getName().upper() in self.mProperties: self.mProperties[prop.getName().upper()].remove(prop) if len(self.mProperties[prop.getName().upper()]) == 0: del self.mProperties[prop.getName().upper()] + def removeProperties(self, propname): - if self.mProperties.has_key(propname.upper()): + if propname.upper() in self.mProperties: del self.mProperties[propname.upper()] - def getPropertyInteger(self, prop, type = None): + + def getPropertyInteger(self, prop, type=None): return self.loadValueInteger(prop, type) + def getPropertyString(self, prop): return self.loadValueString(prop) + def getProperty(self, prop, value): return self.loadValue(prop, value) + def finalise(self): raise NotImplemented + def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems. Return @@ -194,14 +233,14 @@ second problems that were not fixed. Caller can then decide what to do with unfixed issues. """ - + fixed = [] unfixed = [] # Cardinality tests for check in self.cardinalityChecks: check(fixed, unfixed, doFix) - + # Value constraints - these tests come from class specific attributes if self.propertyValueChecks is not None: for properties in self.mProperties.values(): @@ -221,12 +260,14 @@ return fixed, unfixed + def check_cardinality_1(self, fixed, unfixed, doFix): for propname in self.propertyCardinality_1: if self.countProperty(propname) != 1: # Cannot fix a missing required property logProblem = "[%s] Missing or too many required property: %s" % (self.getType(), propname) unfixed.append(logProblem) + def check_cardinality_1_Fix_Empty(self, fixed, unfixed, doFix): for propname in self.propertyCardinality_1_Fix_Empty: if self.countProperty(propname) > 1: # Cannot fix too many required property @@ -240,23 +281,27 @@ else: unfixed.append(logProblem) + def check_cardinality_0_1(self, fixed, unfixed, doFix): for propname in self.propertyCardinality_0_1: if self.countProperty(propname) > 1: # Cannot be fixed - no idea which one to delete logProblem = "[%s] Too many properties present: %s" % (self.getType(), propname) unfixed.append(logProblem) + def check_cardinality_1_More(self, fixed, unfixed, doFix): for propname in self.propertyCardinality_1_More: if not self.countProperty(propname) > 0: # Cannot fix a missing required property logProblem = "[%s] Missing required property: %s" % (self.getType(), propname) unfixed.append(logProblem) + def getText(self): s = StringIO() self.generate(s) return s.getvalue() + def generate(self, os): # Header os.write(self.getBeginDelimiter()) @@ -272,6 +317,7 @@ os.write(self.getEndDelimiter()) os.write("\r\n") + def generateFiltered(self, os, filter): # Header os.write(self.getBeginDelimiter()) @@ -287,57 +333,66 @@ os.write(self.getEndDelimiter()) os.write("\r\n") + def writeXML(self, node, namespace): - + # Component element comp = XML.SubElement(node, xmldefs.makeTag(namespace, self.getType())) - + # Each property self.writePropertiesXML(comp, namespace) - + # Each component self.writeComponentsXML(comp, namespace) - + + def writeXMLFiltered(self, node, namespace, filter): # Component element comp = XML.SubElement(node, xmldefs.makeTag(namespace, self.getType())) - + # Each property self.writePropertiesFilteredXML(comp, namespace, filter) - + # Each component self.writeComponentsFilteredXML(comp, namespace, filter) + def sortedComponents(self): - + components = self.mComponents[:] sortedcomponents = [] # Write each component based on specific order orderedNames = self.sortedComponentNames() for name in orderedNames: - + # Group by name then sort by map key (UID/R-ID) namedcomponents = [] for component in tuple(components): if component.getType().upper() == name: namedcomponents.append(component) components.remove(component) - for component in sorted(namedcomponents, key=lambda x:x.getSortKey()): + for component in sorted(namedcomponents, key=lambda x: x.getSortKey()): sortedcomponents.append(component) - - # Write out the remainder - for component in components: + + # Write out the remainder sorted by name, sortKey + if self.sortSubComponents: + remainder = sorted(components, key=lambda x: (x.getType().upper(), x.getSortKey(),)) + else: + remainder = components + for component in remainder: sortedcomponents.append(component) - + return sortedcomponents - + + def writeComponents(self, os): - - # Write out the remainder + + # Write out the remainder for component in self.sortedComponents(): component.generate(os) - + + def writeComponentsFiltered(self, os, filter): # Shortcut for all sub-components if filter.isAllSubComponents(): @@ -347,21 +402,23 @@ subfilter = filter.getSubComponentFilter(subcomp.getType()) if subfilter is not None: subcomp.generateFiltered(os, subfilter) - + + def writeComponentsXML(self, node, namespace): - + if self.mComponents: comps = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.components)) - - # Write out the remainder + + # Write out the remainder for component in self.sortedComponents(): component.writeXML(comps, namespace) - + + def writeComponentsFilteredXML(self, node, namespace, filter): if self.mComponents: comps = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.components)) - + # Shortcut for all sub-components if filter.isAllSubComponents(): self.writeXML(comps, namespace) @@ -370,13 +427,15 @@ subfilter = filter.getSubComponentFilter(subcomp.getType()) if subfilter is not None: subcomp.writeFilteredXML(comps, namespace, subfilter) - + + def loadValue(self, value_name): if self.hasProperty(value_name): return self.findFirstProperty(value_name) return None + def loadValueInteger(self, value_name, type=None): if type: if self.hasProperty(value_name): @@ -388,11 +447,12 @@ uvalue = self.findFirstProperty(value_name).getUTCOffsetValue() if (uvalue is not None): return uvalue.getValue() - + return None else: return self.loadValueInteger(value_name, PyCalendarValue.VALUETYPE_INTEGER) + def loadValueString(self, value_name): if self.hasProperty(value_name): tvalue = self.findFirstProperty(value_name).getTextValue() @@ -401,6 +461,7 @@ return None + def loadValueDateTime(self, value_name): if self.hasProperty(value_name): dtvalue = self.findFirstProperty(value_name).getDateTimeValue() @@ -409,6 +470,7 @@ return None + def loadValueDuration(self, value_name): if self.hasProperty(value_name): dvalue = self.findFirstProperty(value_name).getDurationValue() @@ -417,6 +479,7 @@ return None + def loadValuePeriod(self, value_name): if self.hasProperty(value_name): pvalue = self.findFirstProperty(value_name).getPeriodValue() @@ -425,6 +488,7 @@ return None + def loadValueRRULE(self, value_name, value, add): # Get RRULEs if self.hasProperty(value_name): @@ -440,6 +504,7 @@ else: return False + def loadValueRDATE(self, value_name, value, add): # Get RDATEs if self.hasProperty(value_name): @@ -463,10 +528,11 @@ else: return False + def sortedPropertyKeys(self): keys = self.mProperties.keys() keys.sort() - + results = [] for skey in self.sortedPropertyKeyOrder(): if skey in keys: @@ -475,9 +541,11 @@ results.extend(keys) return results + def sortedPropertyKeyOrder(self): return () + def writeProperties(self, os): # Sort properties by name keys = self.sortedPropertyKeys() @@ -486,6 +554,7 @@ for prop in props: prop.generate(os) + def writePropertiesFiltered(self, os, filter): # Sort properties by name @@ -501,10 +570,11 @@ for prop in self.getProperties(key): prop.generateFiltered(os, filter) + def writePropertiesXML(self, node, namespace): properties = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.properties)) - + # Sort properties by name keys = self.sortedPropertyKeys() for key in keys: @@ -512,10 +582,11 @@ for prop in props: prop.writeXML(properties, namespace) + def writePropertiesFilteredXML(self, node, namespace, filter): props = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.properties)) - + # Sort properties by name keys = self.sortedPropertyKeys() @@ -529,6 +600,7 @@ for prop in self.getProperties(key): prop.writeFilteredXML(props, namespace, filter) + def loadPrivateValue(self, value_name): # Read it in from properties list and then delete the property from the # main list @@ -537,10 +609,12 @@ self.removeProperties(value_name) return result + def writePrivateProperty(self, os, key, value): prop = PyCalendarProperty(name=key, value=value) prop.generate(os) + def editProperty(self, propname, propvalue): # Remove existing items diff -Nru pycalendar-2.0~svn188/src/pycalendar/componentexpanded.py pycalendar-2.0~svn13177/src/pycalendar/componentexpanded.py --- pycalendar-2.0~svn188/src/pycalendar/componentexpanded.py 2011-04-27 15:38:06.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/componentexpanded.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -37,6 +37,7 @@ else: return e1.mInstanceStart < e2.mInstanceStart + @staticmethod def sort_by_dtstart(e1, e2): if e1.mInstanceStart == e2.mInstanceStart: @@ -48,45 +49,56 @@ else: return e1.mInstanceStart < e2.mInstanceStart + def __init__(self, owner, rid): self.mOwner = owner self.initFromOwner(rid) + def duplicate(self): other = PyCalendarComponentExpanded(self.mOwner, None) other.mInstanceStart = self.mInstanceStart.duplicate() other.mInstanceEnd = self.mInstanceEnd.duplicate() other.mRecurring = self.mRecurring return other - + + def close(self): # Clean-up self.mOwner = None + def getOwner(self): return self.mOwner + def getMaster(self): return self.mOwner + def getTrueMaster(self): return self.mOwner.getMaster() + def getInstanceStart(self): return self.mInstanceStart + def getInstanceEnd(self): return self.mInstanceEnd + def recurring(self): return self.mRecurring + def isNow(self): # Check instance start/end against current date-time now = PyCalendarDateTime.getNowUTC() return self.mInstanceStart <= now and self.mInstanceEnd > now + def initFromOwner(self, rid): # There are four possibilities here: # diff -Nru pycalendar-2.0~svn188/src/pycalendar/component.py pycalendar-2.0~svn13177/src/pycalendar/component.py --- pycalendar-2.0~svn188/src/pycalendar/component.py 2011-04-27 15:38:06.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/component.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -28,30 +28,34 @@ uid_ctr = 1 def __init__(self, parent=None): - + super(PyCalendarComponent, self).__init__(parent) self.mUID = "" self.mSeq = 0 self.mOriginalSeq = 0 self.mChanged = False + def duplicate(self, parent=None, **args): - + other = super(PyCalendarComponent, self).duplicate(parent=parent, **args) other.mUID = self.mUID other.mSeq = self.mSeq other.mOriginalSeq = self.mOriginalSeq other.mChanged = self.mChanged - + return other + def __repr__(self): return "%s: UID: %s" % (self.getType(), self.getMapKey(),) - + + def getMimeComponentName(self): raise NotImplementedError + def getMapKey(self): if hasattr(self, "mMapKey"): return self.mMapKey @@ -61,15 +65,19 @@ self.mMapKey = str(uuid.uuid4()) return self.mMapKey + def getSortKey(self): return self.getMapKey() + def getMasterKey(self): return self.mUID + def getUID(self): return self.mUID + def setUID(self, uid): if uid: self.mUID = uid @@ -109,9 +117,11 @@ prop = PyCalendarProperty(definitions.cICalProperty_UID, self.mUID) self.addProperty(prop) + def getSeq(self): return self.mSeq + def setSeq(self, seq): self.mSeq = seq @@ -120,15 +130,19 @@ prop = PyCalendarProperty(definitions.cICalProperty_SEQUENCE, self.mSeq) self.addProperty(prop) + def getOriginalSeq(self): return self.mOriginalSeq + def getChanged(self): return self.mChanged + def setChanged(self, changed): self.mChanged = changed + def initDTSTAMP(self): self.removeProperties(definitions.cICalProperty_DTSTAMP) @@ -136,6 +150,7 @@ PyCalendarDateTime.getNowUTC()) self.addProperty(prop) + def updateLastModified(self): self.removeProperties(definitions.cICalProperty_LAST_MODIFIED) @@ -143,6 +158,7 @@ PyCalendarDateTime.getNowUTC()) self.addProperty(prop) + def finalise(self): # Get UID temps = self.loadValueString(definitions.cICalProperty_UID) @@ -159,9 +175,11 @@ # same calendar self.mOriginalSeq = self.mSeq + def canGenerateInstance(self): return True + def getTimezones(self, tzids): # Look for all date-time properties for props in self.mProperties.itervalues(): diff -Nru pycalendar-2.0~svn188/src/pycalendar/componentrecur.py pycalendar-2.0~svn13177/src/pycalendar/componentrecur.py --- pycalendar-2.0~svn188/src/pycalendar/componentrecur.py 2011-10-26 20:50:37.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/componentrecur.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -31,7 +31,7 @@ ) @staticmethod - def mapKey(uid, rid = None): + def mapKey(uid, rid=None): if uid: result = "u:" + uid if rid is not None: @@ -40,6 +40,7 @@ else: return None + @staticmethod def sort_by_dtstart_allday(e1, e2): @@ -59,6 +60,7 @@ else: return e1.self.mStart < e2.self.mStart + @staticmethod def sort_by_dtstart(e1, e2): if e1.self.mStart == e2.self.mStart: @@ -70,6 +72,7 @@ else: return e1.self.mStart < e2.self.mStart + def __init__(self, parent=None): super(PyCalendarComponentRecur, self).__init__(parent=parent) self.mMaster = self @@ -87,12 +90,13 @@ self.mAdjustPrior = False self.mRecurrenceID = None self.mRecurrences = None - + # This is a special check we do only for STATUS due to a calendarserver bug self.cardinalityChecks += ( self.check_cardinality_STATUS_Fix, ) + def duplicate(self, parent=None): other = super(PyCalendarComponentRecur, self).duplicate(parent=parent) @@ -120,31 +124,38 @@ other.mRecurrenceID = self.mRecurrenceID.duplicate() other._resetRecurrenceSet() - + return other + def canGenerateInstance(self): return not self.mHasRecurrenceID + def recurring(self): return (self.mMaster is not None) and (self.mMaster is not self) + def setMaster(self, master): self.mMaster = master self.initFromMaster() + def getMaster(self): return self.mMaster + def getMapKey(self): - + if self.mMapKey is None: self.mMapKey = str(uuid.uuid4()) return self.mMapKey + def getMasterKey(self): return PyCalendarComponentRecur.mapKey(self.mUID) + def initDTSTAMP(self): # Save new one super(PyCalendarComponentRecur, self).initDTSTAMP() @@ -154,46 +165,60 @@ self.mHasStamp = temp is not None if self.mHasStamp: self.mStamp = temp - + + def getStamp(self): return self.mStamp + def hasStamp(self): return self.mHasStamp + def getStart(self): return self.mStart + def hasStart(self): return self.mHasStart + def getEnd(self): return self.mEnd + def hasEnd(self): return self.mHasEnd + def useDuration(self): return self.mDuration + def isRecurrenceInstance(self): return self.mHasRecurrenceID + def isAdjustFuture(self): return self.mAdjustFuture + def isAdjustPrior(self): return self.mAdjustPrior + def getRecurrenceID(self): return self.mRecurrenceID + def isRecurring(self): return (self.mRecurrences is not None) and self.mRecurrences.hasRecurrence() + def getRecurrenceSet(self): return self.mRecurrences + def setUID(self, uid): super(PyCalendarComponentRecur, self).setUID(uid) @@ -203,12 +228,15 @@ else: self.mMapKey = self.mapKey(self.mUID) + def getSummary(self): return self.mSummary + def setSummary(self, summary): self.mSummary = summary + def getDescription(self): # Get DESCRIPTION txt = self.loadValueString(definitions.cICalProperty_DESCRIPTION) @@ -217,6 +245,7 @@ else: return "" + def getLocation(self): # Get LOCATION txt = self.loadValueString(definitions.cICalProperty_LOCATION) @@ -225,6 +254,7 @@ else: return "" + def finalise(self): super(PyCalendarComponentRecur, self).finalise() @@ -275,7 +305,7 @@ # Also get the RANGE attribute attrs = self.findFirstProperty(definitions.cICalProperty_RECURRENCE_ID).getAttributes() - if attrs.has_key(definitions.cICalAttribute_RANGE): + if definitions.cICalAttribute_RANGE in attrs: self.mAdjustFuture = (attrs[definitions.cICalAttribute_RANGE][0].getFirstValue() == definitions.cICalAttribute_RANGE_THISANDFUTURE) self.mAdjustPrior = (attrs[definitions.cICalAttribute_RANGE][0].getFirstValue() == definitions.cICalAttribute_RANGE_THISANDPRIOR) else: @@ -286,6 +316,7 @@ self._resetRecurrenceSet() + def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems. Return @@ -293,7 +324,7 @@ second problems that were not fixed. Caller can then decide what to do with unfixed issues. """ - + # Do normal checks fixed, unfixed = super(PyCalendarComponentRecur, self).validate(doFix) @@ -320,6 +351,7 @@ return fixed, unfixed + def check_cardinality_STATUS_Fix(self, fixed, unfixed, doFix): """ Special for bug with STATUS where STATUS:CANCELLED is added alongside @@ -341,12 +373,13 @@ else: unfixed.append(logProblem) + def _resetRecurrenceSet(self): # May need to create items self.mRecurrences = None if ((self.countProperty(definitions.cICalProperty_RRULE) != 0) or - (self.countProperty(definitions.cICalProperty_RDATE) != 0) or - (self.countProperty(definitions.cICalProperty_EXRULE) != 0) or + (self.countProperty(definitions.cICalProperty_RDATE) != 0) or + (self.countProperty(definitions.cICalProperty_EXRULE) != 0) or (self.countProperty(definitions.cICalProperty_EXDATE) != 0)): self.mRecurrences = PyCalendarRecurrenceSet() @@ -363,6 +396,7 @@ # Get EXDATEs self.loadValueRDATE(definitions.cICalProperty_EXDATE, self.mRecurrences, False) + def FixStartEnd(self): # End is always greater than start if start exists if self.mHasStart and self.mEnd <= self.mStart: @@ -381,6 +415,7 @@ self.mEnd.offsetDay(1) self.mEnd.setHHMMSS(0, 0, 0) + def expandPeriod(self, period, results): # Check for recurrence and True master if ((self.mRecurrences is not None) and self.mRecurrences.hasRecurrence() @@ -460,6 +495,7 @@ rid = None results.append(PyCalendarComponentExpanded(self, rid)) + def withinPeriod(self, period): # Check for recurrence if ((self.mRecurrences is not None) and self.mRecurrences.hasRecurrence()): @@ -474,11 +510,13 @@ else: return True + def changedRecurrence(self): # Clear cached values if self.mRecurrences is not None: self.mRecurrences.changed() + # Editing def editSummary(self, summary): # Updated cached value @@ -487,12 +525,14 @@ # Remove existing items self.editProperty(definitions.cICalProperty_SUMMARY, summary) + def editDetails(self, description, location): # Edit existing items self.editProperty(definitions.cICalProperty_DESCRIPTION, description) self.editProperty(definitions.cICalProperty_LOCATION, location) + def editTiming(self): # Updated cached values self.mHasStart = False @@ -507,6 +547,7 @@ self.removeProperties(definitions.cICalProperty_DURATION) self.removeProperties(definitions.cICalProperty_DUE) + def editTimingDue(self, due): # Updated cached values self.mHasStart = False @@ -525,6 +566,7 @@ prop = PyCalendarProperty(definitions.cICalProperty_DUE, due) self.addProperty(prop) + def editTimingStartEnd(self, start, end): # Updated cached values self.mHasStart = self.mHasEnd = True @@ -549,6 +591,7 @@ prop = PyCalendarProperty(definitions.cICalProperty_DTEND, end) self.addProperty(prop) + def editTimingStartDuration(self, start, duration): # Updated cached values self.mHasStart = True @@ -573,6 +616,7 @@ prop = PyCalendarProperty(definitions.cICalProperty_DURATION, duration) self.addProperty(prop) + def editRecurrenceSet(self, recurs): # Must have items if self.mRecurrences is None: @@ -601,6 +645,7 @@ prop = PyCalendarProperty(definitions.cICalProperty_EXDATE, iter) self.addProperty(prop) + def excludeRecurrence(self, start): # Must have items if self.mRecurrences is None: @@ -613,6 +658,7 @@ prop = PyCalendarProperty(definitions.cICalProperty_EXDATE, start) self.addProperty(prop) + def excludeFutureRecurrence(self, start): # Must have items if self.mRecurrences is None: @@ -633,6 +679,7 @@ prop = PyCalendarProperty(definitions.cICalProperty_RDATE, iter) self.addProperty(prop) + def initFromMaster(self): # Only if not master if self.recurring(): @@ -662,5 +709,6 @@ temp = self.loadValueDuration(definitions.cICalProperty_DURATION) self.mEnd = self.mStart + temp + def createExpanded(self, master, recurid): return PyCalendarComponentExpanded(master, recurid) diff -Nru pycalendar-2.0~svn188/src/pycalendar/datetime.py pycalendar-2.0~svn13177/src/pycalendar/datetime.py --- pycalendar-2.0~svn188/src/pycalendar/datetime.py 2011-10-11 17:13:17.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/datetime.py 2014-04-07 16:00:00.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,6 +19,7 @@ from pycalendar import utils from pycalendar import xmldefs from pycalendar.duration import PyCalendarDuration +from pycalendar.parser import ParserContext from pycalendar.timezone import PyCalendarTimezone from pycalendar.valueutils import ValueMixin import cStringIO as StringIO @@ -34,7 +35,7 @@ THURSDAY = 4 FRIDAY = 5 SATURDAY = 6 - + FULLDATE = 0 ABBREVDATE = 1 NUMERICDATE = 2 @@ -47,8 +48,9 @@ return e1.compareDateTime(e2) - def __init__( self, year = None, month = None, day = None, hours = None, minutes = None, seconds = None, tzid = None, utcoffset = None ): - + + def __init__(self, year=None, month=None, day=None, hours=None, minutes=None, seconds=None, tzid=None, utcoffset=None): + self.mYear = 1970 self.mMonth = 1 self.mDay = 1 @@ -61,6 +63,7 @@ self.mTZUTC = False self.mTZID = None + self.mTZOffset = None self.mPosixTimeCached = False self.mPosixTime = 0 @@ -78,7 +81,8 @@ if tzid: self.mTZUTC = tzid.getUTC() self.mTZID = tzid.getTimezoneID() - + + def duplicate(self): other = PyCalendarDateTime(self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds) @@ -86,94 +90,107 @@ other.mTZUTC = self.mTZUTC other.mTZID = self.mTZID + other.mTZOffset = self.mTZOffset other.mPosixTimeCached = self.mPosixTimeCached other.mPosixTime = self.mPosixTime - + return other - + + def duplicateAsUTC(self): other = self.duplicate() other.adjustToUTC() return other + def __repr__(self): return "PyCalendarDateTime: %s" % (self.getText(),) + def __hash__(self): return hash(self.getPosixTime()) - + + # Operators - def __add__( self, duration ): + def __add__(self, duration): # Add duration seconds to temp object and normalise it result = self.duplicate() result.mSeconds += duration.getTotalSeconds() result.normalise() return result - - def __sub__( self, dateorduration ): - + + + def __sub__(self, dateorduration): + if isinstance(dateorduration, PyCalendarDateTime): - + date = dateorduration - + # Look for floating if self.floating() or date.floating(): # Adjust the floating ones to fixed copy1 = self.duplicate() copy2 = date.duplicate() - + if copy1.floating() and copy2.floating(): # Set both to UTC and do comparison - copy1.setTimezoneUTC( True ) - copy2.setTimezoneUTC( True ) + copy1.setTimezoneUTC(True) + copy2.setTimezoneUTC(True) return copy1 - copy2 elif copy1.floating(): # Set to be the same - copy1.setTimezoneID( copy2.getTimezoneID() ) - copy1.setTimezoneUTC( copy2.getTimezoneUTC() ) + copy1.setTimezoneID(copy2.getTimezoneID()) + copy1.setTimezoneUTC(copy2.getTimezoneUTC()) return copy1 - copy2 else: # Set to be the same - copy2.setTimezoneID( copy1.getTimezoneID() ) - copy2.setTimezoneUTC( copy1.getTimezoneUTC() ) + copy2.setTimezoneID(copy1.getTimezoneID()) + copy2.setTimezoneUTC(copy1.getTimezoneUTC()) return copy1 - copy2 - + else: # Do diff of date-time in seconds diff = self.getPosixTime() - date.getPosixTime() return PyCalendarDuration(duration=diff) elif isinstance(dateorduration, PyCalendarDuration): - + duration = dateorduration result = self.duplicate() result.mSeconds -= duration.getTotalSeconds() result.normalise() return result - + raise ValueError + # Comparators - def __eq__( self, comp ): - return self.compareDateTime( comp ) == 0 + def __eq__(self, comp): + return self.compareDateTime(comp) == 0 + + + def __ne__(self, comp): + return self.compareDateTime(comp) != 0 + - def __ne__( self, comp ): - return self.compareDateTime( comp ) != 0 + def __ge__(self, comp): + return self.compareDateTime(comp) >= 0 - def __ge__( self, comp ): - return self.compareDateTime( comp ) >= 0 - def __le__( self, comp ): - return self.compareDateTime( comp ) <= 0 + def __le__(self, comp): + return self.compareDateTime(comp) <= 0 - def __gt__( self, comp ): - return self.compareDateTime( comp ) > 0 - def __lt__( self, comp ): - return self.compareDateTime( comp ) < 0 + def __gt__(self, comp): + return self.compareDateTime(comp) > 0 - def compareDateTime( self, comp ): + + def __lt__(self, comp): + return self.compareDateTime(comp) < 0 + + + def compareDateTime(self, comp): if comp is None: return 1 # If either are date only, then just do date compare @@ -197,10 +214,10 @@ return -1 else: return 1 - + # If they have the same timezone do simple compare - no posix calc # needed - elif ( PyCalendarTimezone.same( self.mTZUTC, self.mTZID, comp.mTZUTC, comp.mTZID ) ): + elif (PyCalendarTimezone.same(self.mTZUTC, self.mTZID, comp.mTZUTC, comp.mTZID)): if self.mYear == comp.mYear: if self.mMonth == comp.mMonth: if self.mDay == comp.mDay: @@ -249,17 +266,19 @@ else: return 1 - def compareDate( self, comp ): - return ( self.mYear == comp.mYear ) and ( self.mMonth == comp.mMonth ) and ( self.mDay == comp.mDay ) - - def getPosixTime( self ): + + def compareDate(self, comp): + return (self.mYear == comp.mYear) and (self.mMonth == comp.mMonth) and (self.mDay == comp.mDay) + + + def getPosixTime(self): # Look for cached value (or floating time which has to be calculated # each time) - if ( not self.mPosixTimeCached ) or self.floating(): + if (not self.mPosixTimeCached) or self.floating(): result = 0L # Add hour/mins/secs - result = ( self.mHours * 60L + self.mMinutes ) * 60L + self.mSeconds + result = (self.mHours * 60L + self.mMinutes) * 60L + self.mSeconds # Number of days since 1970 result += self.daysSince1970() * 24L * 60L * 60L @@ -270,57 +289,77 @@ # Now indicate cache state self.mPosixTimeCached = True self.mPosixTime = result - + return self.mPosixTime - def isDateOnly( self ): + + def isDateOnly(self): return self.mDateOnly - def setDateOnly( self, date_only ): + + def setDateOnly(self, date_only): self.mDateOnly = date_only self.changed() - def getYear( self ): + + def getYear(self): return self.mYear - def setYear( self, year ): + + def setYear(self, year): if self.mYear != year: self.mYear = year self.changed() - def offsetYear( self, diff_year ): + + def offsetYear(self, diff_year): self.mYear += diff_year self.normalise() - def getMonth( self ): + + def getMonth(self): return self.mMonth - def setMonth( self, month ): + + def setMonth(self, month): if self.mMonth != month: self.mMonth = month self.changed() - def offsetMonth( self, diff_month ): + + def offsetMonth(self, diff_month): self.mMonth += diff_month self.normalise() - def getDay( self ): + + def getDay(self): return self.mDay - def setDay( self, day ): + + def setDay(self, day): if self.mDay != day: self.mDay = day self.changed() - def offsetDay( self, diff_day ): + + def offsetDay(self, diff_day): self.mDay += diff_day self.normalise() - def setYearDay( self, day ): + + def setYearDay(self, day, allow_invalid=False): # 1 .. 366 offset from start, or # -1 .. -366 offset from end - if day > 0: + if day == 366: + self.mMonth = 12 + self.mDay = 31 if utils.isLeapYear(self.mYear) else 32 + + elif day == -366: + self.mMonth = 1 if utils.isLeapYear(self.mYear) else 1 + self.mDay = 1 if utils.isLeapYear(self.mYear) else 0 + + elif day > 0: # Offset current date to 1st January of current year self.mMonth = 1 self.mDay = 1 @@ -328,24 +367,26 @@ # Increment day self.mDay += day - 1 - # Normalise to get proper month/day values - self.normalise() elif day < 0: # Offset current date to 1st January of next year - self.mYear += 1 - self.mMonth = 1 - self.mDay = 1 + self.mMonth = 12 + self.mDay = 31 # Decrement day - self.mDay += day + self.mDay += day + 1 + if not allow_invalid: # Normalise to get proper year/month/day values self.normalise() + else: + self.changed() + + + def getYearDay(self): + return self.mDay + utils.daysUptoMonth(self.mMonth, self.mYear) - def getYearDay( self ): - return self.mDay + utils.daysUptoMonth( self.mMonth, self.mYear ) - def setMonthDay( self, day ): + def setMonthDay(self, day, allow_invalid=False): # 1 .. 31 offset from start, or # -1 .. -31 offset from end @@ -356,58 +397,98 @@ # Increment day self.mDay += day - 1 - # Normalise to get proper month/day values - self.normalise() elif day < 0: - # Offset current date to 1st of next month - self.mMonth += 1 - self.mDay = 1 + # Offset current date to last of month + self.mDay = utils.daysInMonth(self.mMonth, self.mYear) # Decrement day - self.mDay += day + self.mDay += day + 1 + if not allow_invalid: # Normalise to get proper year/month/day values self.normalise() + else: + self.changed() - def isMonthDay( self, day ): + + def isMonthDay(self, day): if day > 0: return self.mDay == day elif day < 0: - return self.mDay - 1 - utils.daysInMonth( self.mMonth, self.mYear ) == day + return self.mDay - 1 - utils.daysInMonth(self.mMonth, self.mYear) == day else: return False - def setWeekNo( self, weekno ): - # This is the iso 8601 week number definition - # What day does the current year start on + def setWeekNo(self, weekno): + """ + Set the current date to one with the same day of the week in the current year with the + specified week number. Note this might cause the year to shift backwards or forwards + if the date is at the boundary between two years. + + @param weekno: the week number to set (currently must be positive) + @type weekno: C{int} + """ + + # Don't both if already correct + if self.getWeekNo() == weekno: + return + + # What day does the current year start on, and diff that with the current day temp = PyCalendarDateTime(year=self.mYear, month=1, day=1) first_day = temp.getDayOfWeek() + current_day = self.getDayOfWeek() + + # Calculate and set yearday for start of week. The first week is the one that contains at least + # four days (with week start defaulting to MONDAY), so that means the 1st of January would fall + # on MO, TU, WE, TH. + if first_day in (PyCalendarDateTime.MONDAY, PyCalendarDateTime.TUESDAY, PyCalendarDateTime.WEDNESDAY, PyCalendarDateTime.THURSDAY): + year_day = (weekno - 1) * 7 + current_day - first_day + else: + year_day = weekno * 7 + current_day - first_day - # Calculate and set yearday for start of week - if ( first_day == PyCalendarDateTime.SUNDAY ) or ( first_day == PyCalendarDateTime.MONDAY ) or \ - ( first_day == PyCalendarDateTime.TUESDAY ) or ( first_day == PyCalendarDateTime.WEDNESDAY ) or ( first_day == PyCalendarDateTime.THURSDAY ): - self.setYearDay( ( weekno - 1 ) * 7 - first_day ) - elif ( first_day == PyCalendarDateTime.FRIDAY ) or ( first_day == PyCalendarDateTime.SATURDAY ): - self.setYearDay( ( weekno - 1 ) * 7 - first_day + 7 ) + # It is possible we have a negative offset which means go back to the prior year as part of + # week #1 exists at the end of that year. + if year_day < 0: + self.offsetYear(-1) + else: + year_day += 1 + self.setYearDay(year_day) - def getWeekNo( self ): - # This is the iso 8601 week number definition + + def getWeekNo(self): + """ + Return the ISO week number for the current date. + """ # What day does the current year start on temp = PyCalendarDateTime(year=self.mYear, month=1, day=1) first_day = temp.getDayOfWeek() + if first_day == 0: + first_day = 7 + current_day = self.getDayOfWeek() + if current_day == 0: + current_day = 7 + + # This arithmetic uses the ISO day of week (1-7) and the year day to get the week number + week_no = (self.getYearDay() - current_day + 10) / 7 + + # Might need to adjust forward/backwards based on year boundaries + if week_no == 0: + # Last week of previous year + temp = PyCalendarDateTime(year=self.mYear - 1, month=12, day=31) + week_no = temp.getWeekNo() + elif week_no == 53: + # Might be first week of next year + temp = PyCalendarDateTime(year=self.mYear + 1, month=1, day=1) + first_day = temp.getDayOfWeek() + if first_day in (PyCalendarDateTime.MONDAY, PyCalendarDateTime.TUESDAY, PyCalendarDateTime.WEDNESDAY, PyCalendarDateTime.THURSDAY): + week_no = 1 - # Get days upto the current one - yearday = self.getYearDay() + return week_no - if ( first_day == PyCalendarDateTime.SUNDAY ) or ( first_day == PyCalendarDateTime.MONDAY ) or \ - ( first_day == PyCalendarDateTime.TUESDAY ) or ( first_day == PyCalendarDateTime.WEDNESDAY ) or ( first_day == PyCalendarDateTime.THURSDAY ): - return ( yearday + first_day ) / 7 + 1 - elif ( first_day == PyCalendarDateTime.FRIDAY ) or ( first_day == PyCalendarDateTime.SATURDAY ): - return ( yearday + first_day - 7 ) / 7 + 1 - def isWeekNo( self, weekno ): + def isWeekNo(self, weekno): # This is the iso 8601 week number definition if weekno > 0: @@ -417,7 +498,8 @@ # the current year return False - def setDayOfWeekInYear( self, offset, day ): + + def setDayOfWeekInYear(self, offset, day): # Set to first day in year self.mMonth = 1 self.mDay = 1 @@ -426,24 +508,25 @@ first_day = self.getDayOfWeek() if offset > 0: - cycle = ( offset - 1 ) * 7 + day + cycle = (offset - 1) * 7 + day cycle -= first_day if first_day > day: cycle += 7 self.mDay = cycle + 1 elif offset < 0: - first_day += 365 + [1, 0][not utils.isLeapYear( self.mYear )] - 1 + first_day += 365 + [1, 0][not utils.isLeapYear(self.mYear)] - 1 first_day %= 7 - cycle = ( -offset - 1 ) * 7 - day + cycle = (-offset - 1) * 7 - day cycle += first_day if day > first_day: cycle += 7 - self.mDay = 365 + [1, 0][not utils.isLeapYear( self.mYear )] - cycle + self.mDay = 365 + [1, 0][not utils.isLeapYear(self.mYear)] - cycle self.normalise() - def setDayOfWeekInMonth( self, offset, day ): + + def setDayOfWeekInMonth(self, offset, day, allow_invalid=False): # Set to first day in month self.mDay = 1 @@ -451,25 +534,29 @@ first_day = self.getDayOfWeek() if offset > 0: - cycle = ( offset - 1 ) * 7 + day + cycle = (offset - 1) * 7 + day cycle -= first_day if first_day > day: cycle += 7 self.mDay = cycle + 1 elif offset < 0: - days_in_month = utils.daysInMonth( self.mMonth, self.mYear ) + days_in_month = utils.daysInMonth(self.mMonth, self.mYear) first_day += days_in_month - 1 first_day %= 7 - cycle = ( -offset - 1 ) * 7 - day + cycle = (-offset - 1) * 7 - day cycle += first_day if day > first_day: cycle += 7 self.mDay = days_in_month - cycle - self.normalise() + if not allow_invalid: + self.normalise() + else: + self.changed() - def setNextDayOfWeek( self, start, day ): + + def setNextDayOfWeek(self, start, day): # Set to first day in month self.mDay = start @@ -480,10 +567,11 @@ self.mDay += 7 self.mDay += day - first_day - + self.normalise() - def isDayOfWeekInMonth( self, offset, day ): + + def isDayOfWeekInMonth(self, offset, day): # First of the actual day must match if self.getDayOfWeek() != day: return False @@ -495,12 +583,13 @@ # Create temp date-time with the appropriate parameters and then # compare temp = self.duplicate() - temp.setDayOfWeekInMonth( offset, day ) + temp.setDayOfWeekInMonth(offset, day) # Now compare dates - return self.compareDate( temp ) + return self.compareDate(temp) + - def getDayOfWeek( self ): + def getDayOfWeek(self): # Count days since 01-Jan-1970 which was a Thursday result = PyCalendarDateTime.THURSDAY + self.daysSince1970() result %= 7 @@ -509,157 +598,199 @@ return result - def getMonthText( self, short_txt ): + + def getMonthText(self, short_txt): # Make sure range is valid - if ( self.mMonth < 1 ) or ( self.mMonth > 12 ): + if (self.mMonth < 1) or (self.mMonth > 12): return "" else: - return locale.getMonth( self.mMonth, - [locale.SHORT, locale.LONG][not short_txt] ) + return locale.getMonth(self.mMonth, + [locale.SHORT, locale.LONG][not short_txt]) + + + def getDayOfWeekText(self, day): + return locale.getDay(day, locale.SHORT) - def getDayOfWeekText( self, day ): - return locale.getDay( day, locale.SHORT ) - def setHHMMSS( self, hours, minutes, seconds ): - if ( self.mHours != hours ) or ( self.mMinutes != minutes ) or ( self.mSeconds != seconds ): + def setHHMMSS(self, hours, minutes, seconds): + if (self.mHours != hours) or (self.mMinutes != minutes) or (self.mSeconds != seconds): self.mHours = hours self.mMinutes = minutes self.mSeconds = seconds self.changed() - def getHours( self ): + + def getHours(self): return self.mHours - def setHours( self, hours ): + + def setHours(self, hours): if self.mHours != hours: self.mHours = hours self.changed() - def offsetHours( self, diff_hour ): + + def offsetHours(self, diff_hour): self.mHours += diff_hour self.normalise() - def getMinutes( self ): + + def getMinutes(self): return self.mMinutes - def setMinutes( self, minutes ): + + def setMinutes(self, minutes): if self.mMinutes != minutes: self.mMinutes = minutes self.changed() - def offsetMinutes( self, diff_minutes ): + + def offsetMinutes(self, diff_minutes): self.mMinutes += diff_minutes self.normalise() - def getSeconds( self ): + + def getSeconds(self): return self.mSeconds - def setSeconds( self, seconds ): + + def setSeconds(self, seconds): if self.mSeconds != seconds: self.mSeconds = seconds self.changed() - def offsetSeconds( self, diff_seconds ): + + def offsetSeconds(self, diff_seconds): self.mSeconds += diff_seconds self.normalise() - def getTimezoneUTC( self ): + + def getTimezoneUTC(self): return self.mTZUTC - def setTimezoneUTC( self, utc ): + + def setTimezoneUTC(self, utc): if self.mTZUTC != utc: self.mTZUTC = utc self.changed() - def getTimezoneID( self ): + + def getTimezoneID(self): return self.mTZID - def setTimezoneID( self, tzid ): + + def setTimezoneID(self, tzid): self.mTZUTC = False self.mTZID = tzid self.changed() - def utc( self ): + + def utc(self): return self.mTZUTC - def local( self ): - return ( not self.mTZUTC ) and self.mTZID - def floating( self ): - return ( not self.mTZUTC ) and not self.mTZID + def local(self): + return (not self.mTZUTC) and self.mTZID + + + def floating(self): + return (not self.mTZUTC) and not self.mTZID + def getTimezone(self): return PyCalendarTimezone(utc=self.mTZUTC, tzid=self.mTZID) - def setTimezone( self, tzid ): + + def setTimezone(self, tzid): self.mTZUTC = tzid.getUTC() self.mTZID = tzid.getTimezoneID() self.changed() - def adjustTimezone( self, tzid ): + + def adjustTimezone(self, tzid): # Only if different s1 = tzid.getTimezoneID() - if ( tzid.getUTC() != self.mTZUTC ) or ( s1 != self.mTZID ): - offset_from = self.timeZoneSecondsOffset() - self.setTimezone( tzid ) - offset_to = self.timeZoneSecondsOffset() - self.offsetSeconds( offset_to - offset_from ) + if (tzid.getUTC() != self.mTZUTC) or (s1 != self.mTZID): + if not self.mTZUTC: + self.adjustToUTC() + self.setTimezone(tzid) + offset_to = self.timeZoneSecondsOffset(relative_to_utc=True) + self.offsetSeconds(offset_to) return self - def adjustToUTC( self ): + + def adjustToUTC(self): if self.local() and not self.mDateOnly: + # Cache and restore and adjust the posix value to avoid a recalc since it won't change during this adjust + tempPosix = self.mPosixTime if self.mPosixTimeCached else None + utc = PyCalendarTimezone(utc=True) offset_from = self.timeZoneSecondsOffset() - self.setTimezone( utc ) - offset_to = self.timeZoneSecondsOffset() + self.setTimezone(utc) + + self.offsetSeconds(-offset_from) + + if tempPosix is not None: + self.mPosixTimeCached = True + self.mPosixTime = tempPosix + + self.mTZOffset = 0 - self.offsetSeconds( offset_to - offset_from ) return self - def getAdjustedTime( self, tzid = None ): + + def getAdjustedTime(self, tzid=None): # Copy this and adjust to input timezone adjusted = self.duplicate() - adjusted.adjustTimezone( tzid ) + adjusted.adjustTimezone(tzid) return adjusted - def setToday( self ): + + def setToday(self): tz = PyCalendarTimezone(utc=self.mTZUTC, tzid=self.mTZID) - self.copy_ICalendarDateTime( self.getToday( tz ) ) + self.copy_ICalendarDateTime(self.getToday(tz)) + @staticmethod - def getToday( tzid=None ): + def getToday(tzid=None): # Get from posix time now = time.time() - now_tm = time.localtime( now ) - + now_tm = time.localtime(now) + temp = PyCalendarDateTime(year=now_tm.tm_year, month=now_tm.tm_mon, day=now_tm.tm_mday, tzid=tzid) return temp - - def setNow( self ): + + + def setNow(self): tz = PyCalendarTimezone(utc=self.mTZUTC, tzid=self.mTZID) - self.copy_ICalendarDateTime( self.getNow( tz ) ) + self.copy_ICalendarDateTime(self.getNow(tz)) + @staticmethod - def getNow( tzid ): + def getNow(tzid): utc = PyCalendarDateTime.getNowUTC() - utc.adjustTimezone( tzid if tzid is not None else PyCalendarTimezone() ) + utc.adjustTimezone(tzid if tzid is not None else PyCalendarTimezone()) return utc - - def setNowUTC( self ): - self.copy_PyCalendarDateTime( self.getNowUTC() ) + + + def setNowUTC(self): + self.copy_PyCalendarDateTime(self.getNowUTC()) + @staticmethod - def getNowUTC( ): + def getNowUTC(): # Get from posix time now = time.time() - now_tm = time.gmtime( now ) + now_tm = time.gmtime(now) tzid = PyCalendarTimezone(utc=True) - - return PyCalendarDateTime(year=now_tm.tm_year, month=now_tm.tm_mon, day=now_tm.tm_mday, hours=now_tm.tm_hour, minutes=now_tm.tm_min, seconds=now_tm.tm_sec, tzid=tzid ) - def recur( self, freq, interval ): + return PyCalendarDateTime(year=now_tm.tm_year, month=now_tm.tm_mon, day=now_tm.tm_mday, hours=now_tm.tm_hour, minutes=now_tm.tm_min, seconds=now_tm.tm_sec, tzid=tzid) + + + def recur(self, freq, interval, allow_invalid=False): # Add appropriate interval + normalize = True if freq == definitions.eRecurrence_SECONDLY: self.mSeconds += interval elif freq == definitions.eRecurrence_MINUTELY: @@ -677,62 +808,81 @@ # or 1/2 May, or 31 March or what? We choose to find the next month with # the same day number as the current one. self.mMonth += interval - self.normalise() - while self.mDay > utils.daysInMonth( self.mMonth, self.mYear ): - self.mMonth += interval + + # Normalise month + normalised_month = ((self.mMonth - 1) % 12) + 1 + adjustment_year = (self.mMonth - 1) / 12 + if (normalised_month - 1) < 0: + normalised_month += 12 + adjustment_year -= 1 + self.mMonth = normalised_month + self.mYear += adjustment_year + + if not allow_invalid: self.normalise() + while self.mDay > utils.daysInMonth(self.mMonth, self.mYear): + self.mMonth += interval + self.normalise() + normalize = False elif freq == definitions.eRecurrence_YEARLY: self.mYear += interval + if allow_invalid: + normalize = False + + if normalize: + # Normalise to standard date-time ranges + self.normalise() + else: + self.changed() - # Normalise to standard date-time ranges - self.normalise() - def getLocaleDate( self, locale ): + def getLocaleDate(self, locale): buf = StringIO.StringIO() if locale == PyCalendarDateTime.FULLDATE: - buf.write( locale.getDay( self.getDayOfWeek(), locale.LONG ) ) - buf.write( ", " ) - buf.write( locale.getMonth( self.mMonth, locale.LONG ) ) - buf.write( " " ) - buf.write( str( self.mDay ) ) - buf.write( ", " ) - buf.write( str( self.mYear ) ) + buf.write(locale.getDay(self.getDayOfWeek(), locale.LONG)) + buf.write(", ") + buf.write(locale.getMonth(self.mMonth, locale.LONG)) + buf.write(" ") + buf.write(str(self.mDay)) + buf.write(", ") + buf.write(str(self.mYear)) elif locale == PyCalendarDateTime.ABBREVDATE: - buf.write( locale.getDay( self.getDayOfWeek(), locale.SHORT ) ) - buf.write( ", " ) - buf.write( locale.getMonth( self.mMonth, locale.SHORT ) ) - buf.write( " " ) - buf.write( str( self.mDay ) ) - buf.write( ", " ) - buf.write( str( self.mYear ) ) + buf.write(locale.getDay(self.getDayOfWeek(), locale.SHORT)) + buf.write(", ") + buf.write(locale.getMonth(self.mMonth, locale.SHORT)) + buf.write(" ") + buf.write(str(self.mDay)) + buf.write(", ") + buf.write(str(self.mYear)) elif locale == PyCalendarDateTime.NUMERICDATE: - buf.write( str( self.mMonth ) ) - buf.write( "/" ) - buf.write( str( self.mDay ) ) - buf.write( "/" ) - buf.write( str( self.mYear ) ) + buf.write(str(self.mMonth)) + buf.write("/") + buf.write(str(self.mDay)) + buf.write("/") + buf.write(str(self.mYear)) elif locale == PyCalendarDateTime.FULLDATENOYEAR: - buf.write( locale.getDay( self.getDayOfWeek(), locale.LONG ) ) - buf.write( ", " ) - buf.write( locale.getMonth( self.mMonth, locale.LONG ) ) - buf.write( " " ) - buf.write( str( self.mDay ) ) + buf.write(locale.getDay(self.getDayOfWeek(), locale.LONG)) + buf.write(", ") + buf.write(locale.getMonth(self.mMonth, locale.LONG)) + buf.write(" ") + buf.write(str(self.mDay)) elif locale == PyCalendarDateTime.ABBREVDATENOYEAR: - buf.write( locale.getDay( self. getDayOfWeek(), locale.SHORT ) ) - buf.write( ", " ) - buf.write( locale.getMonth( self.mMonth, locale.SHORT ) ) - buf.write( " " ) - buf.write( str( self.mDay ) ) + buf.write(locale.getDay(self. getDayOfWeek(), locale.SHORT)) + buf.write(", ") + buf.write(locale.getMonth(self.mMonth, locale.SHORT)) + buf.write(" ") + buf.write(str(self.mDay)) elif locale == PyCalendarDateTime.NUMERICDATENOYEAR: - buf.write( str( self.mMonth ) ) - buf.write( "/" ) - buf.write( str( self.mDay ) ) + buf.write(str(self.mMonth)) + buf.write("/") + buf.write(str(self.mDay)) return buf.getvalue() - def getTime( self, with_seconds, am_pm, tzid ): + + def getTime(self, with_seconds, am_pm, tzid): buf = StringIO.StringIO() adjusted_hour = self.mHours @@ -746,72 +896,76 @@ if adjusted_hour == 0: adjusted_hour = 12 - buf.write( str( adjusted_hour ) ) - buf.write( ":" ) + buf.write(str(adjusted_hour)) + buf.write(":") if self.mMinutes < 10: - buf.write( "0" ) - buf.write( str( self.mMinutes ) ) + buf.write("0") + buf.write(str(self.mMinutes)) if with_seconds: - buf.write( ":" ) + buf.write(":") if self.mSeconds < 10: - buf.write( "0" ) - buf.write( str( self.mSeconds ) ) + buf.write("0") + buf.write(str(self.mSeconds)) - buf.write( [" AM", " PM"][not am] ) + buf.write([" AM", " PM"][not am]) else: if self.mHours < 10: - buf.write( "0" ) - buf.write( str( self.mHours ) ) - buf.write( ":" ) + buf.write("0") + buf.write(str(self.mHours)) + buf.write(":") if self.mMinutes < 10: - buf.write( "0" ) - buf.write( str( self.mMinutes ) ) + buf.write("0") + buf.write(str(self.mMinutes)) if with_seconds: - buf.write( ":" ) + buf.write(":") if self.mSeconds < 10: - buf.write( "0" ) - buf.write( str( self.mSeconds ) ) + buf.write("0") + buf.write(str(self.mSeconds)) if tzid: desc = self.timeZoneDescriptor() - if len( desc ) > 0: - buf.write( " " ) - buf.write( desc ) + if len(desc) > 0: + buf.write(" ") + buf.write(desc) return buf.getvalue() - def getLocaleDateTime( self, locale, with_seconds, am_pm, tzid ): - return self.getLocaleDate( locale ) + " " + self.getTime( with_seconds, am_pm, tzid ) - def getText( self ): - + def getLocaleDateTime(self, locale, with_seconds, am_pm, tzid): + return self.getLocaleDate(locale) + " " + self.getTime(with_seconds, am_pm, tzid) + + + def getText(self): + if self.mDateOnly: - return "%04d%02d%02d" % ( self.mYear, self.mMonth, self.mDay ) + return "%04d%02d%02d" % (self.mYear, self.mMonth, self.mDay) else: if self.mTZUTC: - return "%04d%02d%02dT%02d%02d%02dZ" % ( self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds ) + return "%04d%02d%02dT%02d%02d%02dZ" % (self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds) elif isinstance(self.mTZID, int): sign = "-" if self.mTZID < 0 else "+" hours = abs(self.mTZID) / 3600 minutes = divmod(abs(self.mTZID) / 60, 60)[1] - return "%04d%02d%02dT%02d%02d%02d%s%02d%02d" % ( self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds, sign, hours, minutes ) + return "%04d%02d%02dT%02d%02d%02d%s%02d%02d" % (self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds, sign, hours, minutes) else: - return "%04d%02d%02dT%02d%02d%02d" % ( self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds ) + return "%04d%02d%02dT%02d%02d%02d" % (self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds) + + + def getXMLText(self): - def getXMLText( self ): - if self.mDateOnly: - return "%04d-%02d-%02d" % ( self.mYear, self.mMonth, self.mDay ) + return "%04d-%02d-%02d" % (self.mYear, self.mMonth, self.mDay) else: if self.mTZUTC: - return "%04d-%02d-%02dT%02d:%02d:%02dZ" % ( self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds ) + return "%04d-%02d-%02dT%02d:%02d:%02dZ" % (self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds) elif isinstance(self.mTZID, int): sign = "-" if self.mTZID < 0 else "+" hours = abs(self.mTZID) / 3600 minutes = divmod(abs(self.mTZID) / 60, 60)[1] - return "%04d-%02d-%02dT%02d:%02d:%02d%s%02d:%02d" % ( self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds, sign, hours, minutes ) + return "%04d-%02d-%02dT%02d:%02d:%02d%s%02d:%02d" % (self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds, sign, hours, minutes) else: - return "%04d-%02d-%02dT%02d:%02d:%02d" % ( self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds ) + return "%04d-%02d-%02dT%02d:%02d:%02d" % (self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds) + @classmethod def parseText(cls, data, fullISO=False): @@ -837,7 +991,7 @@ self.mDay = int(data[index:index + 2]) index += 2 - if ' ' in data[:index]: + if ' ' in data[:index] and ParserContext.INVALID_DATETIME_LEADINGSPACE == ParserContext.PARSER_RAISE: raise ValueError if self.mYear < 0 or self.mMonth < 0 or self.mDay < 0: raise ValueError @@ -872,7 +1026,7 @@ raise ValueError while index < dlen and data[index].isdigit(): index += 1 - + # Optional timezone descriptor if index < dlen: if data[index] == "Z": @@ -903,6 +1057,7 @@ raise ValueError return index + def parse(self, data, fullISO=False): # iCalendar: @@ -913,19 +1068,19 @@ try: # Parse out the date index = self.parseDate(data, fullISO) - + # Now look for more dlen = len(data) if index < dlen: - + if data[index] != 'T': raise ValueError index += 1 - + # Parse out the time index = self.parseTime(data, index, fullISO) self.mDateOnly = False - + if index < dlen: if fullISO: index = self.parseFractionalAndUTCOffset(data, index, dlen) @@ -947,15 +1102,18 @@ # Always uncache posix time self.changed() - def generate( self, os ): + + def generate(self, os): try: - os.write( self.getText() ) + os.write(self.getText()) except: pass - def generateRFC2822( self, os ): + + def generateRFC2822(self, os): pass + def writeXML(self, node, namespace): value = XML.SubElement( node, @@ -965,7 +1123,22 @@ )) value.text = self.getXMLText() - def normalise( self ): + + def invalid(self): + """ + Are any of the current fields invalid. + """ + + # Right now we only care about invalid days of the month (e.g. February 30th). In the + # future we may also want to look for invalid times during a DST transition. + + if self.mDay <= 0 or self.mDay > utils.daysInMonth(self.mMonth, self.mYear): + return True + + return False + + + def normalise(self): # Normalise seconds normalised_secs = self.mSeconds % 60 adjustment_mins = self.mSeconds / 60 @@ -1000,9 +1173,9 @@ # Adjust the month first, since the day adjustment is month dependent # Normalise month - normalised_month = ( ( self.mMonth - 1 ) % 12 ) + 1 - adjustment_year = ( self.mMonth - 1 ) / 12 - if ( normalised_month - 1 ) < 0: + normalised_month = ((self.mMonth - 1) % 12) + 1 + adjustment_year = (self.mMonth - 1) / 12 + if (normalised_month - 1) < 0: normalised_month += 12 adjustment_year -= 1 self.mMonth = normalised_month @@ -1010,8 +1183,8 @@ # Now do days if self.mDay > 0: - while self.mDay > utils.daysInMonth( self.mMonth, self.mYear ): - self.mDay -= utils.daysInMonth( self.mMonth, self.mYear ) + while self.mDay > utils.daysInMonth(self.mMonth, self.mYear): + self.mDay -= utils.daysInMonth(self.mMonth, self.mYear) self.mMonth += 1 if self.mMonth > 12: self.mMonth = 1 @@ -1022,34 +1195,41 @@ if self.mMonth < 1: self.mMonth = 12 self.mYear -= 1 - self.mDay += utils.daysInMonth( self.mMonth, self.mYear ) + self.mDay += utils.daysInMonth(self.mMonth, self.mYear) # Always invalidate posix time cache self.changed() - def timeZoneSecondsOffset( self ): + + def timeZoneSecondsOffset(self, relative_to_utc=False): if self.mTZUTC: return 0 - tz = PyCalendarTimezone(utc=self.mTZUTC, tzid=self.mTZID) - return tz.timeZoneSecondsOffset( self ) + if self.mTZOffset is None: + tz = PyCalendarTimezone(utc=self.mTZUTC, tzid=self.mTZID) + self.mTZOffset = tz.timeZoneSecondsOffset(self, relative_to_utc) + return self.mTZOffset + - def timeZoneDescriptor( self ): + def timeZoneDescriptor(self): tz = PyCalendarTimezone(utc=self.mTZUTC, tzid=self.mTZID) - return tz.timeZoneDescriptor( self ) + return tz.timeZoneDescriptor(self) - def changed( self ): + + def changed(self): self.mPosixTimeCached = False + self.mTZOffset = None + - def daysSince1970( self ): + def daysSince1970(self): # Add days between 1970 and current year (ignoring leap days) - result = ( self.mYear - 1970 ) * 365 + result = (self.mYear - 1970) * 365 # Add leap days between years - result += utils.leapDaysSince1970( self.mYear - 1970 ) + result += utils.leapDaysSince1970(self.mYear - 1970) # Add days in current year up to current month (includes leap day for # current year as needed) - result += utils.daysUptoMonth( self.mMonth, self.mYear ) + result += utils.daysUptoMonth(self.mMonth, self.mYear) # Add days in month result += self.mDay - 1 diff -Nru pycalendar-2.0~svn188/src/pycalendar/datetimevalue.py pycalendar-2.0~svn13177/src/pycalendar/datetimevalue.py --- pycalendar-2.0~svn188/src/pycalendar/datetimevalue.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/datetimevalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,24 +23,31 @@ def __init__(self, value=None): self.mValue = value if value is not None else PyCalendarDateTime() + def duplicate(self): return PyCalendarDateTimeValue(self.mValue.duplicate()) + def getType(self): return (PyCalendarValue.VALUETYPE_DATETIME, PyCalendarValue.VALUETYPE_DATE)[self.mValue.isDateOnly()] + def parse(self, data, fullISO=False): self.mValue.parse(data, fullISO) + def generate(self, os): self.mValue.generate(os) + def writeXML(self, node, namespace): self.mValue.writeXML(node, namespace) + def getValue(self): return self.mValue + def setValue(self, value): self.mValue = value diff -Nru pycalendar-2.0~svn188/src/pycalendar/definitions.py pycalendar-2.0~svn13177/src/pycalendar/definitions.py --- pycalendar-2.0~svn188/src/pycalendar/definitions.py 2011-11-18 22:06:33.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/definitions.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -89,7 +89,7 @@ ePartRole_Optional = 2 ePartRole_Non = 3 -cICalAttribute_ROLE_CHAIR = "CHAIR" +cICalAttribute_ROLE_CHAIR = "CHAIR" cICalAttribute_ROLE_REQ_PART = "REQ-PARTICIPANT" cICalAttribute_ROLE_OPT_PART = "OPT-PARTICIPANT" cICalAttribute_ROLE_NON_PART = "NON-PARTICIPANT" @@ -102,10 +102,10 @@ eCutype_Unknown = 4 cICalAttribute_CUTYPE_INDIVIDUAL = "INDIVIDUAL" -cICalAttribute_CUTYPE_GROUP = "GROUP" -cICalAttribute_CUTYPE_RESOURCE = "RESOURCE" -cICalAttribute_CUTYPE_ROOM = "ROOM" -cICalAttribute_CUTYPE_UNKNOWN = "UNKNOWN" +cICalAttribute_CUTYPE_GROUP = "GROUP" +cICalAttribute_CUTYPE_RESOURCE = "RESOURCE" +cICalAttribute_CUTYPE_ROOM = "ROOM" +cICalAttribute_CUTYPE_UNKNOWN = "UNKNOWN" # 5545 Value types @@ -137,6 +137,7 @@ # Apple Extensions cICalProperty_XWRCALNAME = "X-WR-CALNAME" cICalProperty_XWRCALDESC = "X-WR-CALDESC" +cICalProperty_XWRALARMUID = "X-WR-ALARMUID" # 5545 Component Property names @@ -214,20 +215,20 @@ eRecurrence_MONTHLY = 5 eRecurrence_YEARLY = 6 -eRecurrence_FREQ = 0 -eRecurrence_UNTIL = 1 -eRecurrence_COUNT = 2 -eRecurrence_INTERVAL = 3 -eRecurrence_BYSECOND = 4 -eRecurrence_BYMINUTE = 5 -eRecurrence_BYHOUR = 6 -eRecurrence_BYDAY = 7 +eRecurrence_FREQ = 0 +eRecurrence_UNTIL = 1 +eRecurrence_COUNT = 2 +eRecurrence_INTERVAL = 3 +eRecurrence_BYSECOND = 4 +eRecurrence_BYMINUTE = 5 +eRecurrence_BYHOUR = 6 +eRecurrence_BYDAY = 7 eRecurrence_BYMONTHDAY = 8 -eRecurrence_BYYEARDAY = 9 -eRecurrence_BYWEEKNO = 10 -eRecurrence_BYMONTH = 11 -eRecurrence_BYSETPOS = 12 -eRecurrence_WKST = 13 +eRecurrence_BYYEARDAY = 9 +eRecurrence_BYWEEKNO = 10 +eRecurrence_BYMONTH = 11 +eRecurrence_BYSETPOS = 12 +eRecurrence_WKST = 13 cICalValue_RECUR_FREQ = "FREQ" cICalValue_RECUR_FREQ_LEN = 5 diff -Nru pycalendar-2.0~svn188/src/pycalendar/dummyvalue.py pycalendar-2.0~svn13177/src/pycalendar/dummyvalue.py --- pycalendar-2.0~svn188/src/pycalendar/dummyvalue.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/dummyvalue.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,55 +0,0 @@ -## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -## - -# iCalendar UTC Offset value - -from pycalendar.value import PyCalendarValue - -class PyCalendarDummyValue( PyCalendarValue ): - - def __init__( self, type ): - self.mType = type - self.mValue = '' - - def duplicate(self): - other = PyCalendarDummyValue(self.mType) - other.mValue = self.mValue - return other - - def getType( self ): - return self.mType - - def parse( self, data ): - self.mValue = data - - # os - StringIO object - def generate( self, os ): - try: - os.write( self.mValue ) - except: - pass - - def writeXML(self, node, namespace): - value = self.getXMLNode(node, namespace) - value.text = self.mValue - - def getValue( self ): - return self.mValue - - def setValue( self, value ): - self.mValue = value - -PyCalendarValue.registerType("DUMMY", PyCalendarDummyValue, None) diff -Nru pycalendar-2.0~svn188/src/pycalendar/duration.py pycalendar-2.0~svn13177/src/pycalendar/duration.py --- pycalendar-2.0~svn188/src/pycalendar/duration.py 2011-08-31 20:47:52.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/duration.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,7 +20,7 @@ class PyCalendarDuration(ValueMixin): - def __init__(self, duration = None, weeks=0, days=0, hours=0, minutes=0, seconds=0): + def __init__(self, duration=None, weeks=0, days=0, hours=0, minutes=0, seconds=0): self.mForward = True self.mWeeks = 0 @@ -29,11 +29,12 @@ self.mHours = 0 self.mMinutes = 0 self.mSeconds = 0 - + if duration is None: duration = (((weeks * 7 + days) * 24 + hours) * 60 + minutes) * 60 + seconds self.setDuration(duration) + def duplicate(self): other = PyCalendarDuration(None) other.mForward = self.mForward @@ -47,22 +48,28 @@ return other + def __hash__(self): return hash(self.getTotalSeconds()) - def __eq__( self, comp ): + + def __eq__(self, comp): return self.getTotalSeconds() == comp.getTotalSeconds() - def __gt__( self, comp ): + + def __gt__(self, comp): return self.getTotalSeconds() > comp.getTotalSeconds() - def __lt__( self, comp ): - return self.getTotalSeconds() < comp.getTotalSeconds() + + def __lt__(self, comp): + return self.getTotalSeconds() < comp.getTotalSeconds() + def getTotalSeconds(self): return [1, -1][not self.mForward] \ * (self.mSeconds + (self.mMinutes + (self.mHours + (self.mDays + (self.mWeeks * 7)) * 24) * 60) * 60) + def setDuration(self, seconds): self.mForward = seconds >= 0 @@ -95,59 +102,67 @@ self.mWeeks = 0 + def getForward(self): return self.mForward + def getWeeks(self): return self.mWeeks + def getDays(self): return self.mDays + def getHours(self): return self.mHours + def getMinutes(self): return self.mMinutes + def getSeconds(self): return self.mSeconds + @classmethod def parseText(cls, data): dur = cls() dur.parse(data) return dur + def parse(self, data): # parse format ([+]/-) "P" (dur-date / dur-time / dur-week) try: offset = 0 maxoffset = len(data) - + # Look for +/- self.mForward = True if data[offset] in ('-', '+'): self.mForward = data[offset] == '+' offset += 1 - + # Must have a 'P' if data[offset] != "P": raise ValueError offset += 1 - + # Look for time if data[offset] != "T": # Must have a number num, offset = strtoul(data, offset) - + # Now look at character if data[offset] == "W": # Have a number of weeks self.mWeeks = num offset += 1 - + # There cannot be anything else after this so just exit if offset != maxoffset: if ParserContext.INVALID_DURATION_VALUE != ParserContext.PARSER_RAISE: @@ -158,18 +173,18 @@ # Have a number of days self.mDays = num offset += 1 - + # Look for more data - exit if none if offset == maxoffset: return - + # Look for time - exit if none if data[offset] != "T": raise ValueError else: # Error in format raise ValueError - + # Have time offset += 1 @@ -181,33 +196,33 @@ else: return num, offset = strtoul(data, offset) - + # Look for hour if data[offset] == "H": # Get hours self.mHours = num offset += 1 - + # Look for more data - exit if none if offset == maxoffset: return - + # Parse the next number num, offset = strtoul(data, offset) - + # Look for minute if data[offset] == "M": # Get hours self.mMinutes = num offset += 1 - + # Look for more data - exit if none if offset == maxoffset: return - + # Parse the next number num, offset = strtoul(data, offset) - + # Look for seconds if data[offset] == "S": # Get hours @@ -217,12 +232,13 @@ # No more data - exit if offset == maxoffset: return - + raise ValueError except IndexError: raise ValueError + def generate(self, os): try: if not self.mForward and (self.mWeeks or self.mDays or self.mHours or self.mMinutes or self.mSeconds): @@ -240,10 +256,10 @@ if self.mHours != 0: os.write("%dH" % (self.mHours,)) - + if (self.mMinutes != 0) or ((self.mHours != 0) and (self.mSeconds != 0)): os.write("%dM" % (self.mMinutes,)) - + if self.mSeconds != 0: os.write("%dS" % (self.mSeconds,)) elif self.mDays == 0: @@ -251,5 +267,6 @@ except: pass + def writeXML(self, node, namespace): node.text = self.getText() diff -Nru pycalendar-2.0~svn188/src/pycalendar/durationvalue.py pycalendar-2.0~svn13177/src/pycalendar/durationvalue.py --- pycalendar-2.0~svn188/src/pycalendar/durationvalue.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/durationvalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,29 +20,36 @@ class PyCalendarDurationValue(PyCalendarValue): - def __init__(self, value = None): + def __init__(self, value=None): self.mValue = value if value is not None else PyCalendarDuration() + def duplicate(self): return PyCalendarDurationValue(self.mValue.duplicate()) + def getType(self): return PyCalendarValue.VALUETYPE_DURATION + def parse(self, data): self.mValue.parse(data) + def generate(self, os): self.mValue.generate(os) + def writeXML(self, node, namespace): value = self.getXMLNode(node, namespace) value.text = self.mValue.writeXML() + def getValue(self): return self.mValue - def setValue(self, value): + + def setValue(self, value): self.mValue = value PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_DURATION, PyCalendarDurationValue, xmldefs.value_duration) diff -Nru pycalendar-2.0~svn188/src/pycalendar/exceptions.py pycalendar-2.0~svn13177/src/pycalendar/exceptions.py --- pycalendar-2.0~svn188/src/pycalendar/exceptions.py 2011-06-30 16:03:45.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/exceptions.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,15 +20,23 @@ self.mReason = reason self.mData = data + + class PyCalendarInvalidData(PyCalendarError): pass + + class PyCalendarInvalidProperty(PyCalendarError): pass + + class PyCalendarValidationError(PyCalendarError): pass + + class PyCalendarNoTimezoneInDatabase(Exception): def __init__(self, dbpath, tzid): diff -Nru pycalendar-2.0~svn188/src/pycalendar/freebusy.py pycalendar-2.0~svn13177/src/pycalendar/freebusy.py --- pycalendar-2.0~svn188/src/pycalendar/freebusy.py 2011-02-25 22:03:42.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/freebusy.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -21,29 +21,36 @@ BUSYUNAVAILABLE = 2 BUSY = 3 - def __init__( self, type = None, period = None ): - + def __init__(self, type=None, period=None): + self.mType = type if type else PyCalendarFreeBusy.FREE self.mPeriod = period.duplicate() if period is not None else None + def duplicate(self): return PyCalendarFreeBusy(self.mType, self.mPeriod) - def setType( self, type ): + + def setType(self, type): self.mType = type - def getType( self ): + + def getType(self): return self.mType - def setPeriod( self, period ): + + def setPeriod(self, period): self.mPeriod = period.duplicate() - def getPeriod( self ): + + def getPeriod(self): return self.mPeriod - def isPeriodOverlap( self, period ): - return self.mPeriod.isPeriodOverlap( period ) - def resolveOverlaps( self, fb ): + def isPeriodOverlap(self, period): + return self.mPeriod.isPeriodOverlap(period) + + + def resolveOverlaps(self, fb): # TODO: pass diff -Nru pycalendar-2.0~svn188/src/pycalendar/icalendar/__init__.py pycalendar-2.0~svn13177/src/pycalendar/icalendar/__init__.py --- pycalendar-2.0~svn188/src/pycalendar/icalendar/__init__.py 2011-03-23 17:53:31.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/icalendar/__init__.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,16 +1,15 @@ # -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## - diff -Nru pycalendar-2.0~svn188/src/pycalendar/icalendar/tests/__init__.py pycalendar-2.0~svn13177/src/pycalendar/icalendar/tests/__init__.py --- pycalendar-2.0~svn188/src/pycalendar/icalendar/tests/__init__.py 2011-03-23 17:53:31.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/icalendar/tests/__init__.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,16 +1,15 @@ # -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## - diff -Nru pycalendar-2.0~svn188/src/pycalendar/icalendar/tests/test_validation.py pycalendar-2.0~svn13177/src/pycalendar/icalendar/tests/test_validation.py --- pycalendar-2.0~svn188/src/pycalendar/icalendar/tests/test_validation.py 2011-11-18 22:06:33.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/icalendar/tests/test_validation.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,9 +19,9 @@ import unittest class TestValidation(unittest.TestCase): - + def test_basic(self): - + data = ( ( "No problems", @@ -63,15 +63,16 @@ )), ), ) - + for title, item, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(item) fixed, unfixed = cal.validate(doFix=False) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) + def test_mode_no_fix_no_raise(self): - + data = ( ( "OK", @@ -221,8 +222,9 @@ self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) + def test_mode_fix_no_raise(self): - + data = ( ( "OK", @@ -371,8 +373,9 @@ self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) + def test_mode_no_fix_raise(self): - + data = ( ( "OK", @@ -532,8 +535,9 @@ self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) + def test_mode_fix_raise(self): - + data = ( ( "OK", @@ -693,6 +697,7 @@ self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) + def test_vevent(self): data = ( ( @@ -970,7 +975,7 @@ set(), ), ) - + for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) @@ -978,6 +983,7 @@ self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) + def test_vfreebusy(self): data = ( ( @@ -1039,7 +1045,7 @@ )), ), ) - + for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) @@ -1047,6 +1053,7 @@ self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) + def test_vjournal(self): data = ( ( @@ -1106,7 +1113,7 @@ )), ), ) - + for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) @@ -1114,6 +1121,7 @@ self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) + def test_vtimezone(self): data = ( ( @@ -1250,7 +1258,7 @@ )), ), ) - + for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) @@ -1258,6 +1266,7 @@ self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) + def test_vtodo(self): data = ( ( @@ -1471,7 +1480,7 @@ set(), ), ) - + for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) @@ -1479,6 +1488,7 @@ self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) + def test_vavailability(self): data = ( ( @@ -1668,7 +1678,7 @@ set(), ), ) - + for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) @@ -1676,6 +1686,7 @@ self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) + def test_available(self): data = ( ( @@ -1861,7 +1872,7 @@ set(), ), ) - + for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) @@ -1869,6 +1880,7 @@ self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) + def test_valarm(self): data = ( ( @@ -2550,7 +2562,7 @@ set(), ), ) - + for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) @@ -2558,6 +2570,7 @@ self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) + def test_xcomponents(self): data = ( ( @@ -2698,7 +2711,7 @@ )), ), ) - + for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) @@ -2706,6 +2719,7 @@ self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) + def test_STATUS_fix(self): """ Test calendarserver issue where multiple STATUS properties can be present in components. @@ -3166,11 +3180,10 @@ )), ), ) - + for title, test_old, test_new, test_doFix, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=test_doFix) self.assertEqual(str(cal), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) - diff -Nru pycalendar-2.0~svn188/src/pycalendar/icalendar/validation.py pycalendar-2.0~svn13177/src/pycalendar/icalendar/validation.py --- pycalendar-2.0~svn188/src/pycalendar/icalendar/validation.py 2011-11-18 22:06:33.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/icalendar/validation.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff -Nru pycalendar-2.0~svn188/src/pycalendar/__init__.py pycalendar-2.0~svn13177/src/pycalendar/__init__.py --- pycalendar-2.0~svn188/src/pycalendar/__init__.py 2011-03-09 21:15:39.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/__init__.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,7 +23,6 @@ "datetime", "datetimevalue", "definitions", - "dummyvalue", "duration", "durationvalue", "exceptions", @@ -42,6 +41,7 @@ "textvalue", "timezone", "timezonedb", + "unknownvalue", "urivalue", "utcoffsetvalue", "valarm", @@ -57,9 +57,19 @@ ] # Import these to register the values -import binaryvalue, caladdressvalue, datetimevalue, dummyvalue, durationvalue, \ - integervalue, multivalue, periodvalue, recurrencevalue, \ - requeststatusvalue, textvalue, urivalue, utcoffsetvalue - +import binaryvalue +import caladdressvalue +import datetimevalue +import durationvalue +import integervalue +import multivalue +import periodvalue +import recurrencevalue +import requeststatusvalue +import textvalue +import unknownvalue +import urivalue +import utcoffsetvalue + # Import these to force static initialisation import property diff -Nru pycalendar-2.0~svn188/src/pycalendar/integervalue.py pycalendar-2.0~svn13177/src/pycalendar/integervalue.py --- pycalendar-2.0~svn188/src/pycalendar/integervalue.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/integervalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,35 +19,42 @@ from pycalendar import xmldefs from pycalendar.value import PyCalendarValue -class PyCalendarIntegerValue( PyCalendarValue ): +class PyCalendarIntegerValue(PyCalendarValue): - def __init__(self, value = None): + def __init__(self, value=None): self.mValue = value if value is not None else 0 + def duplicate(self): return PyCalendarIntegerValue(self.mValue) + def getType(self): return PyCalendarValue.VALUETYPE_INTEGER - def parse( self, data ): + + def parse(self, data): self.mValue = int(data) - + + # os - StringIO object - def generate( self, os ): + def generate(self, os): try: - os.write( str(self.mValue) ) + os.write(str(self.mValue)) except: pass + def writeXML(self, node, namespace): value = self.getXMLNode(node, namespace) value.text = str(self.mValue) + def getValue(self): return self.mValue - def setValue( self, value ): + + def setValue(self, value): self.mValue = value PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_INTEGER, PyCalendarIntegerValue, xmldefs.value_integer) diff -Nru pycalendar-2.0~svn188/src/pycalendar/itipdefinitions.py pycalendar-2.0~svn13177/src/pycalendar/itipdefinitions.py --- pycalendar-2.0~svn188/src/pycalendar/itipdefinitions.py 2011-03-09 21:15:39.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/itipdefinitions.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff -Nru pycalendar-2.0~svn188/src/pycalendar/locale.py pycalendar-2.0~svn13177/src/pycalendar/locale.py --- pycalendar-2.0~svn188/src/pycalendar/locale.py 2007-10-25 02:13:56.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/locale.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,20 +19,20 @@ SHORT = 1 ABBREVIATED = 2 -cLongDays = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ] +cLongDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] -cShortDays = [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ] +cShortDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] -cAbbrevDays = [ "S", "M", "T", "W", "T", "F", "S" ] +cAbbrevDays = ["S", "M", "T", "W", "T", "F", "S"] -cLongMonths = [ "", "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" ] +cLongMonths = ["", "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"] -cShortMonths = [ "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ] +cShortMonths = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] -cAbbrevMonths = [ "", "J", "F", "M", "A", "M", "J", - "J", "A", "S", "O", "N", "D" ] +cAbbrevMonths = ["", "J", "F", "M", "A", "M", "J", + "J", "A", "S", "O", "N", "D"] s24HourTime = False sDDMMDate = False @@ -41,21 +41,24 @@ def getDay(day, strl): return {LONG: cLongDays[day], SHORT: cShortDays[day], ABBREVIATED: cAbbrevDays[day]}[strl] + + # 1..12 - January - December def getMonth(month, strl): return {LONG: cLongMonths[month], SHORT: cShortMonths[month], ABBREVIATED: cAbbrevMonths[month]}[strl] + + # Use 24 hour time display def use24HourTime(): # TODO: get 24 hour option from system prefs + return s24HourTime + - return s24HourTime; # Use DD/MM date display def useDDMMDate(): # TODO: get 24 hour option from system prefs - - return sDDMMDate; - + return sDDMMDate diff -Nru pycalendar-2.0~svn188/src/pycalendar/manager.py pycalendar-2.0~svn13177/src/pycalendar/manager.py --- pycalendar-2.0~svn188/src/pycalendar/manager.py 2011-04-27 15:38:06.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/manager.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -21,7 +21,8 @@ sICalendarManager = None def __init__(self): - self.mDefaultTimezone = PyCalendarTimezone() + PyCalendarTimezone.sDefaultTimezone = PyCalendarTimezone() + def initManager(self): # TODO: - read in timezones from vtimezones.ics file @@ -30,6 +31,7 @@ # hard-coded to my personal prefs! self.setDefaultTimezone(PyCalendarTimezone(utc=False, tzid="US/Eastern")) + def setDefaultTimezoneID(self, tzid): # Check for UTC if tzid == "UTC": @@ -39,16 +41,19 @@ temp = PyCalendarTimezone(utc=False, tzid=tzid) self.setDefaultTimezone(temp) + def setDefaultTimezone(self, tzid): - self.mDefaultTimezone = tzid + PyCalendarTimezone.sDefaultTimezone = tzid + def getDefaultTimezoneID(self): - if self.mDefaultTimezone.getUTC(): + if PyCalendarTimezone.sDefaultTimezone.getUTC(): return "UTC" else: - return self.mDefaultTimezone.getTimezoneID() + return PyCalendarTimezone.sDefaultTimezone.getTimezoneID() + def getDefaultTimezone(self): - return self.mDefaultTimezone + return PyCalendarTimezone.sDefaultTimezone PyCalendarManager.sICalendarManager = PyCalendarManager() diff -Nru pycalendar-2.0~svn188/src/pycalendar/multivalue.py pycalendar-2.0~svn13177/src/pycalendar/multivalue.py --- pycalendar-2.0~svn188/src/pycalendar/multivalue.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/multivalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,63 +16,79 @@ from pycalendar.value import PyCalendarValue -class PyCalendarMultiValue( PyCalendarValue ): +class PyCalendarMultiValue(PyCalendarValue): - def __init__( self, type ): + def __init__(self, type): self.mType = type self.mValues = [] + def __hash__(self): return hash(tuple(self.mValues)) + def duplicate(self): other = PyCalendarMultiValue(self.mType) other.mValues = [i.duplicate() for i in self.mValues] return other - def getType( self ): + + def getType(self): return self.mType + def getRealType(self): return PyCalendarValue.VALUETYPE_MULTIVALUE - def getValue( self ): + + def getValue(self): return self.mValues - def getValues( self ): + + def getValues(self): return self.mValues - def addValue( self, value ): - self.mValues.append( value ) + + def addValue(self, value): + self.mValues.append(value) + def setValue(self, value): - self.mValues = [PyCalendarValue.createFromType(self.mType).setValue(v) for v in value] + newValues = [] + for v in value: + pyCalendarValue = PyCalendarValue.createFromType(self.mType) + pyCalendarValue.setValue(v) + newValues.append(pyCalendarValue) + self.mValues = newValues + - def parse( self, data ): + def parse(self, data): # Tokenize on comma if "," in data: - tokens = data.split( "," ) + tokens = data.split(",") else: tokens = (data,) for token in tokens: # Create single value, and parse data - value = PyCalendarValue.createFromType( self.mType ) - value.parse( token ) - self.mValues.append( value ) + value = PyCalendarValue.createFromType(self.mType) + value.parse(token) + self.mValues.append(value) - def generate( self, os ): + + def generate(self, os): try: first = True for iter in self.mValues: if first: first = False else: - os.write( "," ) - iter.generate( os ) + os.write(",") + iter.generate(os) except: pass + def writeXML(self, node, namespace): for iter in self.mValues: iter.writeXML(node, namespace) diff -Nru pycalendar-2.0~svn188/src/pycalendar/n.py pycalendar-2.0~svn13177/src/pycalendar/n.py --- pycalendar-2.0~svn188/src/pycalendar/n.py 2011-12-12 18:58:52.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/n.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -36,50 +36,66 @@ def __init__(self, last="", first="", middle="", prefix="", suffix=""): self.mValue = (last, first, middle, prefix, suffix) + def duplicate(self): return N(*self.mValue) + def __hash__(self): return hash(self.mValue) + def __repr__(self): return "N %s" % (self.getText(),) - def __eq__( self, comp ): + + def __eq__(self, comp): return self.mValue == comp.mValue + def getFirst(self): return self.mValue[N.FIRST] - + + def setFirst(self, value): self.mValue[N.FIRST] = value + def getLast(self): return self.mValue[N.LAST] - + + def setLast(self, value): self.mValue[N.LAST] = value + def getMiddle(self): return self.mValue[N.MIDDLE] - + + def setMiddle(self, value): self.mValue[N.MIDDLE] = value + def getPrefix(self): return self.mValue[N.PREFIX] - + + def setPrefix(self, value): self.mValue[N.PREFIX] = value + def getSuffix(self): return self.mValue[N.SUFFIX] - + + def setSuffix(self, value): self.mValue[N.SUFFIX] = value + def getFullName(self): - + + def _stringOrList(item): return item if isinstance(item, basestring) else " ".join(item) @@ -91,14 +107,18 @@ return " ".join(results) + def parse(self, data): self.mValue = utils.parseDoubleNestedList(data, N.MAXITEMS) + def generate(self, os): utils.generateDoubleNestedList(os, self.mValue) + def getValue(self): return self.mValue + def setValue(self, value): self.mValue = value diff -Nru pycalendar-2.0~svn188/src/pycalendar/nvalue.py pycalendar-2.0~svn13177/src/pycalendar/nvalue.py --- pycalendar-2.0~svn188/src/pycalendar/nvalue.py 2011-05-31 19:32:49.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/nvalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,26 +19,32 @@ from pycalendar.n import N from pycalendar.value import PyCalendarValue -class NValue( PyCalendarValue ): +class NValue(PyCalendarValue): - def __init__(self, value = None): + def __init__(self, value=None): self.mValue = value if value is not None else N() + def duplicate(self): return NValue(self.mValue.duplicate()) + def getType(self): return PyCalendarValue.VALUETYPE_N + def parse(self, data): self.mValue.parse(data) + def generate(self, os): self.mValue.generate(os) + def getValue(self): return self.mValue + def setValue(self, value): self.mValue = value diff -Nru pycalendar-2.0~svn188/src/pycalendar/orgvalue.py pycalendar-2.0~svn13177/src/pycalendar/orgvalue.py --- pycalendar-2.0~svn188/src/pycalendar/orgvalue.py 2011-05-31 19:32:49.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/orgvalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -27,21 +27,27 @@ def __init__(self, value=None): self.mValue = value + def duplicate(self): return OrgValue(self.mValue) + def getType(self): return PyCalendarValue.VALUETYPE_ORG + def parse(self, data): self.mValue = utils.parseTextList(data, ';') - + + def generate(self, os): utils.generateTextList(os, self.mValue, ';') + def getValue(self): return self.mValue + def setValue(self, value): self.mValue = value diff -Nru pycalendar-2.0~svn188/src/pycalendar/outputfilter.py pycalendar-2.0~svn13177/src/pycalendar/outputfilter.py --- pycalendar-2.0~svn188/src/pycalendar/outputfilter.py 2007-04-23 01:31:53.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/outputfilter.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -24,65 +24,78 @@ self.mAllProperties = False self.mProperties = None + def getType(self): return self.mType + # Test to see if component type can be written out - def testComponent(self, type): - return self.mType == type + def testComponent(self, oftype): + return self.mType == oftype + def isAllSubComponents(self): return self.mAllSubComponents + def setAllSubComponents(self): self.mAllSubComponents = True self.mSubComponents = None + def addSubComponent(self, comp): if self.mSubComponents == None: self.mSubComponents = {} self.mSubComponents[comp.getType()] = comp + # Test to see if sub-component type can be written out - def testSubComponent(self, type): + def testSubComponent(self, oftype): return self.mAllSubComponents or (self.mSubComponents is not None) \ - and self.mSubComponents.has_key(type) + and oftype in self.mSubComponents + def hasSubComponentFilters(self): return self.mSubComponents is not None + def getSubComponentFilter(self, type): if self.mSubComponents is not None: return self.mSubComponents.get(type, None) else: return None + def isAllProperties(self): return self.mAllProperties + def setAllProperties(self): self.mAllProperties = True self.mProperties = None + def addProperty(self, name, no_value): if self.mProperties is None: self.mProperties = {} self.mProperties[name] = no_value + def hasPropertyFilters(self): return self.mProperties is not None + # Test to see if property can be written out and also return whether # the property value is used def testPropertyValue(self, name): if self.mAllProperties: return True, False - + if self.mProperties is None: return False, False - + result = self.mProperties.get(name, None) return result is not None, result diff -Nru pycalendar-2.0~svn188/src/pycalendar/parser.py pycalendar-2.0~svn13177/src/pycalendar/parser.py --- pycalendar-2.0~svn188/src/pycalendar/parser.py 2011-08-25 17:50:54.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/parser.py 2014-02-19 19:56:40.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2013 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -22,32 +22,57 @@ """ ( - PARSER_ALLOW, # Pass the "suspect" data through to the object model - PARSER_IGNORE, # Ignore the "suspect" data - PARSER_FIX, # Fix (or if not possible ignore) the "suspect" data - PARSER_RAISE, # Raise an exception + PARSER_ALLOW, # Pass the "suspect" data through to the object model + PARSER_IGNORE, # Ignore the "suspect" data + PARSER_FIX, # Fix (or if not possible ignore) the "suspect" data + PARSER_RAISE, # Raise an exception ) = range(4) - - # Some clients escape ":" - fix + + # Some clients escape ":" - fix INVALID_COLON_ESCAPE_SEQUENCE = PARSER_FIX - - # Other escape sequences - raise + + # Other escape sequences - raise INVALID_ESCAPE_SEQUENCES = PARSER_RAISE - + # Some client generate empty lines in the body of the data BLANK_LINES_IN_DATA = PARSER_FIX # Some clients still generate vCard 2 parameter syntax VCARD_2_NO_PARAMETER_VALUES = PARSER_ALLOW - + # Use this to fix v2 BASE64 to v3 ENCODING=b - only PARSER_FIX or PARSER_ALLOW VCARD_2_BASE64 = PARSER_FIX + # Allow DATE values when DATETIME specified (and vice versa) + INVALID_DATETIME_VALUE = PARSER_FIX + + # Allow leading space instead of leading zeros for year in DATE or DATE-TIME values + INVALID_DATETIME_LEADINGSPACE = PARSER_ALLOW + # Allow slightly invalid DURATION values INVALID_DURATION_VALUE = PARSER_FIX # Truncate over long ADR and N values INVALID_ADR_N_VALUES = PARSER_FIX - # REQUEST-STATUS values with \; as the first separator + # REQUEST-STATUS values with \; as the first separator or single element INVALID_REQUEST_STATUS_VALUE = PARSER_FIX + + # Remove \-escaping in URI values when parsing - only PARSER_FIX or PARSER_ALLOW + BACKSLASH_IN_URI_VALUE = PARSER_FIX + + @staticmethod + def allRaise(): + """ + Make all tests raise an error - never fix + """ + ParserContext.INVALID_COLON_ESCAPE_SEQUENCE = ParserContext.PARSER_RAISE + ParserContext.INVALID_ESCAPE_SEQUENCES = ParserContext.PARSER_RAISE + ParserContext.BLANK_LINES_IN_DATA = ParserContext.PARSER_RAISE + ParserContext.VCARD_2_NO_PARAMETER_VALUES = ParserContext.PARSER_RAISE + ParserContext.VCARD_2_BASE64 = ParserContext.PARSER_RAISE + ParserContext.INVALID_DURATION_VALUE = ParserContext.PARSER_RAISE + ParserContext.INVALID_ADR_N_VALUES = ParserContext.PARSER_RAISE + ParserContext.INVALID_REQUEST_STATUS_VALUE = ParserContext.PARSER_RAISE + ParserContext.BACKSLASH_IN_URI_VALUE = ParserContext.PARSER_RAISE + ParserContext.INVALID_REQUEST_STATUS = ParserContext.PARSER_RAISE diff -Nru pycalendar-2.0~svn188/src/pycalendar/period.py pycalendar-2.0~svn13177/src/pycalendar/period.py --- pycalendar-2.0~svn188/src/pycalendar/period.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/period.py 2013-05-21 13:34:14.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -22,10 +22,10 @@ class PyCalendarPeriod(ValueMixin): - def __init__( self, start = None, end = None, duration = None ): - + def __init__(self, start=None, end=None, duration=None): + self.mStart = start if start is not None else PyCalendarDateTime() - + if end is not None: self.mEnd = end self.mDuration = self.mEnd - self.mStart @@ -39,59 +39,76 @@ self.mDuration = PyCalendarDuration() self.mUseDuration = False + def duplicate(self): other = PyCalendarPeriod(start=self.mStart.duplicate(), end=self.mEnd.duplicate()) other.mUseDuration = self.mUseDuration return other + def __hash__(self): return hash((self.mStart, self.mEnd,)) + def __repr__(self): return "PyCalendarPeriod %s" % (self.getText(),) + def __str__(self): return self.getText() - def __eq__( self, comp ): + + def __eq__(self, comp): return self.mStart == comp.mStart and self.mEnd == comp.mEnd - def __gt__( self, comp ): + + def __gt__(self, comp): return self.mStart > comp - def __lt__( self, comp ): - return self.mStart < comp.mStart \ - or ( self.mStart == comp.mStart ) and self.mEnd < comp.mEnd - def parse( self, data ): - splits = data.split( '/', 1 ) + def __lt__(self, comp): + return self.mStart < comp.mStart \ + or (self.mStart == comp.mStart) and self.mEnd < comp.mEnd + + + @classmethod + def parseText(cls, data): + period = cls() + period.parse(data) + return period + + + def parse(self, data): + splits = data.split('/', 1) if len(splits) == 2: start = splits[0] end = splits[1] - self.mStart.parse( start ) + self.mStart.parse(start) if end[0] == 'P': - self.mDuration.parse( end ) + self.mDuration.parse(end) self.mUseDuration = True self.mEnd = self.mStart + self.mDuration else: - self.mEnd.parse( end ) + self.mEnd.parse(end) self.mUseDuration = False self.mDuration = self.mEnd - self.mStart else: raise ValueError - def generate( self, os ): + + def generate(self, os): try: - self.mStart.generate( os ) - os.write( "/" ) + self.mStart.generate(os) + os.write("/") if self.mUseDuration: - self.mDuration.generate( os ) + self.mDuration.generate(os) else: - self.mEnd.generate( os ) + self.mEnd.generate(os) except: pass + def writeXML(self, node, namespace): start = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.period_start)) start.text = self.mStart.getXMLText() @@ -103,40 +120,51 @@ end = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.period_end)) end.text = self.mEnd.getXMLText() - def getStart( self ): + + def getStart(self): return self.mStart - def getEnd( self ): + + def getEnd(self): return self.mEnd - def getDuration( self ): + + def getDuration(self): return self.mDuration + def getUseDuration(self): return self.mUseDuration + def setUseDuration(self, use): self.mUseDuration = use - def isDateWithinPeriod( self, dt ): + + def isDateWithinPeriod(self, dt): # Inclusive start, exclusive end return dt >= self.mStart and dt < self.mEnd - def isDateBeforePeriod( self, dt ): + + def isDateBeforePeriod(self, dt): # Inclusive start return dt < self.mStart - def isDateAfterPeriod( self, dt ): + + def isDateAfterPeriod(self, dt): # Exclusive end return dt >= self.mEnd - def isPeriodOverlap( self, p ): + + def isPeriodOverlap(self, p): # Inclusive start, exclusive end - return not ( self.mStart >= p.mEnd or self.mEnd <= p.mStart ) + return not (self.mStart >= p.mEnd or self.mEnd <= p.mStart) + def adjustToUTC(self): self.mStart.adjustToUTC() self.mEnd.adjustToUTC() - def describeDuration( self ): + + def describeDuration(self): return "" diff -Nru pycalendar-2.0~svn188/src/pycalendar/periodvalue.py pycalendar-2.0~svn13177/src/pycalendar/periodvalue.py --- pycalendar-2.0~svn188/src/pycalendar/periodvalue.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/periodvalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,31 +18,38 @@ from pycalendar.period import PyCalendarPeriod from pycalendar.value import PyCalendarValue -class PyCalendarPeriodValue( PyCalendarValue ): +class PyCalendarPeriodValue(PyCalendarValue): - def __init__( self, value = None ): + def __init__(self, value=None): self.mValue = value if value is not None else PyCalendarPeriod() + def duplicate(self): return PyCalendarPeriodValue(self.mValue.duplicate()) - def getType( self ): + + def getType(self): return PyCalendarValue.VALUETYPE_PERIOD - def parse( self, data ): - self.mValue.parse( data ) - def generate( self, os ): - self.mValue.generate( os ) + def parse(self, data): + self.mValue.parse(data) + + + def generate(self, os): + self.mValue.generate(os) + def writeXML(self, node, namespace): value = self.getXMLNode(node, namespace) value.text = self.mValue.writeXML() - def getValue( self ): + + def getValue(self): return self.mValue - def setValue( self, value ): + + def setValue(self, value): self.mValue = value PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_PERIOD, PyCalendarPeriodValue, xmldefs.value_period) diff -Nru pycalendar-2.0~svn188/src/pycalendar/plaintextvalue.py pycalendar-2.0~svn13177/src/pycalendar/plaintextvalue.py --- pycalendar-2.0~svn188/src/pycalendar/plaintextvalue.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/plaintextvalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,32 +18,38 @@ from pycalendar.value import PyCalendarValue -class PyCalendarPlainTextValue( PyCalendarValue ): +class PyCalendarPlainTextValue(PyCalendarValue): def __init__(self, value=''): self.mValue = value + def duplicate(self): return self.__class__(self.mValue) - def parse( self, data ): + + def parse(self, data): # No decoding required self.mValue = data - + + # os - StringIO object - def generate( self, os ): + def generate(self, os): try: # No encoding required - os.write( self.mValue ) + os.write(self.mValue) except: pass + def writeXML(self, node, namespace): value = self.getXMLNode(node, namespace) value.text = self.mValue + def getValue(self): return self.mValue - def setValue( self, value ): + + def setValue(self, value): self.mValue = value diff -Nru pycalendar-2.0~svn188/src/pycalendar/property.py pycalendar-2.0~svn13177/src/pycalendar/property.py --- pycalendar-2.0~svn188/src/pycalendar/property.py 2011-11-18 22:06:33.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/property.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -21,7 +21,6 @@ from pycalendar.caladdressvalue import PyCalendarCalAddressValue from pycalendar.datetime import PyCalendarDateTime from pycalendar.datetimevalue import PyCalendarDateTimeValue -from pycalendar.dummyvalue import PyCalendarDummyValue from pycalendar.duration import PyCalendarDuration from pycalendar.durationvalue import PyCalendarDurationValue from pycalendar.exceptions import PyCalendarInvalidProperty @@ -33,8 +32,10 @@ from pycalendar.recurrence import PyCalendarRecurrence from pycalendar.recurrencevalue import PyCalendarRecurrenceValue from pycalendar.requeststatusvalue import PyCalendarRequestStatusValue +from pycalendar.unknownvalue import PyCalendarUnknownValue from pycalendar.urivalue import PyCalendarURIValue from pycalendar.utcoffsetvalue import PyCalendarUTCOffsetValue +from pycalendar.utils import decodeParameterValue from pycalendar.value import PyCalendarValue import cStringIO as StringIO import xml.etree.cElementTree as XML @@ -43,6 +44,12 @@ sDefaultValueTypeMap = { + # 5545 Section 3.7 + definitions.cICalProperty_CALSCALE : PyCalendarValue.VALUETYPE_TEXT, + definitions.cICalProperty_METHOD : PyCalendarValue.VALUETYPE_TEXT, + definitions.cICalProperty_PRODID : PyCalendarValue.VALUETYPE_TEXT, + definitions.cICalProperty_VERSION : PyCalendarValue.VALUETYPE_TEXT, + # 5545 Section 3.8.1 definitions.cICalProperty_ATTACH : PyCalendarValue.VALUETYPE_URI, definitions.cICalProperty_CATEGORIES : PyCalendarValue.VALUETYPE_TEXT, @@ -84,7 +91,7 @@ # 5545 Section 3.8.5 definitions.cICalProperty_EXDATE : PyCalendarValue.VALUETYPE_DATETIME, - definitions.cICalProperty_EXRULE : PyCalendarValue.VALUETYPE_RECUR, # 2445 only + definitions.cICalProperty_EXRULE : PyCalendarValue.VALUETYPE_RECUR, # 2445 only definitions.cICalProperty_RDATE : PyCalendarValue.VALUETYPE_DATETIME, definitions.cICalProperty_RRULE : PyCalendarValue.VALUETYPE_RECUR, @@ -106,8 +113,9 @@ definitions.cICalProperty_ACKNOWLEDGED : PyCalendarValue.VALUETYPE_DATETIME, # Apple Extensions - definitions.cICalProperty_XWRCALNAME : PyCalendarValue.VALUETYPE_TEXT, - definitions.cICalProperty_XWRCALDESC : PyCalendarValue.VALUETYPE_TEXT, + definitions.cICalProperty_XWRCALNAME : PyCalendarValue.VALUETYPE_TEXT, + definitions.cICalProperty_XWRCALDESC : PyCalendarValue.VALUETYPE_TEXT, + definitions.cICalProperty_XWRALARMUID : PyCalendarValue.VALUETYPE_TEXT, # Mulberry extensions definitions.cICalProperty_ACTION_X_SPEAKTEXT : PyCalendarValue.VALUETYPE_TEXT, @@ -163,19 +171,39 @@ definitions.cICalProperty_RDATE, )) - def __init__(self, name = None, value = None, valuetype = None): + @staticmethod + def registerDefaultValue(propname, valuetype): + if propname not in PyCalendarProperty.sDefaultValueTypeMap: + PyCalendarProperty.sDefaultValueTypeMap[propname] = valuetype + + + def __init__(self, name=None, value=None, valuetype=None): self._init_PyCalendarProperty() self.mName = name if name is not None else "" - if isinstance(value, int): - self._init_attr_value_int(value) + # The None check speeds up .duplicate() + if value is None: + pass + # Order these based on most likely occurrence to speed up this method elif isinstance(value, str): - self._init_attr_value_text(value, valuetype if valuetype else PyCalendarProperty.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarValue.VALUETYPE_TEXT)) + self._init_attr_value_text(value, valuetype if valuetype else PyCalendarProperty.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarValue.VALUETYPE_UNKNOWN)) elif isinstance(value, PyCalendarDateTime): self._init_attr_value_datetime(value) + elif isinstance(value, PyCalendarDuration): + self._init_attr_value_duration(value) + + elif isinstance(value, PyCalendarRecurrence): + self._init_attr_value_recur(value) + + elif isinstance(value, PyCalendarPeriod): + self._init_attr_value_period(value) + + elif isinstance(value, int): + self._init_attr_value_int(value) + elif isinstance(value, list): if name.upper() == definitions.cICalProperty_REQUEST_STATUS: self._init_attr_value_requeststatus(value) @@ -188,18 +216,10 @@ else: self._init_attr_value_datetimelist(value) - elif isinstance(value, PyCalendarDuration): - self._init_attr_value_duration(value) - - elif isinstance(value, PyCalendarPeriod): - self._init_attr_value_period(value) - - elif isinstance(value, PyCalendarRecurrence): - self._init_attr_value_recur(value) - elif isinstance(value, PyCalendarUTCOffsetValue): self._init_attr_value_utcoffset(value) + def duplicate(self): other = PyCalendarProperty(self.mName) for attrname, attrs in self.mAttributes.items(): @@ -208,6 +228,7 @@ return other + def __hash__(self): return hash(( self.mName, @@ -215,48 +236,66 @@ self.mValue, )) - def __ne__(self, other): return not self.__eq__(other) + + def __ne__(self, other): + return not self.__eq__(other) + + def __eq__(self, other): - if not isinstance(other, PyCalendarProperty): return False + if not isinstance(other, PyCalendarProperty): + return False return self.mName == other.mName and self.mValue == other.mValue and self.mAttributes == other.mAttributes + def __repr__(self): return "PyCalendarProperty: %s" % (self.getText(),) + def __str__(self): return self.getText() + def getName(self): return self.mName + def setName(self, name): self.mName = name + def getAttributes(self): return self.mAttributes + def setAttributes(self, attributes): - self.mAttributes = dict([(k.upper(), v) for k,v in attributes.iteritems()]) + self.mAttributes = dict([(k.upper(), v) for k, v in attributes.iteritems()]) + def hasAttribute(self, attr): - return self.mAttributes.has_key(attr.upper()) + return attr.upper() in self.mAttributes + def getAttributeValue(self, attr): return self.mAttributes[attr.upper()][0].getFirstValue() + def addAttribute(self, attr): self.mAttributes.setdefault(attr.getName().upper(), []).append(attr) + def replaceAttribute(self, attr): self.mAttributes[attr.getName().upper()] = [attr] + def removeAttributes(self, attr): - if self.mAttributes.has_key(attr.upper()): + if attr.upper() in self.mAttributes: del self.mAttributes[attr.upper()] + def getValue(self): return self.mValue + def getBinaryValue(self): if isinstance(self.mValue, PyCalendarBinaryValue): @@ -264,6 +303,7 @@ else: return None + def getCalAddressValue(self): if isinstance(self.mValue, PyCalendarCalAddressValue): @@ -271,6 +311,7 @@ else: return None + def getDateTimeValue(self): if isinstance(self.mValue, PyCalendarDateTimeValue): @@ -278,6 +319,7 @@ else: return None + def getDurationValue(self): if isinstance(self.mValue, PyCalendarDurationValue): @@ -285,6 +327,7 @@ else: return None + def getIntegerValue(self): if isinstance(self.mValue, PyCalendarIntegerValue): @@ -292,6 +335,7 @@ else: return None + def getMultiValue(self): if isinstance(self.mValue, PyCalendarMultiValue): @@ -299,6 +343,7 @@ else: return None + def getPeriodValue(self): if isinstance(self.mValue, PyCalendarPeriodValue): @@ -306,6 +351,7 @@ else: return None + def getRecurrenceValue(self): if isinstance(self.mValue, PyCalendarRecurrenceValue): @@ -313,6 +359,7 @@ else: return None + def getTextValue(self): if isinstance(self.mValue, PyCalendarPlainTextValue): @@ -320,6 +367,7 @@ else: return None + def getURIValue(self): if isinstance(self.mValue, PyCalendarURIValue): @@ -327,6 +375,7 @@ else: return None + def getUTCOffsetValue(self): if isinstance(self.mValue, PyCalendarUTCOffsetValue): @@ -334,6 +383,7 @@ else: return None + def parse(self, data): # Look for attribute or value delimiter prop_name, txt = stringutils.strduptokenstr(data, ";:") @@ -342,7 +392,7 @@ # We have the name self.mName = prop_name - + # TODO: Try to use static string for the name # Now loop getting data @@ -350,10 +400,10 @@ while txt: if txt[0] == ';': # Parse attribute - + # Move past delimiter txt = txt[1:] - + # Get quoted string or token attribute_name, txt = stringutils.strduptokenstr(txt, "=") if attribute_name is None: @@ -362,18 +412,18 @@ attribute_value, txt = stringutils.strduptokenstr(txt, ":;,") if attribute_value is None: raise PyCalendarInvalidProperty("Invalid property", data) - - # Now add attribute value - attrvalue = PyCalendarAttribute(name = attribute_name, value=attribute_value) + + # Now add attribute value (decode ^-escpaing) + attrvalue = PyCalendarAttribute(name=attribute_name, value=decodeParameterValue(attribute_value)) self.mAttributes.setdefault(attribute_name.upper(), []).append(attrvalue) - + # Look for additional values while txt[0] == ',': txt = txt[1:] attribute_value2, txt = stringutils.strduptokenstr(txt, ":;,") if attribute_value2 is None: raise PyCalendarInvalidProperty("Invalid property", data) - attrvalue.addValue(attribute_value2) + attrvalue.addValue(decodeParameterValue(attribute_value2)) elif txt[0] == ':': txt = txt[1:] self.createValue(txt) @@ -388,7 +438,7 @@ # We must have a value of some kind if self.mValue is None: raise PyCalendarInvalidProperty("Invalid property", data) - + return True @@ -397,6 +447,7 @@ self.generate(os) return os.getvalue() + def generate(self, os): # Write it out always with value @@ -404,12 +455,13 @@ def generateFiltered(self, os, filter): - + # Check for property in filter and whether value is written out test, novalue = filter.testPropertyValue(self.mName.upper()) if test: self.generateValue(os, novalue) + # Write out the actual property, possibly skipping the value def generateValue(self, os, novalue): @@ -453,15 +505,16 @@ while (temp[offset] > 0x7F) and ((ord(temp[offset]) & 0xC0) == 0x80): # Step back until we have a valid char offset -= 1 - + line = temp[start:offset] os.write(line) os.write("\r\n ") written += offset - start start = offset - + os.write("\r\n") - + + def writeXML(self, node, namespace): # Write it out always with value @@ -469,17 +522,18 @@ def generateFilteredXML(self, node, namespace, filter): - + # Check for property in filter and whether value is written out test, novalue = filter.testPropertyValue(self.mName.upper()) if test: self.generateValueXML(node, namespace, novalue) + # Write out the actual property, possibly skipping the value def generateValueXML(self, node, namespace, novalue): prop = XML.SubElement(node, xmldefs.makeTag(namespace, self.getName())) - + # Write all attributes if len(self.mAttributes): params = XML.SubElement(prop, xmldefs.makeTag(namespace, xmldefs.parameters)) @@ -491,21 +545,23 @@ # Write value if self.mValue and not novalue: self.mValue.writeXML(prop, namespace) - + + def _init_PyCalendarProperty(self): self.mName = "" self.mAttributes = {} self.mValue = None + def createValue(self, data): # Tidy first self.mValue = None # Get value type from property name - type = PyCalendarProperty.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarValue.VALUETYPE_TEXT) + type = PyCalendarProperty.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarValue.VALUETYPE_UNKNOWN) # Check whether custom value is set - if self.mAttributes.has_key(definitions.cICalAttribute_VALUE): + if definitions.cICalAttribute_VALUE in self.mAttributes: type = PyCalendarProperty.sValueTypeMap.get(self.getAttributeValue(definitions.cICalAttribute_VALUE), type) # Check for multivalued @@ -527,7 +583,7 @@ tzid = None if (self.hasAttribute(definitions.cICalAttribute_TZID)): tzid = self.getAttributeValue(definitions.cICalAttribute_TZID) - + if isinstance(self.mValue, PyCalendarDateTimeValue): self.mValue.getValue().setTimezoneID(tzid) elif isinstance(self.mValue, PyCalendarMultiValue): @@ -535,6 +591,7 @@ if isinstance(item, PyCalendarDateTimeValue): item.getValue().setTimezoneID(tzid) + def setValue(self, value): # Tidy first self.mValue = None @@ -543,7 +600,7 @@ type = PyCalendarProperty.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarValue.VALUETYPE_TEXT) # Check whether custom value is set - if self.mAttributes.has_key(definitions.cICalAttribute_VALUE): + if definitions.cICalAttribute_VALUE in self.mAttributes: type = PyCalendarProperty.sValueTypeMap.get(self.getAttributeValue(definitions.cICalAttribute_VALUE), type) # Check for multivalued @@ -561,7 +618,7 @@ tzid = None if (self.hasAttribute(definitions.cICalAttribute_TZID)): tzid = self.getAttributeValue(definitions.cICalAttribute_TZID) - + if isinstance(self.mValue, PyCalendarDateTimeValue): self.mValue.getValue().setTimezoneID(tzid) elif isinstance(self.mValue, PyCalendarMultiValue): @@ -569,22 +626,24 @@ if isinstance(item, PyCalendarDateTimeValue): item.getValue().setTimezoneID(tzid) + def setupValueAttribute(self): - if self.mAttributes.has_key(definitions.cICalAttribute_VALUE): + if definitions.cICalAttribute_VALUE in self.mAttributes: del self.mAttributes[definitions.cICalAttribute_VALUE] # Only if we have a value right now if self.mValue is None: return - # See if current type is default for this property + # See if current type is default for this property. If there is no mapping available, + # then always add VALUE if it is not TEXT. default_type = self.sDefaultValueTypeMap.get(self.mName.upper()) - if default_type is not None: - actual_type = self.mValue.getType() - if default_type != actual_type: - actual_value = self.sTypeValueMap.get(actual_type) - if actual_value is not None: - self.mAttributes.setdefault(definitions.cICalAttribute_VALUE, []).append(PyCalendarAttribute(name=definitions.cICalAttribute_VALUE, value=actual_value)) + actual_type = self.mValue.getType() + if default_type is None or default_type != actual_type: + actual_value = self.sTypeValueMap.get(actual_type) + if actual_value is not None and (default_type is not None or actual_type != PyCalendarValue.VALUETYPE_TEXT): + self.mAttributes.setdefault(definitions.cICalAttribute_VALUE, []).append(PyCalendarAttribute(name=definitions.cICalAttribute_VALUE, value=actual_value)) + # Creation def _init_attr_value_int(self, ival): @@ -598,12 +657,13 @@ def _init_attr_value_text(self, txt, value_type): # Value self.mValue = PyCalendarValue.createFromType(value_type) - if isinstance(self.mValue, PyCalendarPlainTextValue) or isinstance(self.mValue, PyCalendarDummyValue): + if isinstance(self.mValue, PyCalendarPlainTextValue) or isinstance(self.mValue, PyCalendarUnknownValue): self.mValue.setValue(txt) # Attributes self.setupValueAttribute() + def _init_attr_value_requeststatus(self, reqstatus): # Value self.mValue = PyCalendarRequestStatusValue(reqstatus) @@ -611,6 +671,7 @@ # Attributes self.setupValueAttribute() + def _init_attr_value_datetime(self, dt): # Value self.mValue = PyCalendarDateTimeValue(value=dt) @@ -620,11 +681,12 @@ # Look for timezone if not dt.isDateOnly() and dt.local(): - if self.mAttributes.has_key(definitions.cICalAttribute_TZID): + if definitions.cICalAttribute_TZID in self.mAttributes: del self.mAttributes[definitions.cICalAttribute_TZID] self.mAttributes.setdefault(definitions.cICalAttribute_TZID, []).append( PyCalendarAttribute(name=definitions.cICalAttribute_TZID, value=dt.getTimezoneID())) - + + def _init_attr_value_datetimelist(self, dtl): # Value date_only = (len(dtl) > 0) and dtl[0].isDateOnly() @@ -643,11 +705,12 @@ if ((len(dtl) > 0) and not dtl[0].isDateOnly() and dtl[0].local()): - if self.mAttributes.has_key(definitions.cICalAttribute_TZID): + if definitions.cICalAttribute_TZID in self.mAttributes: del self.mAttributes[definitions.cICalAttribute_TZID] self.mAttributes.setdefault(definitions.cICalAttribute_TZID, []).append( PyCalendarAttribute(name=definitions.cICalAttribute_TZID, value=dtl[0].getTimezoneID())) + def _init_attr_value_periodlist(self, periodlist): # Value self.mValue = PyCalendarMultiValue(PyCalendarValue.VALUETYPE_PERIOD) @@ -657,6 +720,7 @@ # Attributes self.setupValueAttribute() + def _init_attr_value_duration(self, du): # Value self.mValue = PyCalendarDurationValue(value=du) @@ -680,6 +744,7 @@ # Attributes self.setupValueAttribute() + def _init_attr_value_utcoffset(self, utcoffset): # Value self.mValue = PyCalendarUTCOffsetValue() diff -Nru pycalendar-2.0~svn188/src/pycalendar/recurrence.py pycalendar-2.0~svn13177/src/pycalendar/recurrence.py --- pycalendar-2.0~svn188/src/pycalendar/recurrence.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/recurrence.py 2014-04-07 16:00:00.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -34,11 +34,15 @@ return 1 else: return 0 - + + + def WeekDayNumSort_less_than(w1, w2): return (w1[0] < w2[0]) or (w1[0] == w2[0]) and (w1[1] < w2[1]) + + class PyCalendarRecurrence(ValueMixin): cFreqMap = { @@ -60,7 +64,7 @@ definitions.eRecurrence_MONTHLY: xmldefs.recur_freq_monthly, definitions.eRecurrence_YEARLY: xmldefs.recur_freq_yearly, } - + cRecurMap = { definitions.cICalValue_RECUR_FREQ : definitions.eRecurrence_FREQ, definitions.cICalValue_RECUR_UNTIL : definitions.eRecurrence_UNTIL, @@ -77,7 +81,7 @@ definitions.cICalValue_RECUR_BYSETPOS : definitions.eRecurrence_BYSETPOS, definitions.cICalValue_RECUR_WKST : definitions.eRecurrence_WKST, } - + cWeekdayMap = { definitions.cICalValue_RECUR_WEEKDAY_SU : definitions.eRecurrence_WEEKDAY_SU, definitions.cICalValue_RECUR_WEEKDAY_MO : definitions.eRecurrence_WEEKDAY_MO, @@ -87,14 +91,15 @@ definitions.cICalValue_RECUR_WEEKDAY_FR : definitions.eRecurrence_WEEKDAY_FR, definitions.cICalValue_RECUR_WEEKDAY_SA : definitions.eRecurrence_WEEKDAY_SA, } - + cWeekdayRecurMap = dict([(v, k) for k, v in cWeekdayMap.items()]) - + cUnknownIndex = -1 def __init__(self): self.init_PyCalendarRecurrence() + def duplicate(self): other = PyCalendarRecurrence() @@ -138,6 +143,7 @@ return other + def init_PyCalendarRecurrence(self): self.mFreq = definitions.eRecurrence_YEARLY @@ -165,6 +171,7 @@ self.mFullyCached = False self.mRecurrences = None + def __hash__(self): return hash(( self.mFreq, @@ -185,11 +192,17 @@ self.mWeekstart, )) - def __ne__(self, other): return not self.__eq__(other) + + def __ne__(self, other): + return not self.__eq__(other) + + def __eq__(self, other): - if not isinstance(other, PyCalendarRecurrence): return False + if not isinstance(other, PyCalendarRecurrence): + return False return self.equals(other) + def equals(self, comp): return (self.mFreq == comp.mFreq) \ and (self.mUseCount == comp.mUseCount) and (self.mCount == comp.mCount) \ @@ -206,6 +219,7 @@ and self.equalsNum(self.mBySetPos, comp.mBySetPos) \ and (self.mWeekstart == comp.mWeekstart) + def equalsNum(self, items1, items2): # Check sizes first if items1 is None: @@ -228,6 +242,7 @@ return False return True + def equalsDayNum(self, items1, items2): # Check sizes first if items1 is None: @@ -250,72 +265,100 @@ return False return True + + def _setAndclearIfChanged(self, attr, value): + if getattr(self, attr) != value: + self.clear() + setattr(self, attr, value) + + def getFreq(self): return self.mFreq + def setFreq(self, freq): - self.mFreq = freq + self._setAndclearIfChanged("mFreq", freq) + def getUseUntil(self): return self.mUseUntil + def setUseUntil(self, use_until): - self.mUseUntil = use_until + self._setAndclearIfChanged("mUseUntil", use_until) + def getUntil(self): return self.mUntil + def setUntil(self, until): - self.mUntil = until + self._setAndclearIfChanged("mUntil", until) + def getUseCount(self): return self.mUseCount + def setUseCount(self, use_count): - self.mUseCount = use_count + self._setAndclearIfChanged("mUseCount", use_count) + def getCount(self): return self.mCount + def setCount(self, count): - self.mCount = count + self._setAndclearIfChanged("mCount", count) + def getInterval(self): return self.mInterval + def setInterval(self, interval): - self.mInterval = interval + self._setAndclearIfChanged("mInterval", interval) + def getByMonth(self): return self.mByMonth + def setByMonth(self, by): - self.mByMonth = by[:] + self._setAndclearIfChanged("mByMonth", by[:]) + def getByMonthDay(self): return self.mByMonthDay + def setByMonthDay(self, by): - self.mByMonthDay = by[:] + self._setAndclearIfChanged("mByMonthDay", by[:]) + def getByYearDay(self): return self.mByYearDay + def setByYearDay(self, by): - self.mByYearDay = by[:] + self._setAndclearIfChanged("mByYearDay", by[:]) + def getByDay(self): return self.mByDay + def setByDay(self, by): - self.mByDay = by[:] + self._setAndclearIfChanged("mByDay", by[:]) def getBySetPos(self): return self.mBySetPos + def setBySetPos(self, by): - self.mBySetPos = by + self._setAndclearIfChanged("mBySetPos", by[:]) + def parse(self, data): self.init_PyCalendarRecurrence() @@ -323,7 +366,7 @@ # Tokenise using '' tokens = data.split(";") tokens.reverse() - + if len(tokens) == 0: raise ValueError("PyCalendarRecurrence: Invalid recurrence rule value") @@ -381,7 +424,7 @@ # Must NOT be less than one if self.mInterval < 1: raise ValueError("PyCalendarRecurrence: Invalid INTERVAL value") - + elif index == definitions.eRecurrence_BYSECOND: if self.mBySeconds is not None: raise ValueError("PyCalendarRecurrence: Only one BYSECOND allowed") @@ -398,52 +441,53 @@ if self.mByHours is not None: raise ValueError("PyCalendarRecurrence: Only one BYHOUR allowed") self.mByHours = [] - self.parseList(tvalue, self.mByHours, 0, 23, errmsg="PyCalendarRecurrence: Invalid BYHOUR value") + self.parseList(tvalue, self.mByHours, 0, 23, errmsg="PyCalendarRecurrence: Invalid BYHOUR value") elif index == definitions.eRecurrence_BYDAY: if self.mByDay is not None: raise ValueError("PyCalendarRecurrence: Only one BYDAY allowed") self.mByDay = [] - self.parseListDW(tvalue, self.mByDay, errmsg="PyCalendarRecurrence: Invalid BYDAY value") + self.parseListDW(tvalue, self.mByDay, errmsg="PyCalendarRecurrence: Invalid BYDAY value") elif index == definitions.eRecurrence_BYMONTHDAY: if self.mByMonthDay is not None: raise ValueError("PyCalendarRecurrence: Only one BYMONTHDAY allowed") self.mByMonthDay = [] - self.parseList(tvalue, self.mByMonthDay, 1, 31, True, errmsg="PyCalendarRecurrence: Invalid BYMONTHDAY value") + self.parseList(tvalue, self.mByMonthDay, 1, 31, True, errmsg="PyCalendarRecurrence: Invalid BYMONTHDAY value") elif index == definitions.eRecurrence_BYYEARDAY: if self.mByYearDay is not None: raise ValueError("PyCalendarRecurrence: Only one BYYEARDAY allowed") self.mByYearDay = [] - self.parseList(tvalue, self.mByYearDay, 1, 366, True, errmsg="PyCalendarRecurrence: Invalid BYYEARDAY value") + self.parseList(tvalue, self.mByYearDay, 1, 366, True, errmsg="PyCalendarRecurrence: Invalid BYYEARDAY value") elif index == definitions.eRecurrence_BYWEEKNO: if self.mByWeekNo is not None: raise ValueError("PyCalendarRecurrence: Only one BYWEEKNO allowed") self.mByWeekNo = [] - self.parseList(tvalue, self.mByWeekNo, 1, 53, True, errmsg="PyCalendarRecurrence: Invalid BYWEEKNO value") + self.parseList(tvalue, self.mByWeekNo, 1, 53, True, errmsg="PyCalendarRecurrence: Invalid BYWEEKNO value") elif index == definitions.eRecurrence_BYMONTH: if self.mByMonth is not None: raise ValueError("PyCalendarRecurrence: Only one BYMONTH allowed") self.mByMonth = [] - self.parseList(tvalue, self.mByMonth, 1, 12, errmsg="PyCalendarRecurrence: Invalid BYMONTH value") + self.parseList(tvalue, self.mByMonth, 1, 12, errmsg="PyCalendarRecurrence: Invalid BYMONTH value") elif index == definitions.eRecurrence_BYSETPOS: if self.mBySetPos is not None: raise ValueError("PyCalendarRecurrence: Only one BYSETPOS allowed") self.mBySetPos = [] - self.parseList(tvalue, self.mBySetPos, allowNegative=True, errmsg="PyCalendarRecurrence: Invalid BYSETPOS value") + self.parseList(tvalue, self.mBySetPos, allowNegative=True, errmsg="PyCalendarRecurrence: Invalid BYSETPOS value") elif index == definitions.eRecurrence_WKST: index = PyCalendarRecurrence.cWeekdayMap.get(tvalue, PyCalendarRecurrence.cUnknownIndex) if (index == PyCalendarRecurrence.cUnknownIndex): raise ValueError("PyCalendarRecurrence: Invalid WKST value") - self.mWeekstart = index + self.mWeekstart = index + def parseList(self, txt, list, min=None, max=None, allowNegative=False, errmsg=""): - + if "," in txt: tokens = txt.split(",") else: @@ -460,6 +504,7 @@ raise ValueError(errmsg) list.append(value) + def parseListDW(self, txt, list, errmsg=""): if "," in txt: @@ -474,10 +519,10 @@ offset = 0 while (offset < len(token)) and token[offset] in "+-1234567890": offset += 1 - + num = int(token[0:offset]) token = token[offset:] - + anum = abs(num) if anum < 1: raise ValueError(errmsg) @@ -491,31 +536,32 @@ wday = index list.append((num, wday)) - + + def generate(self, os): try: os.write(definitions.cICalValue_RECUR_FREQ) os.write("=") - if self.mFreq == definitions.eRecurrence_SECONDLY: + if self.mFreq == definitions.eRecurrence_SECONDLY: os.write(definitions.cICalValue_RECUR_SECONDLY) - elif self.mFreq == definitions.eRecurrence_MINUTELY: + elif self.mFreq == definitions.eRecurrence_MINUTELY: os.write(definitions.cICalValue_RECUR_MINUTELY) - elif self.mFreq == definitions.eRecurrence_HOURLY: + elif self.mFreq == definitions.eRecurrence_HOURLY: os.write(definitions.cICalValue_RECUR_HOURLY) - elif self.mFreq == definitions.eRecurrence_DAILY: + elif self.mFreq == definitions.eRecurrence_DAILY: os.write(definitions.cICalValue_RECUR_DAILY) - elif self.mFreq == definitions.eRecurrence_WEEKLY: + elif self.mFreq == definitions.eRecurrence_WEEKLY: os.write(definitions.cICalValue_RECUR_WEEKLY) - elif self.mFreq == definitions.eRecurrence_MONTHLY: + elif self.mFreq == definitions.eRecurrence_MONTHLY: os.write(definitions.cICalValue_RECUR_MONTHLY) - elif self.mFreq == definitions.eRecurrence_YEARLY: + elif self.mFreq == definitions.eRecurrence_YEARLY: os.write(definitions.cICalValue_RECUR_YEARLY) if self.mUseCount: @@ -528,14 +574,12 @@ os.write(definitions.cICalValue_RECUR_UNTIL) os.write("=") self.mUntil.generate(os) - if self.mInterval > 1: os.write(";") os.write(definitions.cICalValue_RECUR_INTERVAL) os.write("=") os.write(str(self.mInterval)) - self.generateList(os, definitions.cICalValue_RECUR_BYSECOND, self.mBySeconds) self.generateList(os, definitions.cICalValue_RECUR_BYMINUTE, self.mByMinutes) @@ -560,19 +604,19 @@ elif iter[1] == definitions.eRecurrence_WEEKDAY_MO: os.write(definitions.cICalValue_RECUR_WEEKDAY_MO) - elif iter[1] == definitions.eRecurrence_WEEKDAY_TU: + elif iter[1] == definitions.eRecurrence_WEEKDAY_TU: os.write(definitions.cICalValue_RECUR_WEEKDAY_TU) - elif iter[1] == definitions.eRecurrence_WEEKDAY_WE: + elif iter[1] == definitions.eRecurrence_WEEKDAY_WE: os.write(definitions.cICalValue_RECUR_WEEKDAY_WE) - elif iter[1] == definitions.eRecurrence_WEEKDAY_TH: + elif iter[1] == definitions.eRecurrence_WEEKDAY_TH: os.write(definitions.cICalValue_RECUR_WEEKDAY_TH) - elif iter[1] == definitions.eRecurrence_WEEKDAY_FR: + elif iter[1] == definitions.eRecurrence_WEEKDAY_FR: os.write(definitions.cICalValue_RECUR_WEEKDAY_FR) - elif iter[1] == definitions.eRecurrence_WEEKDAY_SA: + elif iter[1] == definitions.eRecurrence_WEEKDAY_SA: os.write(definitions.cICalValue_RECUR_WEEKDAY_SA) self.generateList(os, definitions.cICalValue_RECUR_BYMONTHDAY, self.mByMonthDay) @@ -587,7 +631,7 @@ os.write(definitions.cICalValue_RECUR_WKST) os.write("=") - if self.mWeekstart == definitions.eRecurrence_WEEKDAY_SU: + if self.mWeekstart == definitions.eRecurrence_WEEKDAY_SU: os.write(definitions.cICalValue_RECUR_WEEKDAY_SU) elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_MO: @@ -611,6 +655,7 @@ except: pass + def generateList(self, os, title, items): if (items is not None) and (len(items) != 0): @@ -624,6 +669,7 @@ comma = True os.write(str(e)) + def writeXML(self, node, namespace): recur = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.value_recur)) @@ -645,7 +691,7 @@ self.writeXMLList(recur, namespace, xmldefs.recur_bysecond, self.mBySeconds) self.writeXMLList(recur, namespace, xmldefs.recur_byminute, self.mByMinutes) self.writeXMLList(recur, namespace, xmldefs.recur_byhour, self.mByHours) - + if self.mByDay is not None and len(self.mByDay) != 0: for iter in self.mByDay: byday = XML.SubElement(recur, xmldefs.makeTag(namespace, xmldefs.recur_byday)) @@ -666,13 +712,14 @@ wkst = XML.SubElement(recur, xmldefs.makeTag(namespace, xmldefs.recur_wkst)) wkst.text = self.cWeekdayRecurMap.get(self.mWeekstart, definitions.cICalValue_RECUR_WEEKDAY_MO) + def writeXMLList(self, node, namespace, name, items): if items is not None and len(items) != 0: for item in items: child = XML.SubElement(node, xmldefs.makeTag(namespace, name)) child.text = str(item) - - + + def hasBy(self): return (self.mBySeconds is not None) and (len(self.mBySeconds) != 0) \ or (self.mByMinutes is not None) and (len(self.mByMinutes) != 0) \ @@ -693,7 +740,7 @@ def isAdvancedRule(self): # One that has BYMONTH, # BYMONTHDAY (with no negative value), - # BYDAY (with multiple unumbered, or numbered with all the same number + # BYDAY (with multiple unnumbered, or numbered with all the same number # (1..4, -2, -1) # BYSETPOS with +1, or -1 only # no others @@ -726,7 +773,6 @@ # Check number range if (number > 4) or (number < -2): return False - # If current differs from last, then we have an error elif number != iter[0]: @@ -738,7 +784,6 @@ return False if (len(self.mBySetPos) == 1) and (self.mBySetPos[0] != -1) and (self.mBySetPos[0] != 1): return False - # If we get here it must be OK return True @@ -752,12 +797,16 @@ result = sout.getvalue() except: result = "" - + return result def expand(self, start, range, items, float_offset=0): + # Have to normalize this to be very sure we are starting with a valid date, as otherwise + # we could end up looping forever when doing recurrence. + start.normalise() + # Must have recurrence list at this point if self.mRecurrences is None: self.mRecurrences = [] @@ -767,7 +816,7 @@ self.mCached = False self.mFullyCached = False self.mRecurrences = [] - + # Is the current cache complete or does it extend past the requested # range end if not self.mCached or not self.mFullyCached \ @@ -789,7 +838,7 @@ self.mCached = True self.mCacheStart = start self.mCacheUpto = range.getEnd() - + # Just return the cached items in the requested range limited = not self.mFullyCached for iter in self.mRecurrences: @@ -798,7 +847,8 @@ else: limited = True return limited - + + def simpleExpand(self, start, range, items, float_offset): start_iter = start.duplicate() ctr = 0 @@ -818,7 +868,9 @@ items.append(start_iter.duplicate()) # Get next item - start_iter.recur(self.mFreq, self.mInterval) + start_iter.recur(self.mFreq, self.mInterval, allow_invalid=True) + while start_iter.invalid(): + start_iter.recur(self.mFreq, self.mInterval, allow_invalid=True) # Check limits if self.mUseCount: @@ -832,6 +884,7 @@ if start_iter > float_until: return True + def complexExpand(self, start, range, items, float_offset): start_iter = start.duplicate() ctr = 0 @@ -843,7 +896,6 @@ float_until.offsetSeconds(float_offset) # Always add the initial instance DTSTART - items.append(start.duplicate()) if self.mUseCount: # Bump counter and exit if over ctr += 1 @@ -874,11 +926,14 @@ self.generateMonthlySet(start_iter, set_items) elif self.mFreq == definitions.eRecurrence_YEARLY: - self.generateYearlySet(start_iter, set_items) + self.generateYearlySet(start_iter, set_items) + + # Ignore if it is invalid + set_items = filter(lambda x: not x.invalid(), set_items) # Always sort the set as BYxxx rules may not be sorted #set_items.sort(cmp=PyCalendarDateTime.sort) - set_items.sort(key=lambda x:x.getPosixTime()) + set_items.sort(key=lambda x: x.getPosixTime()) # Process each one in the generated set for iter in set_items: @@ -921,7 +976,8 @@ return False # Get next item - start_iter.recur(self.mFreq, self.mInterval) + start_iter.recur(self.mFreq, self.mInterval, allow_invalid=True) + def clear(self): self.mCached = False @@ -938,7 +994,7 @@ # Exclude dates on or after the chosen one def excludeFutureRecurrence(self, exclude): - # Expand the rule upto the exclude date + # Expand the rule up to the exclude date items = [] period = PyCalendarPeriod() period.init(self.mCacheStart, exclude) @@ -957,10 +1013,11 @@ # The last one is just less than the exclude date self.mUseCount = True self.mCount = len(items) - + # Now clear out the cached set after making changes self.clear() + def generateYearlySet(self, start, items): # All possible BYxxx are valid, though some combinations are not @@ -969,19 +1026,15 @@ if (self.mByMonth is not None) and (len(self.mByMonth) != 0): items[:] = self.byMonthExpand(items) - if (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0): items[:] = self.byWeekNoExpand(items) - if (self.mByYearDay is not None) and (len(self.mByYearDay) != 0): items[:] = self.byYearDayExpand(items) - if (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0): items[:] = self.byMonthDayExpand(items) - if (self.mByDay is not None) and (len(self.mByDay) != 0): # BYDAY is complicated: @@ -1002,16 +1055,17 @@ if (self.mByHours is not None) and (len(self.mByHours) != 0): items[:] = self.byHourExpand(items) - + if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0): items[:] = self.byMinuteExpand(items) - + if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0): items[:] = self.bySecondExpand(items) - + if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0): items[:] = self.bySetPosLimit(items) - + + def generateMonthlySet(self, start, items): # Cannot have BYYEARDAY and BYWEEKNO @@ -1023,14 +1077,14 @@ items[:] = self.byMonthLimit(items) if (len(items) == 0): return - + # No BYWEEKNO # No BYYEARDAY if (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0): items[:] = self.byMonthDayExpand(items) - + if (self.mByDay is not None) and (len(self.mByDay) != 0): # BYDAY is complicated: # if BYDAY is included with BYYEARDAY or BYMONTHDAY then it @@ -1043,20 +1097,21 @@ items[:] = self.byDayLimit(items) else: items[:] = self.byDayExpandMonthly(items) - + if ((self.mByHours is not None) and (len(self.mByHours) != 0)): items[:] = self.byHourExpand(items) - + if ((self.mByMinutes is not None) and (len(self.mByMinutes) != 0)): items[:] = self.byMinuteExpand(items) - + if ((self.mBySeconds is not None) and (len(self.mBySeconds) != 0)): items[:] = self.bySecondExpand(items) - + if ((self.mBySetPos is not None) and (len(self.mBySetPos) != 0)): items[:] = self.bySetPosLimit(items) - - def generateWeeklySet(self, start, items): + + + def generateWeeklySet(self, start, items): # Cannot have BYYEARDAY and BYMONTHDAY # Start with initial date-time @@ -1067,32 +1122,33 @@ items[:] = self.byMonthLimit(items) if (len(items) == 0): return - + if (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0): items[:] = self.byWeekNoLimit(items) if (len(items) == 0): return - + # No BYYEARDAY # No BYMONTHDAY if (self.mByDay is not None) and (len(self.mByDay) != 0): items[:] = self.byDayExpandWeekly(items) - + if (self.mByHours is not None) and (len(self.mByHours) != 0): items[:] = self.byHourExpand(items) - + if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0): items[:] = self.byMinuteExpand(items) - + if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0): items[:] = self.bySecondExpand(items) - + if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0): items[:] = self.bySetPosLimit(items) - - def generateDailySet(self, start, items): + + + def generateDailySet(self, start, items): # Cannot have BYYEARDAY # Start with initial date-time @@ -1103,13 +1159,11 @@ items[:] = self.byMonthLimit(items) if (len(items) == 0): return - if (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0): items[:] = self.byWeekNoLimit(items) if (len(items) == 0): return - # No BYYEARDAY @@ -1117,29 +1171,25 @@ items[:] = self.byMonthDayLimit(items) if (len(items) == 0): return - if (self.mByDay is not None) and (len(self.mByDay) != 0): items[:] = self.byDayLimit(items) if (len(items) == 0): return - if (self.mByHours is not None) and (len(self.mByHours) != 0): items[:] = self.byHourExpand(items) - if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0): items[:] = self.byMinuteExpand(items) - if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0): items[:] = self.bySecondExpand(items) - if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0): items[:] = self.bySetPosLimit(items) - + + def generateHourlySet(self, start, items): # Cannot have BYYEARDAY @@ -1151,13 +1201,11 @@ items[:] = self.byMonthLimit(items) if (len(items) == 0): return - if (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0): items[:] = self.byWeekNoLimit(items) if (len(items) == 0): return - # No BYYEARDAY @@ -1165,30 +1213,27 @@ items[:] = self.byMonthDayLimit(items) if (len(items) == 0): return - if (self.mByDay is not None) and (len(self.mByDay) != 0): items[:] = self.byDayLimit(items) if (len(items) == 0): return - if (self.mByHours is not None) and (len(self.mByHours) != 0): items[:] = self.byHourLimit(items) if (len(items) == 0): return - + if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0): items[:] = self.byMinuteExpand(items) - if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0): items[:] = self.bySecondExpand(items) - if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0): items[:] = self.bySetPosLimit(items) + def generateMinutelySet(self, start, items): # Cannot have BYYEARDAY @@ -1200,41 +1245,42 @@ items[:] = self.byMonthLimit(items) if (len(items) == 0): return - + if (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0): items[:] = self.byWeekNoLimit(items) if (len(items) == 0): return - + # No BYYEARDAY if (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0): items[:] = self.byMonthDayLimit(items) if (len(items) == 0): return - + if (self.mByDay is not None) and (len(self.mByDay) != 0): items[:] = self.byDayLimit(items) if (len(items) == 0): return - + if (self.mByHours is not None) and (len(self.mByHours) != 0): items[:] = self.byHourLimit(items) if (len(items) == 0): return - + if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0): items[:] = self.byMinuteLimit(items) if (len(items) == 0): return - + if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0): items[:] = self.bySecondExpand(items) - + if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0): items[:] = self.bySetPosLimit(items) - - def generateSecondlySet(self, start, items): + + + def generateSecondlySet(self, start, items): # Cannot have BYYEARDAY # Start with initial date-time @@ -1245,42 +1291,43 @@ items[:] = self.byMonthLimit(items) if (len(items) == 0): return - + if (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0): items[:] = self.byWeekNoLimit(items) if (len(items) == 0): return - + # No BYYEARDAY if (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0): items[:] = self.byMonthDayLimit(items) if (len(items) == 0): return - + if (self.mByDay is not None) and (len(self.mByDay) != 0): items[:] = self.byDayLimit(items) if (len(items) == 0): return - + if (self.mByHours is not None) and (len(self.mByHours) != 0): items[:] = self.byHourLimit(items) if (len(items) == 0): return - + if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0): items[:] = self.byMinuteLimit(items) if (len(items) == 0): return - + if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0): items[:] = self.bySecondLimit(items) if (len(items) == 0): return - + if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0): items[:] = self.bySetPosLimit(items) + def byMonthExpand(self, dates): # Loop over all input items output = [] @@ -1294,6 +1341,7 @@ return output + def byWeekNoExpand(self, dates): # Loop over all input items output = [] @@ -1304,9 +1352,10 @@ temp = iter1.duplicate() temp.setWeekNo(iter2) output.append(temp) - + return output + def byYearDayExpand(self, dates): # Loop over all input items output = [] @@ -1315,11 +1364,12 @@ # and insert into output for iter2 in self.mByYearDay: temp = iter1.duplicate() - temp.setYearDay(iter2) + temp.setYearDay(iter2, allow_invalid=True) output.append(temp) return output + def byMonthDayExpand(self, dates): # Loop over all input items output = [] @@ -1328,11 +1378,12 @@ # and insert into output for iter2 in self.mByMonthDay: temp = iter1.duplicate() - temp.setMonthDay(iter2) + temp.setMonthDay(iter2, allow_invalid=True) output.append(temp) return output + def byDayExpandYearly(self, dates): # Loop over all input items output = [] @@ -1355,6 +1406,7 @@ return output + def byDayExpandMonthly(self, dates): # Loop over all input items output = [] @@ -1365,18 +1417,19 @@ # Numeric value means specific instance if iter2[0] != 0: temp = iter1.duplicate() - temp.setDayOfWeekInMonth(iter2[0], iter2[1]) + temp.setDayOfWeekInMonth(iter2[0], iter2[1], allow_invalid=True) output.append(temp) else: # Every matching day in the month for i in range(1, 7): temp = iter1.duplicate() - temp.setDayOfWeekInMonth(i, iter2[1]) + temp.setDayOfWeekInMonth(i, iter2[1], allow_invalid=True) if temp.getMonth() == iter1.getMonth(): output.append(temp) return output + def byDayExpandWeekly(self, dates): # Must take into account the WKST value @@ -1408,6 +1461,7 @@ return output + def byHourExpand(self, dates): # Loop over all input items output = [] @@ -1421,6 +1475,7 @@ return output + def byMinuteExpand(self, dates): # Loop over all input items output = [] @@ -1434,6 +1489,7 @@ return output + def bySecondExpand(self, dates): # Loop over all input items output = [] @@ -1447,6 +1503,7 @@ return output + def byMonthLimit(self, dates): # Loop over all input items output = [] @@ -1457,12 +1514,13 @@ keep = (iter1.getMonth() == iter2) if keep: break - + if keep: output.append(iter1) return output + def byWeekNoLimit(self, dates): # Loop over all input items output = [] @@ -1473,12 +1531,13 @@ keep = iter1.isWeekNo(iter2) if keep: break - + if keep: output.append(iter1) - + return output + def byMonthDayLimit(self, dates): # Loop over all input items output = [] @@ -1490,12 +1549,13 @@ keep = iter1.isMonthDay(iter2) if keep: break - + if keep: output.append(iter1) - + return output + def byDayLimit(self, dates): # Loop over all input items output = [] @@ -1506,12 +1566,13 @@ keep = iter1.isDayOfWeekInMonth(iter2[0], iter2[1]) if keep: break - + if keep: output.append(iter1) return output + def byHourLimit(self, dates): # Loop over all input items output = [] @@ -1522,12 +1583,13 @@ keep = (iter1.getHours() == iter2) if keep: break - + if keep: output.append(iter1) - + return output + def byMinuteLimit(self, dates): # Loop over all input items output = [] @@ -1538,12 +1600,13 @@ keep = (iter1.getMinutes() == iter2) if keep: break - + if keep: output.append(iter1) return output + def bySecondLimit(self, dates): # Loop over all input items output = [] @@ -1554,16 +1617,17 @@ keep = (iter1.getSeconds() == iter2) if keep: break - + if keep: output.append(iter1) - + return output + def bySetPosLimit(self, dates): # The input dates MUST be sorted in order for this to work properly #dates.sort(cmp=PyCalendarDateTime.sort) - dates.sort(key=lambda x:x.getPosixTime()) + dates.sort(key=lambda x: x.getPosixTime()) # Loop over each BYSETPOS and extract the relevant component from the # input array and add to the output @@ -1578,5 +1642,5 @@ # Negative values are offset from the end if -iter <= input_size: output.append(dates[input_size + iter]) - + return output diff -Nru pycalendar-2.0~svn188/src/pycalendar/recurrenceset.py pycalendar-2.0~svn13177/src/pycalendar/recurrenceset.py --- pycalendar-2.0~svn188/src/pycalendar/recurrenceset.py 2011-04-27 15:38:06.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/recurrenceset.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -26,6 +26,7 @@ self.mRperiods = [] self.mExperiods = [] + def duplicate(self): other = PyCalendarRecurrenceSet() other.mRrules = [i.duplicate() for i in self.mRrules] @@ -36,11 +37,13 @@ other.mExperiods = [i.duplicate() for i in self.mExperiods] return other + def hasRecurrence(self): return ((len(self.mRrules) != 0) or (len(self.mRdates) != 0) or (len(self.mRperiods) != 0) or (len(self.mExrules) != 0) or (len(self.mExdates) != 0) or (len(self.mExperiods) != 0)) + def equals(self, comp): # Look at RRULEs if not self.equalsRules(self.mRrules, comp.self.mRrules): @@ -65,6 +68,7 @@ # If we get here they match return True + def equalsRules(self, rules1, rules2): # Check sizes first if len(rules1) != len(rules2): @@ -93,6 +97,7 @@ return True + def equalsDates(self, dates1, dates2): # Check sizes first if len(dates1) != len(dates2): @@ -104,11 +109,12 @@ dt1 = dates1[:] dt2 = dates2[:] - dt1.sort(key=lambda x:x.getPosixTime()) - dt2.sort(key=lambda x:x.getPosixTime()) + dt1.sort(key=lambda x: x.getPosixTime()) + dt2.sort(key=lambda x: x.getPosixTime()) return dt1.equal(dt2) + def equalsPeriods(self, periods1, periods2): # Check sizes first if len(periods1) != len(periods2): @@ -125,42 +131,55 @@ return p1.equal(p2) + def addRule(self, rule): self.mRrules.append(rule) + def subtractRule(self, rule): self.mExrules.append(rule) + def addDT(self, dt): self.mRdates.append(dt) + def subtractDT(self, dt): self.mExdates.append(dt) + def addPeriod(self, p): self.mRperiods.append(p) + def subtractPeriod(self, p): self.mExperiods.append(p) + def getRules(self): return self.mRrules + def getExrules(self): return self.mExrules + def getDates(self): return self.mRdates + def getExdates(self): return self.mExdates + def getPeriods(self): return self.mRperiods + def getExperiods(self): return self.mExperiods + def expand(self, start, range, items, float_offset=0): # Need to return whether the limit was applied or not limited = False @@ -193,7 +212,7 @@ # Make sure the list is unique include = [x for x in set(include)] - include.sort(key=lambda x:x.getPosixTime()) + include.sort(key=lambda x: x.getPosixTime()) # Now create list of items to exclude exclude = [] @@ -212,13 +231,14 @@ # Make sure the list is unique exclude = [x for x in set(exclude)] - exclude.sort(key=lambda x:x.getPosixTime()) + exclude.sort(key=lambda x: x.getPosixTime()) # Add difference between to the two sets (include - exclude) to the # results items.extend(set_difference(include, exclude)) return limited + def changed(self): # RRULES for iter in self.mRrules: @@ -228,6 +248,7 @@ for iter in self.mExrules: iter.clear() + def excludeFutureRecurrence(self, exclude): # Adjust RRULES to end before start for iter in self.mRrules: @@ -239,6 +260,7 @@ if iter > exclude: self.mRperiods.remove(iter) + # UI operations def isSimpleUI(self): # Right now the Event dialog only handles a single RRULE (but we allow @@ -254,6 +276,7 @@ else: return True + def isAdvancedUI(self): # Right now the Event dialog only handles a single RRULE if ((len(self.mRrules) > 1) or (len(self.mExrules) > 0) @@ -266,12 +289,14 @@ else: return True + def getUIRecurrence(self): if len(self.mRrules) == 1: return self.mRrules[0] else: return None + def getUIDescription(self): # Check for anything if not self.hasRecurrence(): diff -Nru pycalendar-2.0~svn188/src/pycalendar/recurrencevalue.py pycalendar-2.0~svn13177/src/pycalendar/recurrencevalue.py --- pycalendar-2.0~svn188/src/pycalendar/recurrencevalue.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/recurrencevalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,30 +18,37 @@ from pycalendar.recurrence import PyCalendarRecurrence from pycalendar.value import PyCalendarValue -class PyCalendarRecurrenceValue( PyCalendarValue ): +class PyCalendarRecurrenceValue(PyCalendarValue): - def __init__( self, value = None ): + def __init__(self, value=None): self.mValue = value if value is not None else PyCalendarRecurrence() + def duplicate(self): return PyCalendarRecurrenceValue(self.mValue.duplicate()) - def getType( self ): + + def getType(self): return PyCalendarValue.VALUETYPE_RECUR - def parse( self, data ): - self.mValue.parse( data ) - def generate( self, os ): - self.mValue.generate( os ) + def parse(self, data): + self.mValue.parse(data) + + + def generate(self, os): + self.mValue.generate(os) + def writeXML(self, node, namespace): self.mValue.writeXML(node, namespace) - def getValue( self ): + + def getValue(self): return self.mValue - def setValue( self, value ): + + def setValue(self, value): self.mValue = value PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_RECUR, PyCalendarRecurrenceValue, xmldefs.value_recur) diff -Nru pycalendar-2.0~svn188/src/pycalendar/requeststatusvalue.py pycalendar-2.0~svn13177/src/pycalendar/requeststatusvalue.py --- pycalendar-2.0~svn188/src/pycalendar/requeststatusvalue.py 2011-08-25 17:50:54.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/requeststatusvalue.py 2013-04-03 15:19:39.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2013 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -21,65 +21,64 @@ from pycalendar.value import PyCalendarValue import xml.etree.cElementTree as XML -class PyCalendarRequestStatusValue( PyCalendarValue ): +class PyCalendarRequestStatusValue(PyCalendarValue): """ The value is a list of strings (either 2 or 3 items) """ - def __init__(self, value = None): + def __init__(self, value=None): self.mValue = value if value is not None else ["2.0", "Success"] + def __hash__(self): return hash(tuple(self.mValue)) + def duplicate(self): return PyCalendarRequestStatusValue(self.mValue[:]) + def getType(self): return PyCalendarValue.VALUETYPE_REQUEST_STATUS - def parse( self, data ): - - # Split fields based on ; - code, rest = data.split(";", 1) + + def parse(self, data): + + result = utils.parseTextList(data, always_list=True) + if len(result) == 1: + if ParserContext.INVALID_REQUEST_STATUS_VALUE != ParserContext.PARSER_RAISE: + if ";" in result[0]: + code, desc = result[0].split(";", 1) + else: + code = result[0] + desc = "" + rest = None + else: + raise ValueError + elif len(result) == 2: + code, desc = result + rest = None + elif len(result) == 3: + code, desc, rest = result + else: + if ParserContext.INVALID_REQUEST_STATUS_VALUE != ParserContext.PARSER_RAISE: + code, desc, rest = result[:3] + else: + raise ValueError if "\\" in code and ParserContext.INVALID_REQUEST_STATUS_VALUE in (ParserContext.PARSER_IGNORE, ParserContext.PARSER_FIX): code = code.replace("\\", "") elif ParserContext.INVALID_REQUEST_STATUS_VALUE == ParserContext.PARSER_RAISE: raise ValueError - - # The next two items are text with possible \; sequences so we have to punt on those - desc = "" - semicolon = rest.find(";") - while semicolon != -1: - if rest[semicolon-1] == "\\": - desc += rest[:semicolon+1] - rest = rest[semicolon+1:] - semicolon = rest.find(";") - else: - desc += rest[:semicolon] - rest = rest[semicolon+1:] - break - - if semicolon == -1: - desc += rest - rest = "" - + # Decoding required - self.mValue = [code, utils.decodeTextValue( desc), utils.decodeTextValue( rest ) if rest else None] - + self.mValue = [code, desc, rest, ] if rest else [code, desc, ] + + # os - StringIO object - def generate( self, os ): - try: - # Encoding required - utils.writeTextValue( os, self.mValue[0] ) - os.write(";") - utils.writeTextValue( os, self.mValue[1] ) - if len(self.mValue) == 3 and self.mValue[2]: - os.write(";") - utils.writeTextValue( os, self.mValue[2] ) - except: - pass + def generate(self, os): + utils.generateTextList(os, self.mValue if len(self.mValue) < 3 or self.mValue[2] else self.mValue[:2]) + def writeXML(self, node, namespace): code = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.req_status_code)) @@ -92,11 +91,12 @@ data = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.req_status_data)) data.text = self.mValue[1] + def getValue(self): return self.mValue - def setValue( self, value ): + + def setValue(self, value): self.mValue = value PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_REQUEST_STATUS, PyCalendarRequestStatusValue, None) - \ No newline at end of file diff -Nru pycalendar-2.0~svn188/src/pycalendar/stringutils.py pycalendar-2.0~svn13177/src/pycalendar/stringutils.py --- pycalendar-2.0~svn188/src/pycalendar/stringutils.py 2011-03-09 21:15:39.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/stringutils.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,7 +17,7 @@ from hashlib import md5 def strduptokenstr(txt, tokens): - + result = None start = 0 @@ -32,7 +32,7 @@ # Handle quoted string if txt[start] == '\"': - + maxlen = len(txt) # Punt leading quote start += 1 @@ -58,14 +58,16 @@ for relend, s in enumerate(txt[start:]): if s in tokens: if relend: - result = txt[start:start+relend] + result = txt[start:start + relend] else: result = "" - return result, txt[start+relend:] + return result, txt[start + relend:] return txt[start:], "" + + def strtoul(s, offset=0): - + max = len(s) startoffset = offset while offset < max: @@ -82,6 +84,8 @@ else: return int(s[startoffset:]), offset + + def strindexfind(s, ss, default_index): if s and ss: i = 0 @@ -93,6 +97,8 @@ return default_index + + def strnindexfind(s, ss, default_index): if s and ss: i = 0 @@ -104,6 +110,8 @@ return default_index + + def compareStringsSafe(s1, s2): if s1 is None and s2 is None: return True @@ -112,5 +120,7 @@ else: return s1 == s2 + + def md5digest(txt): return md5.new(txt).hexdigest() diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/__init__.py pycalendar-2.0~svn13177/src/pycalendar/tests/__init__.py --- pycalendar-2.0~svn188/src/pycalendar/tests/__init__.py 2011-03-09 21:15:39.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/__init__.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,16 +1,15 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## - diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_adr.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_adr.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_adr.py 2011-04-27 15:38:06.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_adr.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,7 +18,7 @@ import unittest class TestAdrValue(unittest.TestCase): - + def testInit(self): data = ( ( @@ -33,17 +33,17 @@ for args, result in data: a = Adr(*args) - + self.assertEqual( a.getValue(), args, ) - + self.assertEqual( a.getText(), result, ) - + self.assertEqual( a.duplicate().getText(), result, diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_adrvalue.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_adrvalue.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_adrvalue.py 2011-04-27 15:38:06.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_adrvalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,9 +19,9 @@ import unittest class TestAdrValue(unittest.TestCase): - + def testParseValue(self): - + items = ( ("", ";;;;;;"), (";", ";;;;;;"), @@ -34,15 +34,16 @@ (";;123 Main\, Street,The Cards;Any Town;CA;91921-1234", ";;123 Main\, Street,The Cards;Any Town;CA;91921-1234;"), (";;123 Main\, Street,The\, Cards;Any Town;CA;91921-1234", ";;123 Main\, Street,The\, Cards;Any Town;CA;91921-1234;"), ) - + for item, result in items: req = AdrValue() req.parse(item) test = req.getText() self.assertEqual(test, result, "Failed to parse and re-generate '%s'" % (item,)) + def testParseProperty(self): - + items = ( ("ADR:", "ADR:;;;;;;"), ("ADR:;", "ADR:;;;;;;"), @@ -53,7 +54,7 @@ ("ADR:;EXT", "ADR:;EXT;;;;;"), ("ADR;VALUE=TEXT:;;123 Main Street;Any Town;CA;91921-1234", "ADR:;;123 Main Street;Any Town;CA;91921-1234;"), ) - + for item, result in items: prop = Property() prop.parse(item) diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_calendar.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_calendar.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_calendar.py 2011-06-04 17:16:44.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_calendar.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -25,7 +25,7 @@ import unittest class TestCalendar(unittest.TestCase): - + data = ( """BEGIN:VCALENDAR VERSION:2.0 @@ -242,6 +242,41 @@ 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 +ATTACH:http://example.com/test.jpg +DTSTAMP:20020101T000000Z +SUMMARY:New Year's Day +X-APPLE-STRUCTURED-LOCATION:geo:123.123,123.123 +X-Test:Some\, text. +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 +ATTACH:http://example.com/test.jpg +DTSTAMP:20020101T000000Z +SUMMARY:New Year's Day +X-APPLE-STRUCTURED-LOCATION;VALUE=URI:geo:123.123,123.123 +X-Test:Some\, text. +END:VEVENT +END:VCALENDAR +""".replace("\n", "\r\n"), + ) data2 = ( ( @@ -277,18 +312,20 @@ ), ) + def testRoundtrip(self): + def _doRoundtrip(caldata, resultdata=None): test1 = resultdata if resultdata is not None else caldata cal = PyCalendar() cal.parse(StringIO.StringIO(caldata)) - + s = StringIO.StringIO() cal.generate(s) test2 = s.getvalue() - + self.assertEqual( test1, test2, @@ -301,13 +338,15 @@ for item1, item2 in self.data2: _doRoundtrip(item1, item2) + def testRoundtripDuplicate(self): + def _doDuplicateRoundtrip(caldata): cal = PyCalendar() cal.parse(StringIO.StringIO(caldata)) cal = cal.duplicate() - + s = StringIO.StringIO() cal.generate(s) self.assertEqual(caldata, s.getvalue()) @@ -315,8 +354,10 @@ for item in self.data: _doDuplicateRoundtrip(item) + def testEquality(self): + def _doEquality(caldata): cal1 = PyCalendar() cal1.parse(StringIO.StringIO(caldata)) @@ -326,6 +367,7 @@ self.assertEqual(cal1, cal2, "%s\n\n%s" % (cal1, cal2,)) + def _doNonEquality(caldata): cal1 = PyCalendar() cal1.parse(StringIO.StringIO(caldata)) @@ -340,8 +382,9 @@ _doEquality(item) _doNonEquality(item) + def testParseComponent(self): - + data1 = """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN @@ -382,7 +425,6 @@ END:VCALENDAR """.replace("\n", "\r\n") - result = """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN @@ -416,14 +458,14 @@ END:VCALENDAR """.replace("\n", "\r\n") - cal = PyCalendar() cal.parse(StringIO.StringIO(data1)) cal.parseComponent(StringIO.StringIO(data2)) self.assertEqual(str(cal), result) + def testParseFail(self): - + data = ( """BEGIN:VCALENDAR VERSION:2.0 @@ -516,8 +558,9 @@ for item in data: self.assertRaises(PyCalendarInvalidData, PyCalendar.parseText, item) + def testParseBlank(self): - + data = ( """ BEGIN:VCALENDAR @@ -615,6 +658,7 @@ ParserContext.BLANK_LINES_IN_DATA = save + def testGetVEvents(self): data = ( @@ -796,7 +840,7 @@ ), ) - for title, caldata, result in data: + for title, caldata, result in data: calendar = PyCalendar.parseText(caldata) instances = [] calendar.getVEvents( diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_componentrecur.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_componentrecur.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_componentrecur.py 2011-04-04 17:36:40.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_componentrecur.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,11 @@ -## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,7 +18,7 @@ import unittest class TestCalendar(unittest.TestCase): - + def testDuplicateWithRecurrenceChange(self): data = ( diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_datetime.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_datetime.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_datetime.py 2011-10-11 17:13:17.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_datetime.py 2014-02-19 19:56:40.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,14 +14,24 @@ # limitations under the License. ## +from pycalendar.calendar import PyCalendar from pycalendar.datetime import PyCalendarDateTime +from pycalendar.parser import ParserContext from pycalendar.timezone import PyCalendarTimezone import unittest class TestDateTime(unittest.TestCase): - + + def _patch(self, obj, attr, value): + oldvalue = getattr(obj, attr) + setattr(obj, attr, value) + def _restore(): + setattr(obj, attr, oldvalue) + self.addCleanup(_restore) + + def testDuplicateASUTC(self): - + items = ( ( PyCalendarDateTime(2011, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), @@ -36,25 +46,27 @@ PyCalendarDateTime(2011, 1, 1), ) ) - + for item, result in items: dup = item.duplicateAsUTC() self.assertEqual(str(dup), str(result), "Failed to convert '%s'" % (item,)) + def testDuplicateInSet(self): - + s = set( ( PyCalendarDateTime(2011, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), PyCalendarDateTime(2011, 1, 2, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), ) ) - + self.assertTrue(PyCalendarDateTime(2011, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) in s) self.assertFalse(PyCalendarDateTime(2011, 1, 3, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) in s) + def testRoundtrip(self): - + data1 = ( "20110102", "20110103T121212", @@ -63,7 +75,7 @@ "00010103T121212", "00010103T121212Z", ) - + data2 = ( ("20110102", "20110102"), ("2011-01-02", "20110102"), @@ -84,13 +96,16 @@ for item in data1: dt = PyCalendarDateTime.parseText(item, False) self.assertEqual(dt.getText(), item, "Failed on: %s" % (item,)) - + for item, result in data2: dt = PyCalendarDateTime.parseText(item, True) self.assertEqual(dt.getText(), result, "Failed on: %s" % (item,)) - + + def testBadParse(self): - + + self._patch(ParserContext, "INVALID_DATETIME_LEADINGSPACE", ParserContext.PARSER_RAISE) + data1 = ( "2011", "201101023", @@ -119,7 +134,268 @@ for item in data1: self.assertRaises(ValueError, PyCalendarDateTime.parseText, item, False) - + for item in data2: self.assertRaises(ValueError, PyCalendarDateTime.parseText, item, False) - + + + def testBadParseFixed(self): + + self._patch(ParserContext, "INVALID_DATETIME_LEADINGSPACE", ParserContext.PARSER_ALLOW) + + data = ( + (" 10102", "00010102"), + ("2001 102", "20010102"), + ("200101 2", "20010102"), + (" 10102T010101", "00010102T010101"), + ("2001 102T010101", "20010102T010101"), + ("200101 2T010101", "20010102T010101"), + ) + + for item, result in data: + dt = PyCalendarDateTime.parseText(item) + self.assertEqual(str(dt), result) + + + def testCachePreserveOnAdjustment(self): + + # UTC first + dt = PyCalendarDateTime(2012, 6, 7, 12, 0, 0, PyCalendarTimezone(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") + + PyCalendar.parseText(tzdata) + + dt = PyCalendarDateTime(2012, 6, 7, 12, 0, 0, PyCalendarTimezone(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 testSetWeekNo(self): + + dt = PyCalendarDateTime(2013, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) + self.assertEqual(dt.getWeekNo(), 1) + dt.setWeekNo(1) + self.assertEqual(dt, PyCalendarDateTime(2013, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))) + self.assertEqual(dt.getWeekNo(), 1) + + dt = PyCalendarDateTime(2013, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) + self.assertEqual(dt.getWeekNo(), 1) + dt.setWeekNo(2) + self.assertEqual(dt, PyCalendarDateTime(2013, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))) + self.assertEqual(dt.getWeekNo(), 2) + + dt = PyCalendarDateTime(2013, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) + self.assertEqual(dt.getWeekNo(), 2) + dt.setWeekNo(1) + self.assertEqual(dt, PyCalendarDateTime(2013, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))) + self.assertEqual(dt.getWeekNo(), 1) + + dt = PyCalendarDateTime(2014, 1, 7, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) + self.assertEqual(dt.getWeekNo(), 2) + dt.setWeekNo(1) + self.assertEqual(dt, PyCalendarDateTime(2013, 12, 31, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))) + self.assertEqual(dt.getWeekNo(), 1) + + dt = PyCalendarDateTime(2012, 12, 31, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) + self.assertEqual(dt.getWeekNo(), 1) + dt.setWeekNo(1) + self.assertEqual(dt, PyCalendarDateTime(2012, 12, 31, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))) + self.assertEqual(dt.getWeekNo(), 1) + + dt = PyCalendarDateTime(2016, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) + self.assertEqual(dt.getWeekNo(), 53) + dt.setWeekNo(1) + self.assertEqual(dt, PyCalendarDateTime(2016, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))) + self.assertEqual(dt.getWeekNo(), 1) + dt.setWeekNo(2) + self.assertEqual(dt, PyCalendarDateTime(2016, 1, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))) + self.assertEqual(dt.getWeekNo(), 2) + + dt = PyCalendarDateTime(2016, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) + self.assertEqual(dt.getWeekNo(), 1) + dt.setWeekNo(1) + self.assertEqual(dt, PyCalendarDateTime(2016, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))) + self.assertEqual(dt.getWeekNo(), 1) diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_duration.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_duration.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_duration.py 2011-08-31 20:47:52.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_duration.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,35 +20,35 @@ import unittest class TestDuration(unittest.TestCase): - + test_data = ( (0, "PT0S"), (1, "PT1S"), (1, "+PT1S"), (-1, "-PT1S"), (60, "PT1M"), - (60+2, "PT1M2S"), - (1*60*60, "PT1H"), - (1*60*60 + 2*60, "PT1H2M"), - (1*60*60 + 1, "PT1H0M1S"), - (1*60*60 + 2*60 + 1, "PT1H2M1S"), - (24*60*60, "P1D"), - (24*60*60 + 3*60*60, "P1DT3H"), - (24*60*60 + 2*60, "P1DT2M"), - (24*60*60 + 3*60*60 + 2*60, "P1DT3H2M"), - (24*60*60 + 1, "P1DT1S"), - (24*60*60 + 2*60 + 1, "P1DT2M1S"), - (24*60*60 + 3*60*60 + 1, "P1DT3H0M1S"), - (24*60*60 + 3*60*60 + 2*60 + 1, "P1DT3H2M1S"), - (14*24*60*60, "P2W"), - (15*24*60*60, "P15D"), - (14*24*60*60 + 1, "P14DT1S"), + (60 + 2, "PT1M2S"), + (1 * 60 * 60, "PT1H"), + (1 * 60 * 60 + 2 * 60, "PT1H2M"), + (1 * 60 * 60 + 1, "PT1H0M1S"), + (1 * 60 * 60 + 2 * 60 + 1, "PT1H2M1S"), + (24 * 60 * 60, "P1D"), + (24 * 60 * 60 + 3 * 60 * 60, "P1DT3H"), + (24 * 60 * 60 + 2 * 60, "P1DT2M"), + (24 * 60 * 60 + 3 * 60 * 60 + 2 * 60, "P1DT3H2M"), + (24 * 60 * 60 + 1, "P1DT1S"), + (24 * 60 * 60 + 2 * 60 + 1, "P1DT2M1S"), + (24 * 60 * 60 + 3 * 60 * 60 + 1, "P1DT3H0M1S"), + (24 * 60 * 60 + 3 * 60 * 60 + 2 * 60 + 1, "P1DT3H2M1S"), + (14 * 24 * 60 * 60, "P2W"), + (15 * 24 * 60 * 60, "P15D"), + (14 * 24 * 60 * 60 + 1, "P14DT1S"), ) - + def testGenerate(self): - + def _doTest(d, result): - + if result[0] == "+": result = result[1:] os = StringIO() @@ -57,15 +57,17 @@ for seconds, result in TestDuration.test_data: _doTest(PyCalendarDuration(duration=seconds), result) - + + def testParse(self): - + for seconds, result in TestDuration.test_data: duration = PyCalendarDuration().parseText(result) self.assertEqual(duration.getTotalSeconds(), seconds) - + + def testParseBad(self): - + test_bad_data = ( "", "ABC", @@ -80,19 +82,20 @@ ) for data in test_bad_data: self.assertRaises(ValueError, PyCalendarDuration.parseText, data) - + + def testRelaxedBad(self): - + test_relaxed_data = ( ("P12DT", 12 * 24 * 60 * 60, "P12D"), ("-P1WT", -7 * 24 * 60 * 60, "-P1W"), ("-P1W1D", -7 * 24 * 60 * 60, "-P1W"), ) for text, seconds, result in test_relaxed_data: - + ParserContext.INVALID_DURATION_VALUE = ParserContext.PARSER_FIX self.assertEqual(PyCalendarDuration.parseText(text).getTotalSeconds(), seconds) self.assertEqual(PyCalendarDuration.parseText(text).getText(), result) - + ParserContext.INVALID_DURATION_VALUE = ParserContext.PARSER_RAISE self.assertRaises(ValueError, PyCalendarDuration.parseText, text) diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_i18n.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_i18n.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_i18n.py 2011-04-21 14:37:42.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_i18n.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,13 +1,13 @@ # coding: utf-8 ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -21,7 +21,6 @@ import unittest class TestCalendar(unittest.TestCase): - def testAddCN(self): @@ -43,7 +42,7 @@ """.replace("\n", "\r\n"), "まだ", - + """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN @@ -64,7 +63,7 @@ cal1 = PyCalendar() cal1.parse(StringIO.StringIO(data[0])) - + vevent = cal1.getComponents("VEVENT")[0] organizer = vevent.getProperties("ORGANIZER")[0] organizer.addAttribute(PyCalendarAttribute("CN", data[1])) diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_multivalue.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_multivalue.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_multivalue.py 1970-01-01 00:00:00.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_multivalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -0,0 +1,45 @@ +## +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +## + +import unittest +from pycalendar.multivalue import PyCalendarMultiValue +from pycalendar.value import PyCalendarValue + +class TestMultiValue(unittest.TestCase): + + def testParseValue(self): + + items = ( + ("", "", 1), + ("Example", "Example", 1), + ("Example1,Example2", "Example1,Example2", 2), + ) + + for item, result, count in items: + req = PyCalendarMultiValue(PyCalendarValue.VALUETYPE_TEXT) + req.parse(item) + test = req.getText() + self.assertEqual(test, result, "Failed to parse and re-generate '%s'" % (item,)) + self.assertEqual(len(req.mValues), count, "Failed to parse and re-generate '%s'" % (item,)) + + + def testSetValue(self): + + req = PyCalendarMultiValue(PyCalendarValue.VALUETYPE_TEXT) + req.parse("Example1, Example2") + req.setValue(("Example3", "Example4",)) + test = req.getText() + self.assertEqual(test, "Example3,Example4") diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_n.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_n.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_n.py 2011-12-12 18:58:52.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_n.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,9 +18,9 @@ import unittest class TestAdrValue(unittest.TestCase): - + def testInit(self): - + data = ( ( ("last", "first", "middle", "prefix", "suffix"), @@ -36,12 +36,12 @@ for args, result, fullName in data: n = N(*args) - + self.assertEqual( n.getValue(), args, ) - + self.assertEqual( n.getText(), result, @@ -56,17 +56,18 @@ n.duplicate().getText(), result, ) - + + def testInitWithKeywords(self): - + data = ( ( - {"first":"first", "last":"last", "middle":"middle", "prefix":"prefix", "suffix":"suffix"}, + {"first": "first", "last": "last", "middle": "middle", "prefix": "prefix", "suffix": "suffix"}, "last;first;middle;prefix;suffix", "prefix first middle last suffix", ), ( - {"first":("first",), "last":"last", "middle":("middle1", "middle2",), "prefix":(), "suffix":("suffix",)}, + {"first": ("first",), "last": "last", "middle": ("middle1", "middle2",), "prefix": (), "suffix": ("suffix",)}, "last;first;middle1,middle2;;suffix", "first middle1 middle2 last suffix", ), @@ -74,7 +75,7 @@ for kwargs, result, fullName in data: n = N(**kwargs) - + self.assertEqual( n.getText(), result, diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_nvalue.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_nvalue.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_nvalue.py 2011-04-27 15:38:06.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_nvalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,9 +19,9 @@ import unittest class TestNValue(unittest.TestCase): - + def testParseValue(self): - + items = ( ("", ";;;;"), (";", ";;;;"), @@ -31,15 +31,16 @@ ("Cyrus", "Cyrus;;;;"), (";Daboo", ";Daboo;;;"), ) - + for item, result in items: req = NValue() req.parse(item) test = req.getText() self.assertEqual(test, result, "Failed to parse and re-generate '%s'" % (item,)) + def testParseProperty(self): - + items = ( ("N:", "N:;;;;"), ("N:;", "N:;;;;"), @@ -50,7 +51,7 @@ ("N:;Daboo", "N:;Daboo;;;"), ("N;VALUE=TEXT:Cyrus;Daboo;;Dr.", "N:Cyrus;Daboo;;Dr.;"), ) - + for item, result in items: prop = Property() prop.parse(item) diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_orgvalue.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_orgvalue.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_orgvalue.py 2011-04-27 15:38:06.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_orgvalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,24 +19,25 @@ import unittest class TestNValue(unittest.TestCase): - + def testParseValue(self): - + items = ( ("", ""), ("Example", "Example"), ("Example\, Inc.", "Example\, Inc."), ("Example\; Inc;Dept. of Silly Walks", "Example\; Inc;Dept. of Silly Walks"), ) - + for item, result in items: req = OrgValue() req.parse(item) test = req.getText() self.assertEqual(test, result, "Failed to parse and re-generate '%s'" % (item,)) + def testParseProperty(self): - + items = ( ("ORG:", "ORG:"), ("ORG:Example", "ORG:Example"), @@ -44,7 +45,7 @@ ("ORG:Example\; Inc;Dept. of Silly Walks", "ORG:Example\; Inc;Dept. of Silly Walks"), ("ORG;VALUE=TEXT:Example", "ORG:Example"), ) - + for item, result in items: prop = Property() prop.parse(item) diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_property.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_property.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_property.py 2011-06-23 15:24:56.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_property.py 2013-05-21 13:35:26.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2013 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,13 +14,15 @@ # limitations under the License. ## +from pycalendar.attribute import PyCalendarAttribute from pycalendar.exceptions import PyCalendarInvalidProperty from pycalendar.parser import ParserContext from pycalendar.property import PyCalendarProperty +from pycalendar.value import PyCalendarValue import unittest class TestProperty(unittest.TestCase): - + test_data = ( # Different value types "ATTACH;VALUE=BINARY:VGVzdA==", @@ -40,31 +42,42 @@ "REQUEST-STATUS:2.0;Success", "URI:http://www.example.com", "TZOFFSETFROM:-0500", - + "X-Test:Some\, text.", + "X-Test:Some:, text.", + "X-APPLE-STRUCTURED-LOCATION;VALUE=URI:geo:123.123,123.123", + "X-CALENDARSERVER-PRIVATE-COMMENT:This\\ntest\\nis\\, here.\\n", + # Various parameters "DTSTART;TZID=\"Somewhere, else\":20060226T120000", "ATTENDEE;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:jdoe@example.com", + "X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-APPLE-ABUID=ab\\://Work;X-TITLE=\"10\\n XX S. XXX Dr.\\nSuite XXX\\nXX XX XXXXX\\nUnited States\":\"geo:11.111111,-11.111111\"", + + # Parameter escaping + "ATTENDEE;CN=My ^'Test^' Name;ROLE=CHAIR:mailto:jdoe@example.com", ) - + + def testParseGenerate(self): - + for data in TestProperty.test_data: prop = PyCalendarProperty() prop.parse(data) - propstr = str(prop) + propstr = str(prop).replace("\r\n ", "") self.assertEqual(propstr[:-2], data, "Failed parse/generate: %s to %s" % (data, propstr,)) - + + def testEquality(self): - + for data in TestProperty.test_data: prop1 = PyCalendarProperty() prop1.parse(data) prop2 = PyCalendarProperty() prop2.parse(data) self.assertEqual(prop1, prop2, "Failed equality: %s" % (data,)) - + + def testParseBad(self): - + test_bad_data = ( "DTSTART;TZID=US/Eastern:abc", "DTSTART;VALUE=DATE:20060226T", @@ -76,7 +89,6 @@ "FREEBUSY:20060226T120000Z/ABC", "SUMMARY:Some \\qtext", "RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,VE;BYSETPOS=-1", - "REQUEST-STATUS:2.0,Success", "TZOFFSETFROM:-050", """ATTENDEE;CN="\\";CUTYPE=INDIVIDUAL;PARTSTAT=X-UNDELIVERABLE:invalid:nomai l""", @@ -87,9 +99,10 @@ prop = PyCalendarProperty() self.assertRaises(PyCalendarInvalidProperty, prop.parse, data) ParserContext.INVALID_ESCAPE_SEQUENCES = save - + + def testHash(self): - + hashes = [] for item in TestProperty.test_data: prop = PyCalendarProperty() @@ -97,10 +110,11 @@ hashes.append(hash(prop)) hashes.sort() for i in range(1, len(hashes)): - self.assertNotEqual(hashes[i-1], hashes[i]) + self.assertNotEqual(hashes[i - 1], hashes[i]) + def testDefaultValueCreate(self): - + test_data = ( ("ATTENDEE", "mailto:attendee@example.com", "ATTENDEE:mailto:attendee@example.com\r\n"), ("attendee", "mailto:attendee@example.com", "attendee:mailto:attendee@example.com\r\n"), @@ -112,3 +126,64 @@ for propname, propvalue, result in test_data: prop = PyCalendarProperty(name=propname, value=propvalue) self.assertEqual(str(prop), result) + + + def testGEOValueRoundtrip(self): + + data = "GEO:123.456,789.101" + prop = PyCalendarProperty() + prop.parse(data) + self.assertEqual(str(prop), data + "\r\n") + + + def testUnknownValueRoundtrip(self): + + data = "X-FOO:Text, not escaped" + prop = PyCalendarProperty() + prop.parse(data) + self.assertEqual(str(prop), data + "\r\n") + + prop = PyCalendarProperty("X-FOO", "Text, not escaped") + self.assertEqual(str(prop), data + "\r\n") + + data = "X-FOO:Text\\, escaped\\n" + prop = PyCalendarProperty() + prop.parse(data) + self.assertEqual(str(prop), data + "\r\n") + + prop = PyCalendarProperty("X-FOO", "Text\\, escaped\\n") + self.assertEqual(str(prop), data + "\r\n") + + + def testNewRegistrationValueRoundtrip(self): + + PyCalendarProperty.registerDefaultValue("X-SPECIAL-REGISTRATION", PyCalendarValue.VALUETYPE_TEXT) + + data = "X-SPECIAL-REGISTRATION:Text\\, escaped\\n" + prop = PyCalendarProperty() + prop.parse(data) + self.assertEqual(str(prop), "X-SPECIAL-REGISTRATION:Text\\, escaped\\n\r\n") + + prop = PyCalendarProperty("X-SPECIAL-REGISTRATION", "Text, escaped\n") + self.assertEqual(str(prop), "X-SPECIAL-REGISTRATION:Text\\, escaped\\n\r\n") + + + def testParameterEncodingDecoding(self): + + prop = PyCalendarProperty("X-FOO", "Test") + prop.addAttribute(PyCalendarAttribute("X-BAR", "\"Check\"")) + self.assertEqual(str(prop), "X-FOO;X-BAR=^'Check^':Test\r\n") + + prop.addAttribute(PyCalendarAttribute("X-BAR2", "Check\nThis\tOut\n")) + self.assertEqual(str(prop), "X-FOO;X-BAR=^'Check^';X-BAR2=Check^nThis\tOut^n:Test\r\n") + + data = "X-FOO;X-BAR=^'Check^':Test" + prop = PyCalendarProperty() + prop.parse(data) + self.assertEqual(prop.getAttributeValue("X-BAR"), "\"Check\"") + + data = "X-FOO;X-BAR=^'Check^';X-BAR2=Check^nThis\tOut^n:Test" + prop = PyCalendarProperty() + prop.parse(data) + self.assertEqual(prop.getAttributeValue("X-BAR"), "\"Check\"") + self.assertEqual(prop.getAttributeValue("X-BAR2"), "Check\nThis\tOut\n") diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_recurrence.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_recurrence.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_recurrence.py 2011-04-27 15:38:06.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_recurrence.py 2014-04-07 16:00:00.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,11 +14,14 @@ # limitations under the License. ## +from pycalendar.datetime import PyCalendarDateTime +from pycalendar.period import PyCalendarPeriod from pycalendar.recurrence import PyCalendarRecurrence +from pycalendar.timezone import PyCalendarTimezone import unittest class TestRecurrence(unittest.TestCase): - + items = ( "FREQ=DAILY", "FREQ=YEARLY;COUNT=400", @@ -26,7 +29,7 @@ "FREQ=MONTHLY;UNTIL=20110102T090000", "FREQ=MONTHLY;UNTIL=20110102T100000Z", "FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=-1", - + # These are from RFC5545 examples "FREQ=YEARLY;INTERVAL=2;BYMINUTE=30;BYHOUR=8,9;BYDAY=SU;BYMONTH=1", "FREQ=YEARLY;UNTIL=19730429T070000Z;BYDAY=-1SU;BYMONTH=4", @@ -49,16 +52,18 @@ "FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3", "FREQ=DAILY;BYMINUTE=0,20,40;BYHOUR=9,10,11,12,13,14,15,16", ) - + + def testParse(self): - + for item in TestRecurrence.items: recur = PyCalendarRecurrence() recur.parse(item) self.assertEqual(recur.getText(), item, "Failed to parse and re-generate '%s'" % (item,)) + def testParseInvalid(self): - + items = ( "", "FREQ=", @@ -74,30 +79,33 @@ "FREQ=MONTHLY;BYHOUR=A", "FREQ=MONTHLY;BYHOUR=54", ) - + for item in items: self.assertRaises(ValueError, PyCalendarRecurrence().parse, item) - + + def testEquality(self): - + recur1 = PyCalendarRecurrence() recur1.parse("FREQ=YEARLY;COUNT=400") recur2 = PyCalendarRecurrence() recur2.parse("COUNT=400;FREQ=YEARLY") self.assertEqual(recur1, recur2) - + + def testInequality(self): - + recur1 = PyCalendarRecurrence() recur1.parse("FREQ=YEARLY;COUNT=400") recur2 = PyCalendarRecurrence() recur2.parse("COUNT=400;FREQ=YEARLY;BYMONTH=1") self.assertNotEqual(recur1, recur2) - + + def testHash(self): - + hashes = [] for item in TestRecurrence.items: recur = PyCalendarRecurrence() @@ -105,4 +113,257 @@ hashes.append(hash(recur)) hashes.sort() for i in range(1, len(hashes)): - self.assertNotEqual(hashes[i-1], hashes[i]) + self.assertNotEqual(hashes[i - 1], hashes[i]) + + + def testByWeekNoExpand(self): + + recur = PyCalendarRecurrence() + recur.parse("FREQ=YEARLY;BYWEEKNO=1,2") + start = PyCalendarDateTime(2013, 1, 1, 0, 0, 0) + end = PyCalendarDateTime(2017, 1, 1, 0, 0, 0) + items = [] + range = PyCalendarPeriod(start, end) + recur.expand(start, range, items) + self.assertEqual( + items, + [ + PyCalendarDateTime(2013, 1, 1, 0, 0, 0), + PyCalendarDateTime(2013, 1, 8, 0, 0, 0), + PyCalendarDateTime(2014, 1, 1, 0, 0, 0), + PyCalendarDateTime(2014, 1, 8, 0, 0, 0), + PyCalendarDateTime(2015, 1, 1, 0, 0, 0), + PyCalendarDateTime(2015, 1, 8, 0, 0, 0), + PyCalendarDateTime(2016, 1, 8, 0, 0, 0), + PyCalendarDateTime(2016, 1, 15, 0, 0, 0), + ], + ) + + + def testMonthlyInvalidStart(self): + + recur = PyCalendarRecurrence() + recur.parse("FREQ=MONTHLY") + start = PyCalendarDateTime(2014, 1, 40, 12, 0, 0) + end = PyCalendarDateTime(2015, 1, 1, 0, 0, 0) + items = [] + range = PyCalendarPeriod(start, end) + recur.expand(start, range, items) + self.assertEqual( + items, + [ + PyCalendarDateTime(2014, 2, 9, 12, 0, 0), + PyCalendarDateTime(2014, 3, 9, 12, 0, 0), + PyCalendarDateTime(2014, 4, 9, 12, 0, 0), + PyCalendarDateTime(2014, 5, 9, 12, 0, 0), + PyCalendarDateTime(2014, 6, 9, 12, 0, 0), + PyCalendarDateTime(2014, 7, 9, 12, 0, 0), + PyCalendarDateTime(2014, 8, 9, 12, 0, 0), + PyCalendarDateTime(2014, 9, 9, 12, 0, 0), + PyCalendarDateTime(2014, 10, 9, 12, 0, 0), + PyCalendarDateTime(2014, 11, 9, 12, 0, 0), + PyCalendarDateTime(2014, 12, 9, 12, 0, 0), + ], + ) + + + def testMonthlyInUTC(self): + + recur = PyCalendarRecurrence() + recur.parse("FREQ=MONTHLY") + start = PyCalendarDateTime(2014, 1, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)) + end = PyCalendarDateTime(2015, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) + items = [] + range = PyCalendarPeriod(start, end) + recur.expand(PyCalendarDateTime(2014, 1, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), range, items) + self.assertEqual( + items, + [ + PyCalendarDateTime(2014, 1, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), + PyCalendarDateTime(2014, 2, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), + PyCalendarDateTime(2014, 3, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), + PyCalendarDateTime(2014, 4, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), + PyCalendarDateTime(2014, 5, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), + PyCalendarDateTime(2014, 6, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), + PyCalendarDateTime(2014, 7, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), + PyCalendarDateTime(2014, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), + PyCalendarDateTime(2014, 9, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), + PyCalendarDateTime(2014, 10, 1, 12, 0, 0), + PyCalendarDateTime(2014, 11, 1, 12, 0, 0), + PyCalendarDateTime(2014, 12, 1, 12, 0, 0), + ], + ) + + + def testMonthlyStart31st(self): + + recur = PyCalendarRecurrence() + recur.parse("FREQ=MONTHLY") + start = PyCalendarDateTime(2014, 1, 31, 12, 0, 0) + end = PyCalendarDateTime(2015, 1, 1, 0, 0, 0) + items = [] + range = PyCalendarPeriod(start, end) + recur.expand(start, range, items) + self.assertEqual( + items, + [ + PyCalendarDateTime(2014, 1, 31, 12, 0, 0), + PyCalendarDateTime(2014, 3, 31, 12, 0, 0), + PyCalendarDateTime(2014, 5, 31, 12, 0, 0), + PyCalendarDateTime(2014, 7, 31, 12, 0, 0), + PyCalendarDateTime(2014, 8, 31, 12, 0, 0), + PyCalendarDateTime(2014, 10, 31, 12, 0, 0), + PyCalendarDateTime(2014, 12, 31, 12, 0, 0), + ], + ) + + + def testMonthlyByMonthDay31(self): + + recur = PyCalendarRecurrence() + recur.parse("FREQ=MONTHLY;BYMONTHDAY=31") + start = PyCalendarDateTime(2014, 1, 31, 12, 0, 0) + end = PyCalendarDateTime(2015, 1, 1, 0, 0, 0) + items = [] + range = PyCalendarPeriod(start, end) + recur.expand(start, range, items) + self.assertEqual( + items, + [ + PyCalendarDateTime(2014, 1, 31, 12, 0, 0), + PyCalendarDateTime(2014, 3, 31, 12, 0, 0), + PyCalendarDateTime(2014, 5, 31, 12, 0, 0), + PyCalendarDateTime(2014, 7, 31, 12, 0, 0), + PyCalendarDateTime(2014, 8, 31, 12, 0, 0), + PyCalendarDateTime(2014, 10, 31, 12, 0, 0), + PyCalendarDateTime(2014, 12, 31, 12, 0, 0), + ], + ) + + + def testMonthlyByMonthDayMinus31(self): + + recur = PyCalendarRecurrence() + recur.parse("FREQ=MONTHLY;BYMONTHDAY=-31") + start = PyCalendarDateTime(2014, 1, 1, 12, 0, 0) + end = PyCalendarDateTime(2015, 1, 1, 0, 0, 0) + items = [] + range = PyCalendarPeriod(start, end) + recur.expand(start, range, items) + self.assertEqual( + items, + [ + PyCalendarDateTime(2014, 1, 1, 12, 0, 0), + PyCalendarDateTime(2014, 3, 1, 12, 0, 0), + PyCalendarDateTime(2014, 5, 1, 12, 0, 0), + PyCalendarDateTime(2014, 7, 1, 12, 0, 0), + PyCalendarDateTime(2014, 8, 1, 12, 0, 0), + PyCalendarDateTime(2014, 10, 1, 12, 0, 0), + PyCalendarDateTime(2014, 12, 1, 12, 0, 0), + ], + ) + + + def testMonthlyByLastFridayExpand(self): + + recur = PyCalendarRecurrence() + recur.parse("FREQ=MONTHLY;BYDAY=-1FR") + start = PyCalendarDateTime(2014, 1, 31, 12, 0, 0) + end = PyCalendarDateTime(2015, 1, 1, 0, 0, 0) + items = [] + range = PyCalendarPeriod(start, end) + recur.expand(start, range, items) + self.assertEqual( + items, + [ + PyCalendarDateTime(2014, 1, 31, 12, 0, 0), + PyCalendarDateTime(2014, 2, 28, 12, 0, 0), + PyCalendarDateTime(2014, 3, 28, 12, 0, 0), + PyCalendarDateTime(2014, 4, 25, 12, 0, 0), + PyCalendarDateTime(2014, 5, 30, 12, 0, 0), + PyCalendarDateTime(2014, 6, 27, 12, 0, 0), + PyCalendarDateTime(2014, 7, 25, 12, 0, 0), + PyCalendarDateTime(2014, 8, 29, 12, 0, 0), + PyCalendarDateTime(2014, 9, 26, 12, 0, 0), + PyCalendarDateTime(2014, 10, 31, 12, 0, 0), + PyCalendarDateTime(2014, 11, 28, 12, 0, 0), + PyCalendarDateTime(2014, 12, 26, 12, 0, 0), + ], + ) + + + def testMonthlyByFifthFridayExpand(self): + + recur = PyCalendarRecurrence() + recur.parse("FREQ=MONTHLY;BYDAY=5FR") + start = PyCalendarDateTime(2014, 1, 31, 12, 0, 0) + end = PyCalendarDateTime(2015, 1, 1, 0, 0, 0) + items = [] + range = PyCalendarPeriod(start, end) + recur.expand(start, range, items) + self.assertEqual( + items, + [ + PyCalendarDateTime(2014, 1, 31, 12, 0, 0), + PyCalendarDateTime(2014, 5, 30, 12, 0, 0), + PyCalendarDateTime(2014, 8, 29, 12, 0, 0), + PyCalendarDateTime(2014, 10, 31, 12, 0, 0), + ], + ) + + + def testYearlyLeapDay(self): + + recur = PyCalendarRecurrence() + recur.parse("FREQ=YEARLY") + start = PyCalendarDateTime(2012, 2, 29, 12, 0, 0) + end = PyCalendarDateTime(2020, 1, 1, 0, 0, 0) + items = [] + range = PyCalendarPeriod(start, end) + recur.expand(start, range, items) + self.assertEqual( + items, + [ + PyCalendarDateTime(2012, 2, 29, 12, 0, 0), + PyCalendarDateTime(2016, 2, 29, 12, 0, 0), + ], + ) + + + def testYearlyYearDay(self): + + recur = PyCalendarRecurrence() + recur.parse("FREQ=YEARLY;BYYEARDAY=366") + start = PyCalendarDateTime(2012, 12, 31, 12, 0, 0) + end = PyCalendarDateTime(2020, 1, 1, 0, 0, 0) + items = [] + range = PyCalendarPeriod(start, end) + recur.expand(start, range, items) + self.assertEqual( + items, + [ + PyCalendarDateTime(2012, 12, 31, 12, 0, 0), + PyCalendarDateTime(2016, 12, 31, 12, 0, 0), + ], + ) + + + def testClearOnChange(self): + + recur = PyCalendarRecurrence() + recur.parse("FREQ=DAILY") + + start = PyCalendarDateTime(2013, 1, 1, 0, 0, 0) + end = PyCalendarDateTime(2017, 1, 1, 0, 0, 0) + range = PyCalendarPeriod(start, end) + items = [] + recur.expand(start, range, items) + self.assertTrue(recur.mCached) + self.assertTrue(len(items) > 100) + + recur.setUseCount(True) + recur.setCount(10) + self.assertFalse(recur.mCached) + items = [] + recur.expand(start, range, items) + self.assertEqual(len(items), 10) diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_requeststatus.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_requeststatus.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_requeststatus.py 2011-08-25 17:50:54.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_requeststatus.py 2013-04-03 15:19:39.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2013 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,9 +20,9 @@ import unittest class TestRequestStatus(unittest.TestCase): - + def testParseValue(self): - + items = ( "2.0;Success", "2.0;Success\;here", @@ -31,14 +31,15 @@ "2.0;Success;Extra\;here", "2.0;Success\;here;Extra\;here too", ) - + for item in items: req = PyCalendarRequestStatusValue() req.parse(item) self.assertEqual(req.getText(), item, "Failed to parse and re-generate '%s'" % (item,)) + def testBadValue(self): - + bad_value = "2.0\;Success" ok_value = "2.0;Success" @@ -56,8 +57,29 @@ ParserContext.INVALID_REQUEST_STATUS_VALUE = oldContext + + def testTruncatedValue(self): + + bad_value = "2.0" + ok_value = "2.0;" + + # Fix the value + oldContext = ParserContext.INVALID_REQUEST_STATUS_VALUE + ParserContext.INVALID_REQUEST_STATUS_VALUE = ParserContext.PARSER_FIX + req = PyCalendarRequestStatusValue() + req.parse(bad_value) + self.assertEqual(req.getText(), ok_value, "Failed to parse and re-generate '%s'" % (bad_value,)) + + # Raise the value + ParserContext.INVALID_REQUEST_STATUS_VALUE = ParserContext.PARSER_RAISE + req = PyCalendarRequestStatusValue() + self.assertRaises(ValueError, req.parse, bad_value) + + ParserContext.INVALID_REQUEST_STATUS_VALUE = oldContext + + def testParseProperty(self): - + items = ( "REQUEST-STATUS:2.0;Success", "REQUEST-STATUS:2.0;Success\;here", @@ -66,7 +88,7 @@ "REQUEST-STATUS:2.0;Success;Extra\;here", "REQUEST-STATUS:2.0;Success\;here;Extra\;here too", ) - + for item in items: req = PyCalendarProperty() req.parse(item) diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_timezone.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_timezone.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_timezone.py 2011-03-17 17:47:50.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_timezone.py 2014-04-03 02:27:44.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,17 +16,18 @@ from pycalendar.calendar import PyCalendar from pycalendar.datetime import PyCalendarDateTime +from pycalendar.timezone import PyCalendarTimezone import unittest class TestCalendar(unittest.TestCase): - + def testOffsets(self): data = ( ("""BEGIN:VCALENDAR +VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN -VERSION:2.0 BEGIN:VTIMEZONE TZID:America/New_York X-LIC-LOCATION:America/New_York @@ -173,18 +174,34 @@ END:VCALENDAR """, ( - (PyCalendarDateTime(1942, 2, 8), -5), - (PyCalendarDateTime(1942, 2, 10), -4), - (PyCalendarDateTime(2011, 1, 1), -5), - (PyCalendarDateTime(2011, 4, 1), -4), - (PyCalendarDateTime(2011, 10, 24), -4), - (PyCalendarDateTime(2011, 11, 8), -5), - (PyCalendarDateTime(2006, 1, 1), -5), - (PyCalendarDateTime(2006, 4, 1), -5), - (PyCalendarDateTime(2006, 5, 1), -4), - (PyCalendarDateTime(2006, 10, 1), -4), - (PyCalendarDateTime(2006, 10, 24), -4), - (PyCalendarDateTime(2006, 11, 8), -5), + (PyCalendarDateTime(1942, 2, 8), False, -5), + (PyCalendarDateTime(1942, 2, 10), False, -4), + (PyCalendarDateTime(2011, 1, 1), False, -5), + (PyCalendarDateTime(2011, 4, 1), False, -4), + (PyCalendarDateTime(2011, 10, 24), False, -4), + (PyCalendarDateTime(2011, 11, 8), False, -5), + (PyCalendarDateTime(2006, 1, 1), False, -5), + (PyCalendarDateTime(2006, 4, 1), False, -5), + (PyCalendarDateTime(2006, 5, 1), False, -4), + (PyCalendarDateTime(2006, 10, 1), False, -4), + (PyCalendarDateTime(2006, 10, 24), False, -4), + (PyCalendarDateTime(2006, 11, 8), False, -5), + (PyCalendarDateTime(2014, 3, 8, 23, 0, 0), False, -5), + (PyCalendarDateTime(2014, 3, 9, 0, 0, 0), False, -5), + (PyCalendarDateTime(2014, 3, 9, 3, 0, 0), False, -4), + (PyCalendarDateTime(2014, 3, 9, 8, 0, 0), False, -4), + (PyCalendarDateTime(2014, 3, 8, 23, 0, 0), True, -5), + (PyCalendarDateTime(2014, 3, 9, 0, 0, 0), True, -5), + (PyCalendarDateTime(2014, 3, 9, 3, 0, 0), True, -5), + (PyCalendarDateTime(2014, 3, 9, 8, 0, 0), True, -4), + (PyCalendarDateTime(2014, 11, 1, 23, 0, 0), False, -4), + (PyCalendarDateTime(2014, 11, 2, 0, 0, 0), False, -4), + (PyCalendarDateTime(2014, 11, 2, 3, 0, 0), False, -5), + (PyCalendarDateTime(2014, 11, 2, 8, 0, 0), False, -5), + (PyCalendarDateTime(2014, 11, 1, 23, 0, 0), True, -4), + (PyCalendarDateTime(2014, 11, 2, 0, 0, 0), True, -4), + (PyCalendarDateTime(2014, 11, 2, 3, 0, 0), True, -4), + (PyCalendarDateTime(2014, 11, 2, 8, 0, 0), True, -5), ) ), ("""BEGIN:VCALENDAR @@ -205,10 +222,10 @@ END:VCALENDAR """, ( - (PyCalendarDateTime(1942, 2, 8), -8), - (PyCalendarDateTime(1942, 2, 10), -8), - (PyCalendarDateTime(2011, 1, 1), -8), - (PyCalendarDateTime(2011, 4, 1), -8), + (PyCalendarDateTime(1942, 2, 8), False, -8), + (PyCalendarDateTime(1942, 2, 10), False, -8), + (PyCalendarDateTime(2011, 1, 1), False, -8), + (PyCalendarDateTime(2011, 4, 1), False, -8), ) ), ) @@ -218,18 +235,323 @@ cal = PyCalendar.parseText(tzdata.replace("\n", "\r\n")) tz = cal.getComponents()[0] - for dt, offset in offsets: - tzoffset = tz.getTimezoneOffsetSeconds(dt) + 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, offset in reversed(offsets): - tzoffset = tz.getTimezoneOffsetSeconds(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, offset in offsets: + for dt, relative_to_utc, offset in offsets: tz.mCachedExpandAllMax = None - tzoffset = tz.getTimezoneOffsetSeconds(dt) + 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, offset in reversed(offsets): + for dt, relative_to_utc, offset in reversed(offsets): tz.mCachedExpandAllMax = None - tzoffset = tz.getTimezoneOffsetSeconds(dt) + 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 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 = ( + ( + PyCalendarDateTime(2014, 3, 8, 23, 0, 0, PyCalendarTimezone(tzid="America/New_York")), + PyCalendarDateTime(2014, 3, 8, 20, 0, 0, PyCalendarTimezone(tzid="America/Los_Angeles")), + ), + ( + PyCalendarDateTime(2014, 3, 9, 3, 0, 0, PyCalendarTimezone(utc=True)), + PyCalendarDateTime(2014, 3, 8, 19, 0, 0, PyCalendarTimezone(tzid="America/Los_Angeles")), + ), + ( + PyCalendarDateTime(2014, 3, 9, 13, 0, 0, PyCalendarTimezone(utc=True)), + PyCalendarDateTime(2014, 3, 9, 6, 0, 0, PyCalendarTimezone(tzid="America/Los_Angeles")), + ), + ) + + PyCalendar.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()) diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_urivalue.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_urivalue.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_urivalue.py 1970-01-01 00:00:00.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_urivalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -0,0 +1,87 @@ +## +# Copyright (c) 2012 Cyrus Daboo. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +## + +from pycalendar.parser import ParserContext +from pycalendar.urivalue import PyCalendarURIValue +from pycalendar.vcard.property import Property +import unittest + +class TestNValue(unittest.TestCase): + + def testParseValue(self): + + # Restore BACKSLASH_IN_URI_VALUE after test + old_state = ParserContext.BACKSLASH_IN_URI_VALUE + self.addCleanup(setattr, ParserContext, "BACKSLASH_IN_URI_VALUE", old_state) + + # Test with BACKSLASH_IN_URI_VALUE = PARSER_FIX + ParserContext.BACKSLASH_IN_URI_VALUE = ParserContext.PARSER_FIX + items = ( + ("http://example.com", "http://example.com"), + ("http://example.com&abd\\,def", "http://example.com&abd,def"), + ) + + for item, result in items: + req = PyCalendarURIValue() + req.parse(item) + test = req.getText() + self.assertEqual(test, result, "Failed to parse and re-generate '%s'" % (item,)) + + # Test with BACKSLASH_IN_URI_VALUE = PARSER_ALLOW + ParserContext.BACKSLASH_IN_URI_VALUE = ParserContext.PARSER_ALLOW + items = ( + ("http://example.com", "http://example.com"), + ("http://example.com&abd\\,def", "http://example.com&abd\\,def"), + ) + + for item, result in items: + req = PyCalendarURIValue() + req.parse(item) + test = req.getText() + self.assertEqual(test, result, "Failed to parse and re-generate '%s'" % (item,)) + + + def testParseProperty(self): + + # Restore BACKSLASH_IN_URI_VALUE after test + old_state = ParserContext.BACKSLASH_IN_URI_VALUE + self.addCleanup(setattr, ParserContext, "BACKSLASH_IN_URI_VALUE", old_state) + + # Test with BACKSLASH_IN_URI_VALUE = PARSER_FIX + ParserContext.BACKSLASH_IN_URI_VALUE = ParserContext.PARSER_FIX + items = ( + ("URL:http://example.com", "URL:http://example.com"), + ("URL:http://example.com&abd\\,def", "URL:http://example.com&abd,def"), + ) + + for item, result in items: + prop = Property() + prop.parse(item) + test = prop.getText() + self.assertEqual(test, result + "\r\n", "Failed to parse and re-generate '%s'" % (item,)) + + # Test with BACKSLASH_IN_URI_VALUE = PARSER_ALLOW + ParserContext.BACKSLASH_IN_URI_VALUE = ParserContext.PARSER_ALLOW + items = ( + ("URL:http://example.com", "URL:http://example.com"), + ("URL:http://example.com&abd\\,def", "URL:http://example.com&abd\\,def"), + ) + + for item, result in items: + prop = Property() + prop.parse(item) + test = prop.getText() + self.assertEqual(test, result + "\r\n", "Failed to parse and re-generate '%s'" % (item,)) diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_utils.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_utils.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_utils.py 1970-01-01 00:00:00.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_utils.py 2013-05-21 13:35:26.000000000 +0000 @@ -0,0 +1,56 @@ +## +# Copyright (c) 2012 Cyrus Daboo. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +## + +import unittest +from pycalendar.utils import encodeParameterValue, decodeParameterValue + +class TestUtils(unittest.TestCase): + + def test_encodeParameterValue(self): + """ + Round trip encodeParameterValue and decodeParameterValue. + """ + + data = ( + ("abc", "abc", None), + ("\"abc\"", "^'abc^'", None), + ("abc\ndef", "abc^ndef", None), + ("abc\rdef", "abc^ndef", "abc\ndef"), + ("abc\r\ndef", "abc^ndef", "abc\ndef"), + ("abc\n\tdef", "abc^n\tdef", None), + ("abc^2", "abc^^2", None), + ("^abc^", "^^abc^^", None), + ) + + for value, encoded, decoded in data: + if decoded is None: + decoded = value + self.assertEqual(encodeParameterValue(value), encoded) + self.assertEqual(decodeParameterValue(encoded), decoded) + + + def test_decodeParameterValue(self): + """ + Special cases for decodeParameterValue. + """ + + data = ( + ("^a^bc^", "^a^bc^"), + ("^^^abc", "^^abc"), + ) + + for value, decoded in data: + self.assertEqual(decodeParameterValue(value), decoded) diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_validation.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_validation.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_validation.py 2011-06-30 16:03:45.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_validation.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,45 +19,49 @@ import unittest class TestValidation(unittest.TestCase): - + def test_partial(self): - + + def _test(a, b): return (a, b) - + self.assertEqual(partial(_test, "a", "b")(), ("a", "b",)) self.assertEqual(partial(_test, "a")("b"), ("a", "b",)) self.assertEqual(partial(_test)("a", "b"), ("a", "b",)) + def test_stringValue(self): - + props = ( ("SUMMARY:Test", "Test", True,), ("SUMMARY:Test", "TEST", True,), ("DTSTART:20110623T174806", "Test", False), ) - + for prop, test, result in props: property = PyCalendarProperty() property.parse(prop) self.assertEqual(PropertyValueChecks.stringValue(test, property), result) + def test_alwaysUTC(self): - + props = ( ("SUMMARY:Test", False,), ("DTSTART:20110623T174806", False), ("DTSTART;VALUE=DATE:20110623", False), ("DTSTART:20110623T174806Z", True), ) - + for prop, result in props: property = PyCalendarProperty() property.parse(prop) self.assertEqual(PropertyValueChecks.alwaysUTC(property), result) + def test_numericRange(self): - + props = ( ("SUMMARY:Test", 0, 100, False,), ("PERCENT-COMPLETE:0", 0, 100, True,), @@ -66,23 +70,23 @@ ("PERCENT-COMPLETE:200", 0, 100, False,), ("PERCENT-COMPLETE:-1", 0, 100, False,), ) - + for prop, low, high, result in props: property = PyCalendarProperty() property.parse(prop) self.assertEqual(PropertyValueChecks.numericRange(low, high, property), result) + def test_positiveIntegerOrZero(self): - + props = ( ("SUMMARY:Test", False,), ("REPEAT:0", True,), ("REPEAT:100", True,), ("REPEAT:-1", False,), ) - + for prop, result in props: property = PyCalendarProperty() property.parse(prop) self.assertEqual(PropertyValueChecks.positiveIntegerOrZero(property), result) - diff -Nru pycalendar-2.0~svn188/src/pycalendar/tests/test_xml.py pycalendar-2.0~svn13177/src/pycalendar/tests/test_xml.py --- pycalendar-2.0~svn188/src/pycalendar/tests/test_xml.py 2011-06-30 16:03:45.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/tests/test_xml.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,7 +20,7 @@ import unittest class TestCalendar(unittest.TestCase): - + data = ( ( """BEGIN:VCALENDAR @@ -49,7 +49,7 @@ -//mulberrymail.com//Mulberry v4.0//EN - Testing + Testing @@ -91,7 +91,7 @@ cal = PyCalendar() cal.parse(StringIO.StringIO(caldata)) - + test2 = cal.getTextXML() self.assertEqual( @@ -102,4 +102,3 @@ for item1, item2 in self.data: _doRoundtrip(item1, item2) - diff -Nru pycalendar-2.0~svn188/src/pycalendar/textvalue.py pycalendar-2.0~svn13177/src/pycalendar/textvalue.py --- pycalendar-2.0~svn188/src/pycalendar/textvalue.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/textvalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,22 +20,23 @@ from pycalendar.plaintextvalue import PyCalendarPlainTextValue from pycalendar.value import PyCalendarValue -class PyCalendarTextValue( PyCalendarPlainTextValue ): +class PyCalendarTextValue(PyCalendarPlainTextValue): - def getType( self ): + def getType(self): return PyCalendarValue.VALUETYPE_TEXT - def parse( self, data ): + + def parse(self, data): # Decoding required - self.mValue = utils.decodeTextValue( data ) - + self.mValue = utils.decodeTextValue(data) + + # os - StringIO object - def generate( self, os ): + def generate(self, os): try: # Encoding required - utils.writeTextValue( os, self.mValue ) + utils.writeTextValue(os, self.mValue) except: pass PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_TEXT, PyCalendarTextValue, xmldefs.value_text) - \ No newline at end of file diff -Nru pycalendar-2.0~svn188/src/pycalendar/timezonedb.py pycalendar-2.0~svn13177/src/pycalendar/timezonedb.py --- pycalendar-2.0~svn188/src/pycalendar/timezonedb.py 2011-04-27 15:38:06.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/timezonedb.py 2014-04-03 02:27:44.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,8 +14,7 @@ # limitations under the License. ## -from pycalendar.calendar import PyCalendar -from pycalendar.exceptions import PyCalendarNoTimezoneInDatabase,\ +from pycalendar.exceptions import PyCalendarNoTimezoneInDatabase, \ PyCalendarInvalidData import os @@ -24,75 +23,98 @@ On demand timezone database cache. This scans a TZdb directory for .ics files matching a TZID and caches the component data in a calendar from whence the actual component is returned. """ - + sTimezoneDatabase = None + @staticmethod def createTimezoneDatabase(dbpath): + PyCalendarTimezoneDatabase.sTimezoneDatabase = PyCalendarTimezoneDatabase() PyCalendarTimezoneDatabase.sTimezoneDatabase.setPath(dbpath) + @staticmethod def clearTimezoneDatabase(): - PyCalendarTimezoneDatabase.sTimezoneDatabase.clear() + if PyCalendarTimezoneDatabase.sTimezoneDatabase is not None: + PyCalendarTimezoneDatabase.sTimezoneDatabase.clear() + def __init__(self): + from pycalendar.calendar import PyCalendar self.dbpath = None self.calendar = PyCalendar() + def setPath(self, dbpath): self.dbpath = dbpath + def clear(self): + from pycalendar.calendar import PyCalendar self.calendar = PyCalendar() - + + + @staticmethod + def getTimezoneDatabase(): + if PyCalendarTimezoneDatabase.sTimezoneDatabase is None: + PyCalendarTimezoneDatabase.sTimezoneDatabase = PyCalendarTimezoneDatabase() + return PyCalendarTimezoneDatabase.sTimezoneDatabase + + @staticmethod def getTimezone(tzid): - + # Check whether current cached - tz = PyCalendarTimezoneDatabase.sTimezoneDatabase.calendar.getTimezone(tzid) + tzdb = PyCalendarTimezoneDatabase.getTimezoneDatabase() + tz = tzdb.calendar.getTimezone(tzid) if tz is None: try: - PyCalendarTimezoneDatabase.sTimezoneDatabase.cacheTimezone(tzid) + tzdb.cacheTimezone(tzid) except PyCalendarNoTimezoneInDatabase: pass - tz = PyCalendarTimezoneDatabase.sTimezoneDatabase.calendar.getTimezone(tzid) - + tz = tzdb.calendar.getTimezone(tzid) + return tz + @staticmethod def getTimezoneInCalendar(tzid): """ Return a VTIMEZONE inside a valid VCALENDAR """ - + tz = PyCalendarTimezoneDatabase.getTimezone(tzid) if tz is not None: + from pycalendar.calendar import PyCalendar cal = PyCalendar() cal.addComponent(tz.duplicate(cal)) return cal else: return None + @staticmethod - def getTimezoneOffsetSeconds(tzid, dt): + def getTimezoneOffsetSeconds(tzid, dt, relative_to_utc=False): # Cache it first tz = PyCalendarTimezoneDatabase.getTimezone(tzid) if tz is not None: - return PyCalendarTimezoneDatabase.sTimezoneDatabase.calendar.getTimezoneOffsetSeconds(tzid, dt) + return tz.getTimezoneOffsetSeconds(dt, relative_to_utc) else: return 0 + @staticmethod def getTimezoneDescriptor(tzid, dt): # Cache it first tz = PyCalendarTimezoneDatabase.getTimezone(tzid) if tz is not None: - return PyCalendarTimezoneDatabase.sTimezoneDatabase.calendar.getTimezoneDescriptor(tzid, dt) + return tz.getTimezoneDescriptor(dt) else: return "" + def cacheTimezone(self, tzid): - + if self.dbpath is None: return @@ -105,31 +127,34 @@ raise PyCalendarNoTimezoneInDatabase(self.dbpath, tzid) else: raise PyCalendarNoTimezoneInDatabase(self.dbpath, tzid) - + + def addTimezone(self, tz): copy = tz.duplicate(self.calendar) self.calendar.addComponent(copy) - + + @staticmethod def mergeTimezones(cal, tzs): """ Merge each timezone from other calendar. """ - + + tzdb = PyCalendarTimezoneDatabase.getTimezoneDatabase() + # Not if our own calendar - if cal is PyCalendarTimezoneDatabase.sTimezoneDatabase.calendar: + if cal is tzdb.calendar: return # Merge each timezone from other calendar for tz in tzs: - PyCalendarTimezoneDatabase.sTimezoneDatabase.mergeTimezone(tz) - + tzdb.mergeTimezone(tz) + + def mergeTimezone(self, tz): """ If the supplied VTIMEZONE is not in our cache then store it in memory. """ - + if self.getTimezone(tz.getID()) is None: self.addTimezone(tz) - -PyCalendarTimezoneDatabase.sTimezoneDatabase = PyCalendarTimezoneDatabase() diff -Nru pycalendar-2.0~svn188/src/pycalendar/timezone.py pycalendar-2.0~svn13177/src/pycalendar/timezone.py --- pycalendar-2.0~svn188/src/pycalendar/timezone.py 2011-08-09 18:36:31.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/timezone.py 2014-04-03 02:27:44.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,18 +15,21 @@ ## from pycalendar import stringutils +from pycalendar.timezonedb import PyCalendarTimezoneDatabase class PyCalendarTimezone(object): """ Wrapper around a timezone specification. There are three options: - + UTC - when mUTC is True TZID - when mUTC is False and tzid is a str UTCOFFSET - when mUTC is False and tzid is an int """ - + + sDefaultTimezone = None + def __init__(self, utc=None, tzid=None): - + if utc is not None: self.mUTC = utc self.mTimezone = tzid @@ -36,17 +39,17 @@ else: self.mUTC = True self.mTimezone = None - + # Copy default timezone if it exists - from manager import PyCalendarManager - if PyCalendarManager.sICalendarManager is not None: - defaulttz = PyCalendarManager.sICalendarManager.getDefaultTimezone() - self.mUTC = defaulttz.mUTC - self.mTimezone = defaulttz.mTimezone + if PyCalendarTimezone.sDefaultTimezone is not None: + self.mUTC = PyCalendarTimezone.sDefaultTimezone.mUTC + self.mTimezone = PyCalendarTimezone.sDefaultTimezone.mTimezone + def duplicate(self): return PyCalendarTimezone(self.mUTC, self.mTimezone) + def equals(self, comp): # Always match if any one of them is 'floating' if self.floating() or comp.floating(): @@ -56,6 +59,7 @@ else: return self.mUTC or stringutils.compareStringsSafe(self.mTimezone, comp.mTimezone) + @staticmethod def same(utc1, tzid1, utc2, tzid2): # Always match if any one of them is 'floating' @@ -66,48 +70,53 @@ else: return utc1 or stringutils.compareStringsSafe(tzid1, tzid2) + @staticmethod def is_float(utc, tzid): return not utc and not tzid + def getUTC(self): return self.mUTC + def setUTC(self, utc): self.mUTC = utc + def getTimezoneID(self): return self.mTimezone + def setTimezoneID(self, tzid): self.mTimezone = tzid + def floating(self): return not self.mUTC and self.mTimezone is None + def hasTZID(self): return not self.mUTC and self.mTimezone is not None - def timeZoneSecondsOffset(self, dt): - from manager import PyCalendarManager - from timezonedb import PyCalendarTimezoneDatabase + + def timeZoneSecondsOffset(self, dt, relative_to_utc=False): if self.mUTC: return 0 elif self.mTimezone is None: - return PyCalendarTimezoneDatabase.getTimezoneOffsetSeconds(PyCalendarManager.sICalendarManager.getDefaultTimezone().getTimezoneID(), dt) + return PyCalendarTimezoneDatabase.getTimezoneOffsetSeconds(PyCalendarTimezone.sDefaultTimezone.getTimezoneID(), dt, relative_to_utc) elif isinstance(self.mTimezone, int): return self.mTimezone else: # Look up timezone and resolve date using default timezones - return PyCalendarTimezoneDatabase.getTimezoneOffsetSeconds(self.mTimezone, dt) + return PyCalendarTimezoneDatabase.getTimezoneOffsetSeconds(self.mTimezone, dt, relative_to_utc) + def timeZoneDescriptor(self, dt): - from manager import PyCalendarManager - from timezonedb import PyCalendarTimezoneDatabase if self.mUTC: return "(UTC)" elif self.mTimezone is None: - return PyCalendarTimezoneDatabase.getTimezoneDescriptor(PyCalendarManager.sICalendarManager.getDefaultTimezone().getTimezoneID(), dt) + return PyCalendarTimezoneDatabase.getTimezoneDescriptor(PyCalendarTimezone.sDefaultTimezone.getTimezoneID(), dt) elif isinstance(self.mTimezone, int): sign = "-" if self.mTimezone < 0 else "+" hours = abs(self.mTimezone) / 3600 @@ -116,4 +125,5 @@ else: # Look up timezone and resolve date using default timezones return PyCalendarTimezoneDatabase.getTimezoneDescriptor(self.mTimezone, dt) - \ No newline at end of file + +PyCalendarTimezone.sDefaultTimezone = PyCalendarTimezone() diff -Nru pycalendar-2.0~svn188/src/pycalendar/unknownvalue.py pycalendar-2.0~svn13177/src/pycalendar/unknownvalue.py --- pycalendar-2.0~svn188/src/pycalendar/unknownvalue.py 1970-01-01 00:00:00.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/unknownvalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -0,0 +1,28 @@ +## +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +## + +# iCalendar Unknown value - one whose default type we don't know about + +from pycalendar import xmldefs +from pycalendar.plaintextvalue import PyCalendarPlainTextValue +from pycalendar.value import PyCalendarValue + +class PyCalendarUnknownValue(PyCalendarPlainTextValue): + + def getType(self): + return PyCalendarUnknownValue.VALUETYPE_UNKNOWN + +PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_UNKNOWN, PyCalendarUnknownValue, xmldefs.value_unknown) diff -Nru pycalendar-2.0~svn188/src/pycalendar/urivalue.py pycalendar-2.0~svn13177/src/pycalendar/urivalue.py --- pycalendar-2.0~svn188/src/pycalendar/urivalue.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/urivalue.py 2013-04-03 03:23:23.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2013 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,15 +16,40 @@ # iCalendar URI value -from pycalendar import xmldefs +from pycalendar import xmldefs, utils from pycalendar.plaintextvalue import PyCalendarPlainTextValue from pycalendar.value import PyCalendarValue +from pycalendar.parser import ParserContext -class PyCalendarURIValue( PyCalendarPlainTextValue ): +class PyCalendarURIValue(PyCalendarPlainTextValue): def getType(self): return PyCalendarURIValue.VALUETYPE_URI -PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_URI, PyCalendarURIValue, xmldefs.value_uri) - \ No newline at end of file + def parse(self, data): + + if ParserContext.BACKSLASH_IN_URI_VALUE == ParserContext.PARSER_FIX: + # Decoding required + self.mValue = utils.decodeTextValue(data) + else: + # No decoding required + self.mValue = data + + + # os - StringIO object + def generate(self, os): + """ + Handle a client bug where it sometimes includes a \n in the value and we need + to make sure that gets encoded rather than included literally which would break syntax. + """ + if '\n' in self.mValue: + try: + # No encoding required + os.write(self.mValue.replace("\n", "\\n")) + except: + pass + else: + super(PyCalendarURIValue, self).generate(os) + +PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_URI, PyCalendarURIValue, xmldefs.value_uri) diff -Nru pycalendar-2.0~svn188/src/pycalendar/utcoffsetvalue.py pycalendar-2.0~svn13177/src/pycalendar/utcoffsetvalue.py --- pycalendar-2.0~svn188/src/pycalendar/utcoffsetvalue.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/utcoffsetvalue.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,18 +20,21 @@ from pycalendar import xmldefs from pycalendar.value import PyCalendarValue -class PyCalendarUTCOffsetValue( PyCalendarValue ): +class PyCalendarUTCOffsetValue(PyCalendarValue): - def __init__( self, value = 0 ): + def __init__(self, value=0): self.mValue = value + def duplicate(self): return PyCalendarUTCOffsetValue(self.mValue) - def getType( self ): + + def getType(self): return PyCalendarValue.VALUETYPE_UTC_OFFSET - def parse( self, data ): + + def parse(self, data): # Must be of specific lengths datalen = len(data) if datalen not in (5, 7): @@ -41,7 +44,7 @@ # Get sign if data[0] not in ('+', '-'): raise ValueError - plus = ( data[0] == '+' ) + plus = (data[0] == '+') # Get hours hours = int(data[1:3]) @@ -54,40 +57,41 @@ if datalen == 7 : secs = int(data[5:]) - self.mValue = ((hours * 60) + mins) * 60 + secs if not plus: self.mValue = -self.mValue - + + # os - StringIO object - def generate( self, os ): + def generate(self, os): try: abs_value = self.mValue if self.mValue < 0 : - os.write( "-" ) + os.write("-") abs_value = -self.mValue else: - os.write( "+" ) + os.write("+") secs = abs_value % 60 - mins = ( abs_value / 60 ) % 60 - hours = abs_value / ( 60 * 60 ) + mins = (abs_value / 60) % 60 + hours = abs_value / (60 * 60) - if ( hours < 10 ): - os.write( "0" ) - os.write( str( hours ) ) - if ( mins < 10 ): - os.write( "0" ) - os.write( str( mins ) ) - if ( secs != 0 ): - if ( secs < 10 ): - os.write( "0" ) - os.write( str( secs ) ) + if (hours < 10): + os.write("0") + os.write(str(hours)) + if (mins < 10): + os.write("0") + os.write(str(mins)) + if (secs != 0): + if (secs < 10): + os.write("0") + os.write(str(secs)) except: pass + def writeXML(self, node, namespace): - + os = StringIO.StringIO() self.generate(os) text = os.getvalue() @@ -96,11 +100,12 @@ value = self.getXMLNode(node, namespace) value.text = text - def getValue( self ): + + def getValue(self): return self.mValue - def setValue( self, value ): + + def setValue(self, value): self.mValue = value PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_UTC_OFFSET, PyCalendarUTCOffsetValue, xmldefs.value_utc_offset) - diff -Nru pycalendar-2.0~svn188/src/pycalendar/utils.py pycalendar-2.0~svn13177/src/pycalendar/utils.py --- pycalendar-2.0~svn188/src/pycalendar/utils.py 2011-08-09 18:36:00.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/utils.py 2013-04-05 19:40:56.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2013 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,7 +17,7 @@ from pycalendar.parser import ParserContext import cStringIO as StringIO -def readFoldedLine( ins, lines ): +def readFoldedLine(ins, lines): # If line2 already has data, transfer that into line1 if lines[1] is not None: @@ -42,7 +42,7 @@ except: lines[0] = None return False - + # Now loop looking ahead at the next line to see if it is folded while True: # Get next line @@ -79,25 +79,31 @@ return True -def find_first_of( text, tokens, offset ): + + +def find_first_of(text, tokens, offset): for ctr, c in enumerate(text[offset:]): if c in tokens: return offset + ctr return -1 + + def escapeTextValue(value): os = StringIO.StringIO() writeTextValue(os, value) return os.getvalue() -def writeTextValue( os, value ): + + +def writeTextValue(os, value): try: start_pos = 0 - end_pos = find_first_of( value, "\r\n;\\,", start_pos ) + end_pos = find_first_of(value, "\r\n;\\,", start_pos) if end_pos != -1: while True: # Write current segment - os.write( value[start_pos:end_pos] ) + os.write(value[start_pos:end_pos]) # Write escape os.write("\\") @@ -122,10 +128,12 @@ break else: os.write(value) - + except: pass - + + + def decodeTextValue(value): os = StringIO.StringIO() @@ -164,7 +172,7 @@ raise ValueError elif ParserContext.INVALID_COLON_ESCAPE_SEQUENCE == ParserContext.PARSER_FIX: os.write(':') - + # Other escaped chars normally not allowed elif ParserContext.INVALID_ESCAPE_SEQUENCES == ParserContext.PARSER_RAISE: raise ValueError @@ -186,8 +194,69 @@ return os.getvalue() + + +def encodeParameterValue(value): + """ + RFC6868 parameter encoding. + """ + + encoded = [] + last = '' + for c in value: + if c == '\r': + encoded.append('^') + encoded.append('n') + elif c == '\n': + if last != '\r': + encoded.append('^') + encoded.append('n') + elif c == '"': + encoded.append('^') + encoded.append('\'') + elif c == '^': + encoded.append('^') + encoded.append('^') + else: + encoded.append(c) + last = c + + return "".join(encoded) + + + +def decodeParameterValue(value): + """ + RFC6868 parameter decoding. + """ + + if value is None: + return None + decoded = [] + last = '' + for c in value: + if last == '^': + if c == 'n': + decoded.append('\n') + elif c == '\'': + decoded.append('"') + elif c == '^': + decoded.append('^') + c = '' + else: + decoded.append('^') + decoded.append(c) + elif c != '^': + decoded.append(c) + last = c + if last == '^': + decoded.append('^') + return "".join(decoded) + + + # vCard text list parsing/generation -def parseTextList(data, sep=';'): +def parseTextList(data, sep=';', always_list=False): """ Each element of the list has to be separately un-escaped """ @@ -201,10 +270,12 @@ else: item.append(s) pre_s = s - + results.append(decodeTextValue("".join(item))) - return tuple(results) if len(results) > 1 else (results[0] if len(results) else "") + return tuple(results) if len(results) > 1 or always_list else (results[0] if len(results) else "") + + def generateTextList(os, data, sep=';'): """ @@ -218,6 +289,8 @@ except: pass + + # vCard double-nested list parsing/generation def parseDoubleNestedList(data, maxsize): results = [] @@ -225,21 +298,21 @@ pre_s = '' for s in data: if s == ';' and pre_s != '\\': - + if len(items) > 1: results.append(tuple([decodeTextValue(item) for item in items])) elif len(items) == 1: results.append(decodeTextValue(items[0])) else: results.append("") - + items = [""] elif s == ',' and pre_s != '\\': items.append("") else: items[-1] += s pre_s = s - + if len(items) > 1: results.append(tuple([decodeTextValue(item) for item in items])) elif len(items) == 1: @@ -258,6 +331,8 @@ return tuple(results) + + def generateDoubleNestedList(os, data): try: def _writeElement(item): @@ -269,18 +344,18 @@ for bit in item[1:]: os.write(",") writeTextValue(os, bit) - + for item in data[:-1]: _writeElement(item) os.write(";") _writeElement(data[-1]) - + except: pass # Date/time calcs -days_in_month = ( 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ) -days_in_month_leap = ( 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ) +days_in_month = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) +days_in_month_leap = (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) def daysInMonth(month, year): # NB month is 1..12 so use dummy value at start of array to avoid index @@ -290,8 +365,8 @@ else: return days_in_month[month] -days_upto_month = ( 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 ) -days_upto_month_leap = ( 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 ) +days_upto_month = (0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334) +days_upto_month_leap = (0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335) def daysUptoMonth(month, year): # NB month is 1..12 so use dummy value at start of array to avoid index @@ -303,7 +378,7 @@ cachedLeapYears = {} def isLeapYear(year): - + try: return cachedLeapYears[year] except KeyError: @@ -316,7 +391,7 @@ cachedLeapDaysSince1970 = {} def leapDaysSince1970(year_offset): - + try: return cachedLeapDaysSince1970[year_offset] except KeyError: @@ -331,26 +406,37 @@ cachedLeapDaysSince1970[year_offset] = result return result + + # Packed date def packDate(year, month, day): return (year << 16) | (month << 8) | (day + 128) + + def unpackDate(data, unpacked): unpacked[0] = (data & 0xFFFF0000) >> 16 unpacked[1] = (data & 0x0000FF00) >> 8 unpacked[2] = (data & 0xFF) - 128 + + def unpackDateYear(data): return (data & 0xFFFF0000) >> 16 + + def unpackDateMonth(data): return (data & 0x0000FF00) >> 8 + + def unpackDateDay(data): return (data & 0xFF) - 128 -# Display elements + +# Display elements def getMonthTable(month, year, weekstart, table, today_index): from pycalendar.datetime import PyCalendarDateTime @@ -373,7 +459,7 @@ max_day = daysInMonth(month, year) # Fill up each row - for day in range( 1, max_day + 1): + for day in range(1, max_day + 1): # Insert new row if we are at the start of a row if (col == 0) or (day == 1): table.extend([0] * 7) @@ -401,7 +487,7 @@ # Check on today if (temp.getYear() == today.getYear()) and (temp.getMonth() == today.getMonth()) and (day == today.getDay()): today_index = [row, col] - + day += 1 col += 1 @@ -416,12 +502,14 @@ # Check on today if (temp.getYear() == today.getYear()) and (temp.getMonth() == today.getMonth()) and (day == today.getDay()): today_index = [0, back_col] - + back_col -= 1 day -= 1 return table, today_index + + def set_difference(v1, v2): if len(v1) == 0 or len(v2) == 0: return v1 @@ -429,4 +517,4 @@ s1 = set(v1) s2 = set(v2) s3 = s1.difference(s2) - return list(s3) \ No newline at end of file + return list(s3) diff -Nru pycalendar-2.0~svn188/src/pycalendar/valarm.py pycalendar-2.0~svn13177/src/pycalendar/valarm.py --- pycalendar-2.0~svn188/src/pycalendar/valarm.py 2011-11-18 22:06:33.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/valarm.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -69,20 +69,21 @@ def getType(self): return self.mType + class PyCalendarVAlarmAudio(PyCalendarVAlarmAction): propertyCardinality_1 = ( definitions.cICalProperty_ACTION, definitions.cICalProperty_TRIGGER, ) - + propertyCardinality_0_1 = ( definitions.cICalProperty_DURATION, definitions.cICalProperty_REPEAT, definitions.cICalProperty_ATTACH, definitions.cICalProperty_ACKNOWLEDGED, ) - + def __init__(self, speak=None): super(PyCalendarVAlarm.PyCalendarVAlarmAudio, self).__init__(type=definitions.eAction_VAlarm_Audio) self.mSpeakText = speak @@ -110,13 +111,14 @@ def getSpeakText(self): return self.mSpeakText + class PyCalendarVAlarmDisplay(PyCalendarVAlarmAction): propertyCardinality_1 = ( definitions.cICalProperty_ACTION, definitions.cICalProperty_TRIGGER, ) - + propertyCardinality_1_Fix_Empty = ( definitions.cICalProperty_DESCRIPTION, ) @@ -126,7 +128,7 @@ definitions.cICalProperty_REPEAT, definitions.cICalProperty_ACKNOWLEDGED, ) - + def __init__(self, description=None): super(PyCalendarVAlarm.PyCalendarVAlarmDisplay, self).__init__(type=definitions.eAction_VAlarm_Display) self.mDescription = description @@ -151,13 +153,14 @@ def getDescription(self): return self.mDescription + class PyCalendarVAlarmEmail(PyCalendarVAlarmAction): propertyCardinality_1 = ( definitions.cICalProperty_ACTION, definitions.cICalProperty_TRIGGER, ) - + propertyCardinality_1_Fix_Empty = ( definitions.cICalProperty_DESCRIPTION, definitions.cICalProperty_SUMMARY, @@ -225,25 +228,27 @@ def getAttendees(self): return self.mAttendees + class PyCalendarVAlarmUnknown(PyCalendarVAlarmAction): propertyCardinality_1 = ( definitions.cICalProperty_ACTION, definitions.cICalProperty_TRIGGER, ) - + propertyCardinality_0_1 = ( definitions.cICalProperty_DURATION, definitions.cICalProperty_REPEAT, definitions.cICalProperty_ACKNOWLEDGED, ) - + def __init__(self): super(PyCalendarVAlarm.PyCalendarVAlarmUnknown, self).__init__(type=definitions.eAction_VAlarm_Unknown) def duplicate(self): return PyCalendarVAlarm.PyCalendarVAlarmUnknown() + class PyCalendarVAlarmURI(PyCalendarVAlarmAction): propertyCardinality_1 = ( @@ -251,7 +256,7 @@ definitions.cICalProperty_TRIGGER, definitions.cICalProperty_URL, ) - + propertyCardinality_0_1 = ( definitions.cICalProperty_DURATION, definitions.cICalProperty_REPEAT, @@ -282,6 +287,7 @@ def getURI(self): return self.mURI + class PyCalendarVAlarmNone(PyCalendarVAlarmAction): propertyCardinality_1 = ( @@ -294,11 +300,11 @@ def duplicate(self): return PyCalendarVAlarm.PyCalendarVAlarmNone() + def getMimeComponentName(self): # Cannot be sent as a separate MIME object return None - sActionToAlarmMap = { definitions.eAction_VAlarm_Audio: PyCalendarVAlarmAudio, definitions.eAction_VAlarm_Display: PyCalendarVAlarmDisplay, @@ -310,7 +316,7 @@ propertyValueChecks = ICALENDAR_VALUE_CHECKS def __init__(self, parent=None): - + super(PyCalendarVAlarm, self).__init__(parent=parent) self.mAction = definitions.eAction_VAlarm_Display @@ -337,6 +343,7 @@ # Create action data self.mActionData = PyCalendarVAlarm.PyCalendarVAlarmDisplay("") + def duplicate(self, parent=None): other = super(PyCalendarVAlarm, self).duplicate(parent=parent) other.mAction = self.mAction @@ -358,33 +365,43 @@ other.mActionData = self.mActionData.duplicate() return other + def getType(self): return definitions.cICalComponent_VALARM + def getAction(self): return self.mAction + def getActionData(self): return self.mActionData + def isTriggerAbsolute(self): return self.mTriggerAbsolute + def getTriggerOn(self): return self.mTriggerOn + def getTriggerDuration(self): return self.mTriggerBy - + + def isTriggerOnStart(self): return self.mTriggerOnStart + def getRepeats(self): return self.mRepeats + def getInterval(self): return self.mRepeatInterval + def added(self): # Added to calendar so add to calendar notifier # calstore::CCalendarNotifier::sCalendarNotifier.AddAlarm(this) @@ -392,6 +409,7 @@ # Do inherited super(PyCalendarVAlarm, self).added() + def removed(self): # Removed from calendar so add to calendar notifier # calstore::CCalendarNotifier::sCalendarNotifier.RemoveAlarm(this) @@ -399,6 +417,7 @@ # Do inherited super(PyCalendarVAlarm, self).removed() + def changed(self): # Always force recalc of trigger status self.mStatusInit = False @@ -410,6 +429,7 @@ # do top-level component changes # super.changed() + def finalise(self): # Do inherited super(PyCalendarVAlarm, self).finalise() @@ -453,6 +473,9 @@ if temp is not None: self.mRepeatInterval = temp + # Set a map key for sorting + self.mMapKey = "%s:%s" % (self.mAction, self.mTriggerOn if self.mTriggerAbsolute else self.mTriggerBy,) + # Alarm status - private to Mulberry status = self.loadValueString(definitions.cICalProperty_ALARM_X_ALARMSTATUS) if status is not None: @@ -470,13 +493,14 @@ if temp is not None: self.mLastTrigger = temp + def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems, else raise. If loggedProblems is not None it must be a C{list} and problem descriptions are appended - to that. + to that. """ - + # Validate using action specific constraints self.propertyCardinality_1 = self.mActionData.propertyCardinality_1 self.propertyCardinality_1_Fix_Empty = self.mActionData.propertyCardinality_1_Fix_Empty @@ -494,9 +518,10 @@ definitions.cICalProperty_REPEAT, ) unfixed.append(logProblem) - + return fixed, unfixed + def editStatus(self, status): # Remove existing self.removeProperties(definitions.cICalProperty_ALARM_X_ALARMSTATUS) @@ -514,6 +539,7 @@ status_txt = definitions.cICalProperty_ALARM_X_ALARMSTATUS_DISABLED self.addProperty(PyCalendarProperty(definitions.cICalProperty_ALARM_X_ALARMSTATUS, status_txt)) + def editAction(self, action, data): # Remove existing self.removeProperties(definitions.cICalProperty_ACTION) @@ -532,6 +558,7 @@ self.mActionData.add(self) + def editTriggerOn(self, dt): # Remove existing self.removeProperties(definitions.cICalProperty_TRIGGER) @@ -544,6 +571,7 @@ prop = PyCalendarProperty(definitions.cICalProperty_TRIGGER, dt) self.addProperty(prop) + def editTriggerBy(self, duration, trigger_start): # Remove existing self.removeProperties(definitions.cICalProperty_TRIGGER) @@ -561,6 +589,7 @@ prop.addAttribute(attr) self.addProperty(prop) + def editRepeats(self, repeat, interval): # Remove existing self.removeProperties(definitions.cICalProperty_REPEAT) @@ -575,14 +604,17 @@ self.addProperty(PyCalendarProperty(definitions.cICalProperty_REPEAT, repeat)) self.addProperty(PyCalendarProperty(definitions.cICalProperty_DURATION, interval)) + def getAlarmStatus(self): return self.mAlarmStatus + def getNextTrigger(self, dt): if not self.mStatusInit: self.initNextTrigger() dt.copy(self.mNextTrigger) + def alarmTriggered(self, dt): # Remove existing self.removeProperties(definitions.cICalProperty_ALARM_X_LASTTRIGGER) @@ -613,12 +645,14 @@ # Now update dt to the next alarm time return self.mAlarmStatus == definitions.eAlarm_Status_Pending + def loadAction(self): # Delete current one self.mActionData = None self.mActionData = PyCalendarVAlarm.sActionToAlarmMap.get(self.mAction, PyCalendarVAlarm.PyCalendarVAlarmUnknown)() self.mActionData.load(self) + def initNextTrigger(self): # Do not bother if its completed if self.mAlarmStatus == definitions.eAlarm_Status_Completed: @@ -654,6 +688,7 @@ self.mNextTrigger = trigger + def getFirstTrigger(self, dt): # If absolute trigger, use that if self.isTriggerAbsolute(): diff -Nru pycalendar-2.0~svn188/src/pycalendar/validation.py pycalendar-2.0~svn13177/src/pycalendar/validation.py --- pycalendar-2.0~svn188/src/pycalendar/validation.py 2011-06-30 16:03:45.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/validation.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -27,8 +27,10 @@ newfunc.keywords = keywords return newfunc + + class PropertyValueChecks(object): - + @staticmethod def stringValue(text, property): @@ -36,36 +38,38 @@ if value and isinstance(value, PyCalendarPlainTextValue): value = value.getValue() return value.lower() == text.lower() - + return False - + + @staticmethod def alwaysUTC(property): - + value = property.getDateTimeValue() if value: value = value.getValue() return value.utc() - + return False + @staticmethod def numericRange(low, high, property): - + value = property.getIntegerValue() if value: value = value.getValue() return value >= low and value <= high - + return False + @staticmethod def positiveIntegerOrZero(property): - + value = property.getIntegerValue() if value: value = value.getValue() return value >= 0 - + return False - \ No newline at end of file diff -Nru pycalendar-2.0~svn188/src/pycalendar/validator.py pycalendar-2.0~svn13177/src/pycalendar/validator.py --- pycalendar-2.0~svn188/src/pycalendar/validator.py 1970-01-01 00:00:00.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/validator.py 2013-03-14 14:03:20.000000000 +0000 @@ -0,0 +1,67 @@ +#!/usr/bin/env python +## +# Copyright (c) 2012 Cyrus Daboo. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +## + +from pycalendar.calendar import PyCalendar +from pycalendar.exceptions import PyCalendarError +from pycalendar.parser import ParserContext +from pycalendar.vcard.card import Card +import os +import sys + +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 = PyCalendar.parseText(data) + except PyCalendarError, e: + print "Failed to parse iCalendar: %r" % (e,) + sys.exit(1) + elif data.find("BEGIN:VCARD") != -1: + try: + cal = Card.parseText(data) + except PyCalendarError, e: + print "Failed to parse vCard: %r" % (e,) + 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: %s" % (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 %d at position %d" % (ord(i), ctr,) + + +if __name__ == '__main__': + + fname = os.path.expanduser(sys.argv[1]) + validate(fname) diff -Nru pycalendar-2.0~svn188/src/pycalendar/value.py pycalendar-2.0~svn13177/src/pycalendar/value.py --- pycalendar-2.0~svn188/src/pycalendar/value.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/value.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -40,29 +40,38 @@ VALUETYPE_REQUEST_STATUS, VALUETYPE_TEXT, VALUETYPE_TIME, + VALUETYPE_UNKNOWN, VALUETYPE_URI, VALUETYPE_UTC_OFFSET, VALUETYPE_VCARD, VALUETYPE_MULTIVALUE, VALUETYPE_XNAME, - ) = range(22) - + ) = range(23) + _typeMap = {} _xmlMap = {} - + + def __hash__(self): return hash((self.getType(), self.getValue())) - def __ne__(self, other): return not self.__eq__(other) + + def __ne__(self, other): + return not self.__eq__(other) + + def __eq__(self, other): - if not isinstance(other, PyCalendarValue): return False + if not isinstance(other, PyCalendarValue): + return False return self.getType() == other.getType() and self.getValue() == other.getValue() + @classmethod def registerType(clz, type, cls, xmlNode): clz._typeMap[type] = cls clz._xmlMap[type] = xmlNode - + + @classmethod def createFromType(clz, type): # Create the type @@ -70,23 +79,28 @@ if created: return created() else: - return clz._typeMap.get("DUMMY")(type) - + return clz._typeMap.get(PyCalendarValue.VALUETYPE_UNKNOWN)(type) + + def getType(self): raise NotImplementedError + def getRealType(self): return self.getType() - def getValue( self ): + + def getValue(self): raise NotImplementedError - def setValue( self, value ): + + def setValue(self, value): raise NotImplementedError + def writeXML(self, node, namespace): raise NotImplementedError + def getXMLNode(self, node, namespace): return XML.SubElement(node, xmldefs.makeTag(namespace, self._xmlMap[self.getType()])) - diff -Nru pycalendar-2.0~svn188/src/pycalendar/valueutils.py pycalendar-2.0~svn13177/src/pycalendar/valueutils.py --- pycalendar-2.0~svn188/src/pycalendar/valueutils.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/valueutils.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,22 +23,27 @@ def __str__(self): return self.getText() + @classmethod def parseText(cls, data): value = cls() value.parse(data) return value + def parse(self, data): raise NotImplementedError - + + def generate(self, os): raise NotImplementedError - + + def getText(self): os = StringIO() self.generate(os) return os.getvalue() + def writeXML(self, node, namespace): raise NotImplementedError diff -Nru pycalendar-2.0~svn188/src/pycalendar/vavailability.py pycalendar-2.0~svn13177/src/pycalendar/vavailability.py --- pycalendar-2.0~svn188/src/pycalendar/vavailability.py 2011-06-30 16:03:45.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vavailability.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -47,25 +47,30 @@ def __init__(self, parent=None): super(PyCalendarVAvailability, self).__init__(parent=parent) + def duplicate(self, parent=None): return super(PyCalendarVAvailability, self).duplicate(parent=parent) + def getType(self): return definitions.cICalComponent_VAVAILABILITY + def getMimeComponentName(self): return itipdefinitions.cICalMIMEComponent_VAVAILABILITY + def finalise(self): super(PyCalendarVAvailability, self).finalise() + def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems, else raise. If loggedProblems is not None it must be a C{list} and problem descriptions are appended - to that. + to that. """ - + fixed, unfixed = super(PyCalendarVAvailability, self).validate(doFix) # Extra constraint: only one of DTEND or DURATION @@ -81,9 +86,10 @@ fixed.append(logProblem) else: unfixed.append(logProblem) - + return fixed, unfixed - + + def addComponent(self, comp): # We can embed the available components only if comp.getType() == definitions.cICalComponent_AVAILABLE: @@ -91,6 +97,7 @@ else: raise ValueError + def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, diff -Nru pycalendar-2.0~svn188/src/pycalendar/vcard/card.py pycalendar-2.0~svn13177/src/pycalendar/vcard/card.py --- pycalendar-2.0~svn188/src/pycalendar/vcard/card.py 2011-06-30 16:03:45.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vcard/card.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,12 +16,12 @@ from cStringIO import StringIO from pycalendar.componentbase import PyCalendarComponentBase -from pycalendar.exceptions import PyCalendarInvalidData,\ +from pycalendar.exceptions import PyCalendarInvalidData, \ PyCalendarValidationError from pycalendar.parser import ParserContext from pycalendar.utils import readFoldedLine from pycalendar.vcard import definitions -from pycalendar.vcard.definitions import VCARD, Property_VERSION,\ +from pycalendar.vcard.definitions import VCARD, Property_VERSION, \ Property_PRODID, Property_UID from pycalendar.vcard.property import Property from pycalendar.vcard.validation import VCARD_VALUE_CHECKS @@ -35,6 +35,7 @@ def setPRODID(prodid): Card.sProdID = prodid + @staticmethod def setDomain(domain): Card.sDomain = domain @@ -62,16 +63,20 @@ if add_defaults: self.addDefaultProperties() - + + def duplicate(self): return super(Card, self).duplicate() + def getType(self): return VCARD + def finalise(self): pass + def validate(self, doFix=False, doRaise=False): """ Validate the data in this component and optionally fix any problems. Return @@ -79,13 +84,14 @@ second problems that were not fixed. Caller can then decide what to do with unfixed issues. """ - + # Optional raise behavior fixed, unfixed = super(Card, self).validate(doFix) if doRaise and unfixed: raise PyCalendarValidationError(";".join(unfixed)) return fixed, unfixed + def sortedPropertyKeyOrder(self): return ( Property_VERSION, @@ -93,23 +99,24 @@ Property_UID, ) + @staticmethod def parseMultiple(ins): results = [] - + card = Card(add_defaults=False) - + LOOK_FOR_VCARD = 0 GET_PROPERTY = 1 - + state = LOOK_FOR_VCARD - + # Get lines looking for start of calendar lines = [None, None] - + while readFoldedLine(ins, lines): - + line = lines[0] if state == LOOK_FOR_VCARD: @@ -123,7 +130,7 @@ # Raise if requested, otherwise just ignore if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE: raise PyCalendarInvalidData("vCard data has blank lines") - + # Unrecognized data else: raise PyCalendarInvalidData("vCard data not recognized", line) @@ -141,7 +148,7 @@ raise PyCalendarInvalidData("vCard missing VERSION", "") results.append(card) - + # Change state card = Card(add_defaults=False) state = LOOK_FOR_VCARD @@ -164,38 +171,40 @@ raise PyCalendarInvalidData("Invalid property", str(prop)) else: card.addProperty(prop) - + # Check for truncated data if state != LOOK_FOR_VCARD: raise PyCalendarInvalidData("vCard data not complete") return results - + + @staticmethod def parseText(data): - + cal = Card(add_defaults=False) if cal.parse(StringIO(data)): return cal else: return None + def parse(self, ins): result = False - + self.setProperties({}) - + LOOK_FOR_VCARD = 0 GET_PROPERTY = 1 - + state = LOOK_FOR_VCARD - + # Get lines looking for start of calendar lines = [None, None] - + while readFoldedLine(ins, lines): - + line = lines[0] if state == LOOK_FOR_VCARD: @@ -203,7 +212,7 @@ if line == self.getBeginDelimiter(): # Next state state = GET_PROPERTY - + # Indicate success at this point result = True @@ -212,7 +221,7 @@ # Raise if requested, otherwise just ignore if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE: raise PyCalendarInvalidData("vCard data has blank lines") - + # Unrecognized data else: raise PyCalendarInvalidData("vCard data not recognized", line) @@ -224,7 +233,7 @@ # Finalise the current calendar self.finalise() - + # Change state state = LOOK_FOR_VCARD @@ -241,7 +250,7 @@ prop = Property() try: if prop.parse(line): - + # Check for valid property if not self.validProperty(prop): raise PyCalendarInvalidData("Invalid property", str(prop)) @@ -249,21 +258,23 @@ self.addProperty(prop) except IndexError: print line - + # Check for truncated data if state != LOOK_FOR_VCARD: raise PyCalendarInvalidData("vCard data not complete", "") - + # Validate some things if result and not self.hasProperty("VERSION"): raise PyCalendarInvalidData("vCard missing VERSION", "") return result - + + def addDefaultProperties(self): self.addProperty(Property(definitions.Property_PRODID, Card.sProdID)) self.addProperty(Property(definitions.Property_VERSION, "3.0")) - + + def validProperty(self, prop): if prop.getName() == definitions.Property_VERSION: diff -Nru pycalendar-2.0~svn188/src/pycalendar/vcard/definitions.py pycalendar-2.0~svn13177/src/pycalendar/vcard/definitions.py --- pycalendar-2.0~svn188/src/pycalendar/vcard/definitions.py 2011-03-23 17:53:31.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vcard/definitions.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff -Nru pycalendar-2.0~svn188/src/pycalendar/vcard/__init__.py pycalendar-2.0~svn13177/src/pycalendar/vcard/__init__.py --- pycalendar-2.0~svn188/src/pycalendar/vcard/__init__.py 2011-03-23 17:53:31.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vcard/__init__.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,16 +1,15 @@ # -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## - diff -Nru pycalendar-2.0~svn188/src/pycalendar/vcard/property.py pycalendar-2.0~svn13177/src/pycalendar/vcard/property.py --- pycalendar-2.0~svn188/src/pycalendar/vcard/property.py 2011-04-27 15:38:06.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vcard/property.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,7 +20,6 @@ from pycalendar.attribute import PyCalendarAttribute from pycalendar.datetime import PyCalendarDateTime from pycalendar.datetimevalue import PyCalendarDateTimeValue -from pycalendar.dummyvalue import PyCalendarDummyValue from pycalendar.exceptions import PyCalendarInvalidProperty from pycalendar.integervalue import PyCalendarIntegerValue from pycalendar.multivalue import PyCalendarMultiValue @@ -29,7 +28,9 @@ from pycalendar.orgvalue import OrgValue from pycalendar.parser import ParserContext from pycalendar.plaintextvalue import PyCalendarPlainTextValue +from pycalendar.unknownvalue import PyCalendarUnknownValue from pycalendar.utcoffsetvalue import PyCalendarUTCOffsetValue +from pycalendar.utils import decodeParameterValue from pycalendar.value import PyCalendarValue from pycalendar.vcard import definitions import cStringIO as StringIO @@ -40,41 +41,41 @@ class Property(object): sDefaultValueTypeMap = { - + # 2425 Properties definitions.Property_SOURCE : PyCalendarValue.VALUETYPE_URI, definitions.Property_NAME : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_PROFILE : PyCalendarValue.VALUETYPE_TEXT, - + # 2426 vCard Properties - + # 2426 Section 3.1 definitions.Property_FN : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_N : PyCalendarValue.VALUETYPE_N, definitions.Property_NICKNAME : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_PHOTO : PyCalendarValue.VALUETYPE_BINARY, definitions.Property_BDAY : PyCalendarValue.VALUETYPE_DATE, - + # 2426 Section 3.2 definitions.Property_ADR : PyCalendarValue.VALUETYPE_ADR, definitions.Property_LABEL : PyCalendarValue.VALUETYPE_TEXT, - + # 2426 Section 3.3 definitions.Property_TEL : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_EMAIL : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_MAILER : PyCalendarValue.VALUETYPE_TEXT, - + # 2426 Section 3.4 definitions.Property_TZ : PyCalendarValue.VALUETYPE_UTC_OFFSET, definitions.Property_GEO : PyCalendarValue.VALUETYPE_GEO, - + # 2426 Section 3.5 definitions.Property_TITLE : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_ROLE : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_LOGO : PyCalendarValue.VALUETYPE_BINARY, definitions.Property_AGENT : PyCalendarValue.VALUETYPE_VCARD, definitions.Property_ORG : PyCalendarValue.VALUETYPE_ORG, - + # 2426 Section 3.6 definitions.Property_CATEGORIES : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_NOTE : PyCalendarValue.VALUETYPE_TEXT, @@ -85,12 +86,12 @@ definitions.Property_UID : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_URL : PyCalendarValue.VALUETYPE_URI, definitions.Property_VERSION : PyCalendarValue.VALUETYPE_TEXT, - + # 2426 Section 3.7 definitions.Property_CLASS : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_KEY : PyCalendarValue.VALUETYPE_BINARY, } - + sValueTypeMap = { definitions.Value_BINARY : PyCalendarValue.VALUETYPE_BINARY, definitions.Value_BOOLEAN : PyCalendarValue.VALUETYPE_BOOLEAN, @@ -168,6 +169,7 @@ elif isinstance(value, PyCalendarUTCOffsetValue): self._init_attr_value_utcoffset(value) + def duplicate(self): other = Property(self.mGroup, self.mName) for attrname, attrs in self.mAttributes.items(): @@ -176,6 +178,7 @@ return other + def __hash__(self): return hash(( self.mName, @@ -183,9 +186,14 @@ self.mValue, )) - def __ne__(self, other): return not self.__eq__(other) + + def __ne__(self, other): + return not self.__eq__(other) + + def __eq__(self, other): - if not isinstance(other, Property): return False + if not isinstance(other, Property): + return False return ( self.mGroup == self.mGroup and self.mName == other.mName and @@ -193,49 +201,64 @@ self.mAttributes == other.mAttributes ) + def __repr__(self): return "vCard Property: %s" % (self.getText(),) + def __str__(self): return self.getText() + def getGroup(self): return self.mGroup + def setGroup(self, group): self.mGroup = group + def getName(self): return self.mName + def setName(self, name): self.mName = name + def getAttributes(self): return self.mAttributes + def setAttributes(self, attributes): - self.mAttributes = dict([(k.upper(), v) for k,v in attributes.iteritems()]) + self.mAttributes = dict([(k.upper(), v) for k, v in attributes.iteritems()]) + def hasAttribute(self, attr): - return self.mAttributes.has_key(attr.upper()) + return attr.upper() in self.mAttributes + def getAttributeValue(self, attr): return self.mAttributes[attr.upper()][0].getFirstValue() + def addAttribute(self, attr): self.mAttributes.setdefault(attr.getName().upper(), []).append(attr) + def replaceAttribute(self, attr): self.mAttributes[attr.getName().upper()] = [attr] + def removeAttributes(self, attr): - if self.mAttributes.has_key(attr.upper()): + if attr.upper() in self.mAttributes: del self.mAttributes[attr.upper()] + def getValue(self): return self.mValue + def parse(self, data): # Look for attribute or value delimiter prop_name, txt = stringutils.strduptokenstr(data, ";:") @@ -251,24 +274,24 @@ else: # We have the name self.mName = prop_name - + # Now loop getting data try: stripValueSpaces = False # Fix for AB.app base PHOTO properties that use two spaces at start of line while txt: if txt[0] == ';': # Parse attribute - + # Move past delimiter txt = txt[1:] - + # Get quoted string or token - in iCalendar we only look for "=" here # but for "broken" vCard BASE64 property we need to also terminate on - # ":;" + # ":;" attribute_name, txt = stringutils.strduptokenstr(txt, "=:;") if attribute_name is None: raise PyCalendarInvalidProperty("Invalid property", data) - + if txt[0] != "=": # Deal with parameters without values if ParserContext.VCARD_2_NO_PARAMETER_VALUES == ParserContext.PARSER_RAISE: @@ -287,19 +310,19 @@ attribute_value, txt = stringutils.strduptokenstr(txt, ":;,") if attribute_value is None: raise PyCalendarInvalidProperty("Invalid property", data) - - # Now add attribute value + + # Now add attribute value (decode ^-escaping) if attribute_name is not None: - attrvalue = PyCalendarAttribute(name = attribute_name, value=attribute_value) + attrvalue = PyCalendarAttribute(name=attribute_name, value=decodeParameterValue(attribute_value)) self.mAttributes.setdefault(attribute_name.upper(), []).append(attrvalue) - + # Look for additional values while txt[0] == ',': txt = txt[1:] attribute_value2, txt = stringutils.strduptokenstr(txt, ":;,") if attribute_value2 is None: raise PyCalendarInvalidProperty("Invalid property", data) - attrvalue.addValue(attribute_value2) + attrvalue.addValue(decodeParameterValue(attribute_value2)) elif txt[0] == ':': txt = txt[1:] if stripValueSpaces: @@ -309,11 +332,11 @@ except IndexError: raise PyCalendarInvalidProperty("Invalid property", data) - + # We must have a value of some kind if self.mValue is None: raise PyCalendarInvalidProperty("Invalid property", data) - + return True @@ -322,6 +345,7 @@ self.generate(os) return os.getvalue() + def generate(self, os): # Write it out always with value @@ -329,12 +353,13 @@ def generateFiltered(self, os, filter): - + # Check for property in filter and whether value is written out test, novalue = filter.testPropertyValue(self.mName.upper()) if test: self.generateValue(os, novalue) + # Write out the actual property, possibly skipping the value def generateValue(self, os, novalue): @@ -357,13 +382,13 @@ if self.mName.upper() == "PHOTO" and self.mValue.getType() == PyCalendarValue.VALUETYPE_BINARY: # Handle AB.app PHOTO values sout.write("\r\n") - + value = self.mValue.getText() value_len = len(value) offset = 0 while(value_len > 72): sout.write(" ") - sout.write(value[offset:offset+72]) + sout.write(value[offset:offset + 72]) sout.write("\r\n") value_len -= 72 offset += 72 @@ -373,11 +398,11 @@ else: if self.mValue and not novalue: self.mValue.generate(sout) - + # Get string text temp = sout.getvalue() sout.close() - + # Look for line length exceed if len(temp) < 75: os.write(temp) @@ -398,22 +423,24 @@ while (temp[offset] > 0x7F) and ((ord(temp[offset]) & 0xC0) == 0x80): # Step back until we have a valid char offset -= 1 - + line = temp[start:offset] os.write(line) os.write("\r\n ") lineWrap = 73 # We are now adding a space at the start written += offset - start start = offset - + os.write("\r\n") - + + def _init_PyCalendarProperty(self): self.mGroup = None self.mName = "" self.mAttributes = {} self.mValue = None + def createValue(self, data): # Tidy first self.mValue = None @@ -422,7 +449,7 @@ valueType = Property.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarValue.VALUETYPE_TEXT) # Check whether custom value is set - if self.mAttributes.has_key(definitions.Parameter_VALUE): + if definitions.Parameter_VALUE in self.mAttributes: attr = self.getAttributeValue(definitions.Parameter_VALUE) if attr != definitions.Value_TEXT or self.mName.upper() not in Property.sTextVariants: valueType = Property.sValueTypeMap.get(attr, valueType) @@ -444,15 +471,16 @@ except ValueError: raise PyCalendarInvalidProperty("Invalid property value", data) + def setValue(self, value): # Tidy first self.mValue = None # Get value type from property name - valueType = Property.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarDummyValue) + valueType = Property.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarUnknownValue) # Check whether custom value is set - if self.mAttributes.has_key(definitions.Parameter_VALUE): + if definitions.Parameter_VALUE in self.mAttributes: attr = self.getAttributeValue(definitions.Parameter_VALUE) if attr != definitions.Value_TEXT or self.mName.upper() not in Property.sTextVariants: valueType = Property.sValueTypeMap.get(attr, valueType) @@ -466,22 +494,24 @@ self.mValue.setValue(value) + def setupValueAttribute(self): - if self.mAttributes.has_key(definitions.Parameter_VALUE): + if definitions.Parameter_VALUE in self.mAttributes: del self.mAttributes[definitions.Parameter_VALUE] # Only if we have a value right now if self.mValue is None: return - # See if current type is default for this property + # See if current type is default for this property. If there is no mapping available, + # then always add VALUE if it is not TEXT. default_type = Property.sDefaultValueTypeMap.get(self.mName.upper()) - if default_type is not None: - actual_type = self.mValue.getType() - if default_type != actual_type: - actual_value = self.sTypeValueMap.get(actual_type) - if actual_value is not None: - self.mAttributes.setdefault(definitions.Parameter_VALUE, []).append(PyCalendarAttribute(name=definitions.Parameter_VALUE, value=actual_value)) + actual_type = self.mValue.getType() + if default_type is None or default_type != actual_type: + actual_value = self.sTypeValueMap.get(actual_type) + if actual_value is not None and (default_type is not None or actual_type != PyCalendarValue.VALUETYPE_TEXT): + self.mAttributes.setdefault(definitions.Parameter_VALUE, []).append(PyCalendarAttribute(name=definitions.Parameter_VALUE, value=actual_value)) + # Creation def _init_attr_value_int(self, ival): @@ -495,12 +525,13 @@ def _init_attr_value_text(self, txt, value_type): # Value self.mValue = PyCalendarValue.createFromType(value_type) - if isinstance(self.mValue, PyCalendarPlainTextValue) or isinstance(self.mValue, PyCalendarDummyValue): + if isinstance(self.mValue, PyCalendarPlainTextValue) or isinstance(self.mValue, PyCalendarUnknownValue): self.mValue.setValue(txt) # Attributes self.setupValueAttribute() + def _init_attr_value_adr(self, reqstatus): # Value self.mValue = AdrValue(reqstatus) @@ -508,6 +539,7 @@ # Attributes self.setupValueAttribute() + def _init_attr_value_n(self, reqstatus): # Value self.mValue = NValue(reqstatus) @@ -515,6 +547,7 @@ # Attributes self.setupValueAttribute() + def _init_attr_value_org(self, reqstatus): # Value self.mValue = OrgValue(reqstatus) @@ -522,13 +555,15 @@ # Attributes self.setupValueAttribute() + def _init_attr_value_datetime(self, dt): # Value self.mValue = PyCalendarDateTimeValue(value=dt) # Attributes self.setupValueAttribute() - + + def _init_attr_value_utcoffset(self, utcoffset): # Value self.mValue = PyCalendarUTCOffsetValue() @@ -536,4 +571,3 @@ # Attributes self.setupValueAttribute() - diff -Nru pycalendar-2.0~svn188/src/pycalendar/vcard/tests/__init__.py pycalendar-2.0~svn13177/src/pycalendar/vcard/tests/__init__.py --- pycalendar-2.0~svn188/src/pycalendar/vcard/tests/__init__.py 2011-03-23 17:53:31.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vcard/tests/__init__.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,16 +1,15 @@ # -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## - diff -Nru pycalendar-2.0~svn188/src/pycalendar/vcard/tests/test_card.py pycalendar-2.0~svn13177/src/pycalendar/vcard/tests/test_card.py --- pycalendar-2.0~svn188/src/pycalendar/vcard/tests/test_card.py 2011-05-02 15:48:09.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vcard/tests/test_card.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,7 +23,7 @@ import unittest class TestCard(unittest.TestCase): - + data = ( ( """BEGIN:VCARD @@ -155,20 +155,53 @@ END:VCARD """.replace("\n", "\r\n"), ), + ( +"""BEGIN:VCARD +VERSION:3.0 +N:Thompson;Default;;; +FN:Default Thompson +EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com +TEL;type=WORK;type=pref:1-555-555-5555 +TEL;type=CELL:1-444-444-4444 +item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA +item1.X-ABADR:us +UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson +X-APPLE-STRUCTURED-LOCATION;VALUE=URI:geo:123.123,123.123 +X-Test:Some\, text. +END:VCARD +""".replace("\n", "\r\n"), +"""BEGIN:VCARD +VERSION:3.0 +UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson +item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U + SA +EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com +FN:Default Thompson +N:Thompson;Default;;; +TEL;type=WORK;type=pref:1-555-555-5555 +TEL;type=CELL:1-444-444-4444 +item1.X-ABADR:us +X-APPLE-STRUCTURED-LOCATION;VALUE=URI:geo:123.123,123.123 +X-Test:Some\, text. +END:VCARD +""".replace("\n", "\r\n"), + ), ) + def testRoundtrip(self): + def _doRoundtrip(caldata, resultdata=None): test1 = resultdata if resultdata is not None else caldata card = Card() card.parse(StringIO.StringIO(caldata)) - + s = StringIO.StringIO() card.generate(s) test2 = s.getvalue() - + self.assertEqual( test1, test2, @@ -178,13 +211,15 @@ for item, result in self.data: _doRoundtrip(item, result) + def testRoundtripDuplicate(self): + def _doDuplicateRoundtrip(caldata, result): card = Card() card.parse(StringIO.StringIO(caldata)) card = card.duplicate() - + s = StringIO.StringIO() card.generate(s) test = s.getvalue() @@ -193,8 +228,10 @@ for item, result in self.data: _doDuplicateRoundtrip(item, result) + def testEquality(self): + def _doEquality(caldata): card1 = Card() card1.parse(StringIO.StringIO(caldata)) @@ -204,6 +241,7 @@ self.assertEqual(card1, card2, "\n".join(difflib.unified_diff(str(card1).splitlines(), str(card2).splitlines()))) + def _doNonEquality(caldata): card1 = Card() card1.parse(StringIO.StringIO(caldata)) @@ -218,8 +256,9 @@ _doEquality(item) _doNonEquality(item) + def testMultiple(self): - + data = ( ( """BEGIN:VCARD @@ -303,14 +342,15 @@ ) for item, results in data: - + cards = Card.parseMultiple(StringIO.StringIO(item)) self.assertEqual(len(cards), len(results)) for card, result in zip(cards, results): self.assertEqual(str(card), result, "\n".join(difflib.unified_diff(str(card).splitlines(), result.splitlines()))) - + + def testABapp(self): - + data = """BEGIN:VCARD VERSION:3.0 N:Card;Test;;; @@ -328,7 +368,7 @@ REV:2011-03-23T20:20:04Z END:VCARD """.replace("\n", "\r\n") - + result = """BEGIN:VCARD VERSION:3.0 UID:128ad7ee-a656-4773-95ce-f07f77e8cc23 @@ -350,8 +390,9 @@ card = Card.parseText(data) self.assertEqual(str(card), result) + def testParseFail(self): - + data = ( """BEGIN:VCARD VERSION:3.0 @@ -434,8 +475,9 @@ for item in data: self.assertRaises(PyCalendarInvalidData, Card.parseText, item) + def testParseBlank(self): - + data = ( """ BEGIN:VCARD diff -Nru pycalendar-2.0~svn188/src/pycalendar/vcard/tests/test_property.py pycalendar-2.0~svn13177/src/pycalendar/vcard/tests/test_property.py --- pycalendar-2.0~svn188/src/pycalendar/vcard/tests/test_property.py 2011-04-27 15:38:06.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vcard/tests/test_property.py 2013-05-21 13:35:26.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,13 +14,14 @@ # limitations under the License. ## +from pycalendar.attribute import PyCalendarAttribute from pycalendar.exceptions import PyCalendarInvalidProperty from pycalendar.parser import ParserContext from pycalendar.vcard.property import Property import unittest class TestProperty(unittest.TestCase): - + test_data = ( # Different value types "PHOTO;VALUE=URI:http://example.com/photo.jpg", @@ -31,27 +32,31 @@ "NOTE:Some \\ntext", "note:Some \\ntext", "item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;CA;11111;USA", + "X-Test:Some\, text.", + "X-Test;VALUE=URI:geio:123.123,123.123", ) - + def testParseGenerate(self): - + for data in TestProperty.test_data: prop = Property() prop.parse(data) propstr = str(prop) self.assertEqual(propstr[:-2], data, "Failed parse/generate: %s to %s" % (data, propstr,)) - + + def testEquality(self): - + for data in TestProperty.test_data: prop1 = Property() prop1.parse(data) prop2 = Property() prop2.parse(data) self.assertEqual(prop1, prop2, "Failed equality: %s" % (data,)) - + + def testParseBad(self): - + test_bad_data = ( "REV:20060226T120", "NOTE:Some \\atext", @@ -62,9 +67,10 @@ prop = Property() self.assertRaises(PyCalendarInvalidProperty, prop.parse, data) ParserContext.INVALID_ESCAPE_SEQUENCES = save - + + def testHash(self): - + hashes = [] for item in TestProperty.test_data: prop = Property() @@ -72,18 +78,39 @@ hashes.append(hash(prop)) hashes.sort() for i in range(1, len(hashes)): - self.assertNotEqual(hashes[i-1], hashes[i]) + self.assertNotEqual(hashes[i - 1], hashes[i]) + def testDefaultValueCreate(self): - + test_data = ( ("SOURCE", "http://example.com/source", "SOURCE:http://example.com/source\r\n"), ("souRCE", "http://example.com/source", "souRCE:http://example.com/source\r\n"), - ("PHOTO", "YWJj", "PHOTO:\r\n YWJj\r\n"), - ("photo", "YWJj", "photo:\r\n YWJj\r\n"), + ("PHOTO", "YWJj", "PHOTO:\r\n YWJj\r\n"), + ("photo", "YWJj", "photo:\r\n YWJj\r\n"), ("URL", "http://example.com/tz1", "URL:http://example.com/tz1\r\n"), ) for propname, propvalue, result in test_data: prop = Property(name=propname, value=propvalue) self.assertEqual(str(prop), result) - + + + def testParameterEncodingDecoding(self): + + prop = Property(name="X-FOO", value="Test") + prop.addAttribute(PyCalendarAttribute("X-BAR", "\"Check\"")) + self.assertEqual(str(prop), "X-FOO;X-BAR=^'Check^':Test\r\n") + + prop.addAttribute(PyCalendarAttribute("X-BAR2", "Check\nThis\tOut\n")) + self.assertEqual(str(prop), "X-FOO;X-BAR=^'Check^';X-BAR2=Check^nThis\tOut^n:Test\r\n") + + data = "X-FOO;X-BAR=^'Check^':Test" + prop = Property() + prop.parse(data) + self.assertEqual(prop.getAttributeValue("X-BAR"), "\"Check\"") + + data = "X-FOO;X-BAR=^'Check^';X-BAR2=Check^nThis\tOut^n:Test" + prop = Property() + prop.parse(data) + self.assertEqual(prop.getAttributeValue("X-BAR"), "\"Check\"") + self.assertEqual(prop.getAttributeValue("X-BAR2"), "Check\nThis\tOut\n") diff -Nru pycalendar-2.0~svn188/src/pycalendar/vcard/tests/test_validation.py pycalendar-2.0~svn13177/src/pycalendar/vcard/tests/test_validation.py --- pycalendar-2.0~svn188/src/pycalendar/vcard/tests/test_validation.py 2011-06-30 16:03:45.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vcard/tests/test_validation.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,9 +19,9 @@ import unittest class TestValidation(unittest.TestCase): - + def test_basic(self): - + data = ( ( "No problems", @@ -59,15 +59,16 @@ )), ), ) - + for title, item, test_fixed, test_unfixed in data: card = Card.parseText(item) fixed, unfixed = card.validate(doFix=False) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) + def test_mode_no_raise(self): - + data = ( ( "OK", @@ -138,8 +139,9 @@ self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) + def test_mode_raise(self): - + data = ( ( "OK", diff -Nru pycalendar-2.0~svn188/src/pycalendar/vcard/validation.py pycalendar-2.0~svn13177/src/pycalendar/vcard/validation.py --- pycalendar-2.0~svn188/src/pycalendar/vcard/validation.py 2011-06-30 16:03:45.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vcard/validation.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff -Nru pycalendar-2.0~svn188/src/pycalendar/vevent.py pycalendar-2.0~svn13177/src/pycalendar/vevent.py --- pycalendar-2.0~svn188/src/pycalendar/vevent.py 2011-10-26 20:50:37.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vevent.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -53,17 +53,21 @@ super(PyCalendarVEvent, self).__init__(parent=parent) self.mStatus = definitions.eStatus_VEvent_None + def duplicate(self, parent=None): other = super(PyCalendarVEvent, self).duplicate(parent=parent) other.mStatus = self.mStatus return other + def getType(self): return definitions.cICalComponent_VEVENT + def getMimeComponentName(self): return itipdefinitions.cICalMIMEComponent_VEVENT + def addComponent(self, comp): # We can embed the alarm components only if comp.getType() == definitions.cICalComponent_VALARM: @@ -71,12 +75,15 @@ else: raise ValueError + def getStatus(self): return self.mStatus + def setStatus(self, status): self.mStatus = status + def finalise(self): # Do inherited super(PyCalendarVEvent, self).finalise() @@ -90,13 +97,14 @@ elif temp == definitions.cICalProperty_STATUS_CANCELLED: self.mStatus = definitions.eStatus_VEvent_Cancelled + def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems, else raise. If loggedProblems is not None it must be a C{list} and problem descriptions are appended - to that. + to that. """ - + fixed, unfixed = super(PyCalendarVEvent, self).validate(doFix) # Extra constraint: if METHOD not present, DTSTART must be @@ -105,7 +113,7 @@ # Cannot fix a missing required property logProblem = "[%s] Missing required property: %s" % (self.getType(), definitions.cICalProperty_DTSTART,) unfixed.append(logProblem) - + # Extra constraint: only one of DTEND or DURATION if self.hasProperty(definitions.cICalProperty_DTEND) and self.hasProperty(definitions.cICalProperty_DURATION): # Fix by removing the DTEND @@ -119,9 +127,10 @@ fixed.append(logProblem) else: unfixed.append(logProblem) - + return fixed, unfixed - + + # Editing def editStatus(self, status): # Only if it is different @@ -149,6 +158,7 @@ prop = PyCalendarProperty(definitions.cICalProperty_STATUS, value) self.addProperty(prop) + def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, diff -Nru pycalendar-2.0~svn188/src/pycalendar/vfreebusy.py pycalendar-2.0~svn13177/src/pycalendar/vfreebusy.py --- pycalendar-2.0~svn188/src/pycalendar/vfreebusy.py 2011-06-30 16:03:45.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vfreebusy.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -53,6 +53,7 @@ self.mSpanPeriod = None self.mBusyTime = None + def duplicate(self, parent=None): other = super(PyCalendarVFreeBusy, self).duplicate(parent=parent) other.mStart = self.mStart.duplicate() @@ -64,12 +65,15 @@ other.mBusyTime = None return other + def getType(self): return definitions.cICalComponent_VFREEBUSY + def getMimeComponentName(self): return itipdefinitions.cICalMIMEComponent_VFREEBUSY + def finalise(self): # Do inherited super(PyCalendarVFreeBusy, self).finalise() @@ -97,6 +101,7 @@ self.mDuration = False self.mEnd = temp + def fixStartEnd(self): # End is always greater than start if start exists if self.mHasStart and self.mEnd <= self.mStart: @@ -115,27 +120,35 @@ self.mEnd.offsetDay(1) self.mEnd.setHHMMSS(0, 0, 0) + def getStart(self): return self.mStart + def hasStart(self): return self.mHasStart + def getEnd(self): return self.mEnd + def hasEnd(self): return self.mHasEnd + def useDuration(self): return self.mDuration + def getSpanPeriod(self): return self.mSpanPeriod + def getBusyTime(self): return self.mBusyTime + def editTiming(self): # Updated cached values self.mHasStart = False @@ -149,6 +162,7 @@ self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) + def editTimingStartEnd(self, start, end): # Updated cached values self.mHasStart = self.mHasEnd = True @@ -173,6 +187,7 @@ prop = PyCalendarProperty(definitions.cICalProperty_DTEND, end) self.addProperty(prop) + def editTimingStartDuration(self, start, duration): # Updated cached values self.mHasStart = True @@ -197,6 +212,7 @@ prop = PyCalendarProperty(definitions.cICalProperty_DURATION, duration) self.addProperty(prop) + # Generating info def expandPeriodComp(self, period, list): # Cache the busy-time details if not done already @@ -207,6 +223,7 @@ if (self.mBusyTime is not None) and period.isPeriodOverlap(self.mSpanPeriod): list.append(self) + def expandPeriodFB(self, period, list): # Cache the busy-time details if not done already if not self.mCachedBusyTime: @@ -217,6 +234,7 @@ for fb in self.mBusyTime: list.append(PyCalendarFreeBusy(fb)) + def cacheBusyTime(self): # Clear out any existing cache @@ -273,12 +291,12 @@ period = None if isinstance(o, PyCalendarPeriodValue): period = o - + # Double-check type if period is not None: self.mBusyTime.append(PyCalendarFreeBusy(type, period.getValue())) - + if len(self.mBusyTime) == 1: min_start = period.getValue().getStart() @@ -298,9 +316,8 @@ else: - # Sort the list by period - self.mBusyTime.sort(cmp=lambda x,y: x.getPeriod().getStart().compareDateTime(y.getPeriod().getStart())) + self.mBusyTime.sort(cmp=lambda x, y: x.getPeriod().getStart().compareDateTime(y.getPeriod().getStart())) # Determine range start = PyCalendarDateTime() @@ -313,11 +330,12 @@ end = self.mEnd else: end = max_end - + self.mSpanPeriod = PyCalendarPeriod(start, end) - + self.mCachedBusyTime = True + def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, diff -Nru pycalendar-2.0~svn188/src/pycalendar/vjournal.py pycalendar-2.0~svn13177/src/pycalendar/vjournal.py --- pycalendar-2.0~svn188/src/pycalendar/vjournal.py 2011-10-26 20:50:37.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vjournal.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -45,18 +45,23 @@ def __init__(self, parent=None): super(PyCalendarVJournal, self).__init__(parent=parent) + def duplicate(self, parent=None): return super(PyCalendarVJournal, self).duplicate(parent=parent) + def getType(self): return definitions.cICalComponent_VJOURNAL + def getMimeComponentName(self): return itipdefinitions.cICalMIMEComponent_VJOURNAL + def finalise(self): super(PyCalendarVJournal, self).finalise() + def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, diff -Nru pycalendar-2.0~svn188/src/pycalendar/vtimezonedaylight.py pycalendar-2.0~svn13177/src/pycalendar/vtimezonedaylight.py --- pycalendar-2.0~svn188/src/pycalendar/vtimezonedaylight.py 2011-04-27 15:38:06.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vtimezonedaylight.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -22,8 +22,10 @@ def __init__(self, parent=None): super(PyCalendarVTimezoneDaylight, self).__init__(parent=parent) + def duplicate(self, parent=None): return super(PyCalendarVTimezoneDaylight, self).duplicate(parent=parent) + def getType(self): return definitions.cICalComponent_DAYLIGHT diff -Nru pycalendar-2.0~svn188/src/pycalendar/vtimezoneelement.py pycalendar-2.0~svn13177/src/pycalendar/vtimezoneelement.py --- pycalendar-2.0~svn188/src/pycalendar/vtimezoneelement.py 2011-06-30 16:03:45.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vtimezoneelement.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -47,6 +47,7 @@ self.mCachedExpandBelow = None self.mCachedExpandBelowItems = None + def duplicate(self, parent=None): other = super(PyCalendarVTimezoneElement, self).duplicate(parent=parent) other.mStart = self.mStart.duplicate() @@ -58,6 +59,7 @@ other.mCachedExpandBelowItems = None return other + def finalise(self): # Get DTSTART temp = self.loadValueDateTime(definitions.cICalProperty_DTSTART) @@ -88,26 +90,32 @@ # Do inherited super(PyCalendarVTimezoneElement, self).finalise() + def getSortKey(self): """ We do not want these components sorted. """ return "" + def getStart(self): return self.mStart + def getUTCOffset(self): return self.mUTCOffset + def getUTCOffsetFrom(self): return self.mUTCOffsetFrom + def getTZName(self): return self.mTZName + def expandBelow(self, below): - + # Look for recurrences if not self.mRecurrences.hasRecurrence() or self.mStart > below: # Return DTSTART even if it is newer @@ -135,19 +143,20 @@ period = PyCalendarPeriod(self.mStart, temp) self.mRecurrences.expand(self.mStart, period, self.mCachedExpandBelowItems, float_offset=self.mUTCOffsetFrom) self.mCachedExpandBelow = temp - + if len(self.mCachedExpandBelowItems) != 0: # List comes back sorted so we pick the element just less than # the dt value we want i = bisect_right(self.mCachedExpandBelowItems, below) if i != 0: return self.mCachedExpandBelowItems[i - 1] - + # The first one in the list is the one we want return self.mCachedExpandBelowItems[0] return self.mStart + def expandAll(self, start, end, with_name): if start is None: @@ -195,7 +204,7 @@ period = PyCalendarPeriod(self.mStart, end) self.mRecurrences.expand(self.mStart, period, self.mCachedExpandBelowItems, float_offset=self.mUTCOffsetFrom) self.mCachedExpandBelow = temp - + if len(self.mCachedExpandBelowItems) != 0: # Return them all within the range results = [] diff -Nru pycalendar-2.0~svn188/src/pycalendar/vtimezone.py pycalendar-2.0~svn13177/src/pycalendar/vtimezone.py --- pycalendar-2.0~svn188/src/pycalendar/vtimezone.py 2011-06-30 16:03:45.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vtimezone.py 2014-04-03 02:27:44.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -32,11 +32,17 @@ propertyValueChecks = ICALENDAR_VALUE_CHECKS + UTCOFFSET_CACHE_MAX_ENTRIES = 100000 + + sortSubComponents = False + def __init__(self, parent=None): super(PyCalendarVTimezone, self).__init__(parent=parent) self.mID = "" self.mUTCOffsetSortKey = None - self.mCachedExpandAllMax = None + self.mCachedExpandAllMaxYear = None + self.mCachedOffsets = None + def duplicate(self, parent=None): other = super(PyCalendarVTimezone, self).duplicate(parent=parent) @@ -44,13 +50,16 @@ other.mUTCOffsetSortKey = self.mUTCOffsetSortKey return other + def getType(self): return definitions.cICalComponent_VTIMEZONE + def getMimeComponentName(self): # Cannot be sent as a separate MIME object return None + def addComponent(self, comp): # We can embed the timezone components only if ((comp.getType() == definitions.cICalComponent_STANDARD) @@ -59,9 +68,11 @@ else: raise ValueError + def getMapKey(self): return self.mID + def finalise(self): # Get TZID temp = self.loadValueString(definitions.cICalProperty_TZID) @@ -69,18 +80,19 @@ self.mID = temp # Sort sub-components by DTSTART - self.mComponents.sort(key=lambda x:x.getStart()) + self.mComponents.sort(key=lambda x: x.getStart()) # Do inherited super(PyCalendarVTimezone, self).finalise() + def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems, else raise. If loggedProblems is not None it must be a C{list} and problem descriptions are appended - to that. + to that. """ - + fixed, unfixed = super(PyCalendarVTimezone, self).validate(doFix) # Must have at least one STANDARD or DAYLIGHT sub-component @@ -95,12 +107,14 @@ definitions.cICalComponent_DAYLIGHT, ) unfixed.append(logProblem) - + return fixed, unfixed - + + def getID(self): return self.mID + def getUTCOffsetSortKey(self): if self.mUTCOffsetSortKey is None: # Take time from first element @@ -120,12 +134,24 @@ return self.mUTCOffsetSortKey - def getTimezoneOffsetSeconds(self, dt): + + def getTimezoneOffsetSeconds(self, dt, relative_to_utc=False): """ Caching implementation of expansion. We cache the entire set of transitions up to one year ahead of the requested time. + + We need to handle calculating the offset based on both a local time and a UTC time. The later + is needed when converting from one timezone offset to another which is best done by determining + the UTC time as an intermediate value. + + @param dt: a date-time to determine the offset for + @type dt: L{DateTime} + @param relative_to_utc: if L{False}, then the L{dt} value is the local time for which an + offset is desired, if L{True}, then the L{dt} value is a UTC time for which an + offset is desired. + @type relative_to_utc: L{bool} """ - + # Need to make the incoming date-time relative to the DTSTART in the # timezone component for proper comparison. # This means making the incoming date-time a floating (no timezone) @@ -134,20 +160,31 @@ temp.setTimezoneID(None) # Check whether we need to recache - if self.mCachedExpandAllMax is None or temp > self.mCachedExpandAllMax: + if self.mCachedExpandAllMaxYear is None or temp.mYear >= self.mCachedExpandAllMaxYear: cacheMax = temp.duplicate() - cacheMax.offsetYear(1) + cacheMax.setHHMMSS(0, 0, 0) + cacheMax.offsetYear(2) + cacheMax.setMonth(1) + cacheMax.setDay(1) self.mCachedExpandAll = self.expandAll(None, cacheMax) - self.mCachedExpandAllMax = cacheMax - + self.mCachedExpandAllMaxYear = cacheMax.mYear + self.mCachedOffsets = {} + # Now search for the transition just below the time we want if len(self.mCachedExpandAll): - i = PyCalendarVTimezone.tuple_bisect_right(self.mCachedExpandAll, temp) + cacheKey = (temp.mYear, temp.mMonth, temp.mDay, temp.mHours, temp.mMinutes, relative_to_utc) + i = self.mCachedOffsets.get(cacheKey) + if i is None: + i = PyCalendarVTimezone.tuple_bisect_right(self.mCachedExpandAll, temp, relative_to_utc) + if len(self.mCachedOffsets) >= self.UTCOFFSET_CACHE_MAX_ENTRIES: + self.mCachedOffsets = {} + self.mCachedOffsets[cacheKey] = i if i != 0: - return self.mCachedExpandAll[i-1][2] + return self.mCachedExpandAll[i - 1][3] return 0 + def getTimezoneDescriptor(self, dt): result = "" @@ -178,24 +215,29 @@ return result + def mergeTimezone(self, tz): pass + @staticmethod - def tuple_bisect_right(a, x): + def tuple_bisect_right(a, x, relative_to_utc=False): """ Same as bisect_right except that the values being compared are the first elements of a tuple. """ - + lo = 0 hi = len(a) while lo < hi: - mid = (lo+hi)//2 - if x < a[mid][0]: hi = mid - else: lo = mid+1 + mid = (lo + hi) // 2 + if x < a[mid][1 if relative_to_utc else 0]: + hi = mid + else: + lo = mid + 1 return lo + def findTimezoneElement(self, dt): # Need to make the incoming date-time relative to the DTSTART in the # timezone component for proper comparison. @@ -226,13 +268,22 @@ return found + def expandAll(self, start, end, with_name=False): results = [] for item in self.mComponents: results.extend(item.expandAll(start, end, with_name)) - results = [x for x in set(results)] - results.sort(key=lambda x:x[0].getPosixTime()) - return results + + utc_results = [] + for items in set(results): + items = list(items) + utcdt = items[0].duplicate() + utcdt.offsetSeconds(-items[1]) + items.insert(1, utcdt) + utc_results.append(tuple(items)) + utc_results.sort(key=lambda x: x[0].getPosixTime()) + return utc_results + def sortedPropertyKeyOrder(self): return ( @@ -241,6 +292,7 @@ definitions.cICalProperty_TZURL, ) + @staticmethod def sortByUTCOffsetComparator(tz1, tz2): sort1 = tz1.getUTCOffsetSortKey() diff -Nru pycalendar-2.0~svn188/src/pycalendar/vtimezonestandard.py pycalendar-2.0~svn13177/src/pycalendar/vtimezonestandard.py --- pycalendar-2.0~svn188/src/pycalendar/vtimezonestandard.py 2011-04-27 15:38:06.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vtimezonestandard.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -22,8 +22,10 @@ def __init__(self, parent=None): super(PyCalendarVTimezoneStandard, self).__init__(parent=parent) + def duplicate(self, parent=None): return super(PyCalendarVTimezoneStandard, self).duplicate(parent=parent) + def getType(self): return definitions.cICalComponent_STANDARD diff -Nru pycalendar-2.0~svn188/src/pycalendar/vtodo.py pycalendar-2.0~svn13177/src/pycalendar/vtodo.py --- pycalendar-2.0~svn188/src/pycalendar/vtodo.py 2011-10-26 20:50:37.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vtodo.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -25,10 +25,10 @@ class PyCalendarVToDo(PyCalendarComponentRecur): OVERDUE = 0 - DUE_NOW= 1 + DUE_NOW = 1 DUE_LATER = 2 DONE = 3 - CANCELLED= 4 + CANCELLED = 4 @staticmethod def sort_for_display(e1, e2): @@ -120,6 +120,7 @@ self.mCompleted = PyCalendarDateTime() self.mHasCompleted = False + def duplicate(self, parent=None): other = super(PyCalendarVToDo, self).duplicate(parent=parent) other.mPriority = self.mPriority @@ -129,12 +130,15 @@ other.mHasCompleted = self.mHasCompleted return other + def getType(self): return definitions.cICalComponent_VTODO + def getMimeComponentName(self): return itipdefinitions.cICalMIMEComponent_VTODO + def addComponent(self, comp): # We can embed the alarm components only if comp.getType() == definitions.cICalComponent_VALARM: @@ -142,12 +146,15 @@ else: raise ValueError + def getStatus(self): return self.mStatus + def setStatus(self, status): self.mStatus = status + def getStatusText(self): sout = StringIO() @@ -188,6 +195,7 @@ return sout.toString() + def getCompletionState(self): if self.mStatus in (definitions.eStatus_VToDo_NeedsAction, definitions.eStatus_VToDo_InProcess): if self.hasEnd(): @@ -207,18 +215,23 @@ elif self.mStatus == definitions.eStatus_VToDo_Cancelled: return PyCalendarVToDo.CANCELLED + def getPriority(self): return self.mPriority + def setPriority(self, priority): self.mPriority = priority + def getCompleted(self): return self.mCompleted + def hasCompleted(self): return self.mHasCompleted + def finalise(self): # Do inherited super(PyCalendarVToDo, self).finalise() @@ -261,13 +274,14 @@ if self.mHasCompleted: self.mCompleted = temp + def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems, else raise. If loggedProblems is not None it must be a C{list} and problem descriptions are appended - to that. + to that. """ - + fixed, unfixed = super(PyCalendarVToDo, self).validate(doFix) # Extra constraint: only one of DUE or DURATION @@ -283,7 +297,7 @@ fixed.append(logProblem) else: unfixed.append(logProblem) - + # Extra constraint: DTSTART must be present if DURATION is present if self.hasProperty(definitions.cICalProperty_DURATION) and not self.hasProperty(definitions.cICalProperty_DTSTART): # Cannot fix this one @@ -293,9 +307,10 @@ definitions.cICalProperty_DURATION, ) unfixed.append(logProblem) - + return fixed, unfixed - + + # Editing def editStatus(self, status): # Only if it is different @@ -326,6 +341,7 @@ prop = PyCalendarProperty(definitions.cICalProperty_STATUS, value) self.addProperty(prop) + def editCompleted(self, completed): # Remove existing COMPLETED item self.removeProperties(definitions.cICalProperty_COMPLETED) @@ -338,6 +354,7 @@ prop = PyCalendarProperty(definitions.cICalProperty_STATUS_COMPLETED, self.mCompleted) self.addProperty(prop) + def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, diff -Nru pycalendar-2.0~svn188/src/pycalendar/vunknown.py pycalendar-2.0~svn13177/src/pycalendar/vunknown.py --- pycalendar-2.0~svn188/src/pycalendar/vunknown.py 2011-06-30 16:03:45.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/vunknown.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2011-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -28,30 +28,38 @@ self.mType = comptype self.mMapKey = str(uuid.uuid4()) + def duplicate(self, parent=None): return super(PyCalendarUnknownComponent, self).duplicate(parent=parent, comptype=self.mType) + def getType(self): return self.mType + def getBeginDelimiter(self): return "BEGIN:" + self.mType + def getEndDelimiter(self): return "END:" + self.mType + def getMimeComponentName(self): return "unknown" + def getMapKey(self): return self.mMapKey + def getSortKey(self): """ We do not want unknown components sorted. """ return "" + def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, diff -Nru pycalendar-2.0~svn188/src/pycalendar/xmldefs.py pycalendar-2.0~svn13177/src/pycalendar/xmldefs.py --- pycalendar-2.0~svn188/src/pycalendar/xmldefs.py 2011-05-31 19:25:59.000000000 +0000 +++ pycalendar-2.0~svn13177/src/pycalendar/xmldefs.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,7 +20,7 @@ iCalendar20_namespace = "urn:ietf:params:xml:ns:icalendar-2.0" -icalendar = "icalendar" +icalendar = "icalendar" components = "components" properties = "properties" parameters = "parameters" @@ -35,6 +35,7 @@ value_period = "period" value_recur = "recur" value_text = "text" +value_unknown = "unknown" value_uri = "uri" value_utc_offset = "utc-offset" @@ -75,15 +76,17 @@ def makeTag(namespace, name): return "{%s}%s" % (namespace, name.lower(),) + + def toString(root): - + data = """\n""" INDENT = 2 # Generate indentation def _indentNode(node, level=0): - + if node.text is not None and node.text.strip(): return elif len(node.getchildren()): @@ -99,4 +102,3 @@ data += XML.tostring(root) + "\n" return data - diff -Nru pycalendar-2.0~svn188/src/zonal/__init__.py pycalendar-2.0~svn13177/src/zonal/__init__.py --- pycalendar-2.0~svn188/src/zonal/__init__.py 2011-05-02 15:49:55.000000000 +0000 +++ pycalendar-2.0~svn13177/src/zonal/__init__.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,16 +1,15 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## - diff -Nru pycalendar-2.0~svn188/src/zonal/rule.py pycalendar-2.0~svn13177/src/zonal/rule.py --- pycalendar-2.0~svn188/src/zonal/rule.py 2011-05-02 15:49:55.000000000 +0000 +++ pycalendar-2.0~svn13177/src/zonal/rule.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -37,31 +37,35 @@ """ A set of tzdata rules tied to a specific Rule name """ - + def __init__(self): self.name = "" self.rules = [] - + + def __str__(self): return self.generate() + def __eq__(self, other): return other and ( self.name == other.name and self.rules == other.rules ) + def __ne__(self, other): return not self.__eq__(other) + def parse(self, lines): """ Parse the set of Rule lines from tzdata. - + @param lines: the lines to parse. @type lines: C{str} """ - + splitlines = lines.split("\n") for line in splitlines: splits = line.expandtabs(1).split(" ") @@ -73,16 +77,18 @@ rule = Rule() rule.parse(line) self.rules.append(rule) - + + def generate(self): """ Generate a Rule line. - + @return: a C{str} with the Rule. """ items = [rule.generate() for rule in self.rules] return "\n".join(items) + def expand(self, results, zoneinfo, maxYear): """ Expand the set of rules into transition/offset pairs for the entire RuleSet @@ -92,75 +98,78 @@ @type results: C{list} @param zoneinfo: the Zone in which this RuleSet is being used @type zoneinfo: L{ZoneRule} - @param maxYear: the maximum year to expand out to + @param maxYear: the maximum year to expand out to @type maxYear: C{int} """ for rule in self.rules: rule.expand(results, zoneinfo, maxYear) + + class Rule(object): """ A tzdata Rule """ - + # Some useful mapping tables LASTDAY_NAME_TO_DAY = { - "lastSun":PyCalendarDateTime.SUNDAY, - "lastMon":PyCalendarDateTime.MONDAY, - "lastTue":PyCalendarDateTime.TUESDAY, - "lastWed":PyCalendarDateTime.WEDNESDAY, - "lastThu":PyCalendarDateTime.THURSDAY, - "lastFri":PyCalendarDateTime.FRIDAY, - "lastSat":PyCalendarDateTime.SATURDAY, + "lastSun": PyCalendarDateTime.SUNDAY, + "lastMon": PyCalendarDateTime.MONDAY, + "lastTue": PyCalendarDateTime.TUESDAY, + "lastWed": PyCalendarDateTime.WEDNESDAY, + "lastThu": PyCalendarDateTime.THURSDAY, + "lastFri": PyCalendarDateTime.FRIDAY, + "lastSat": PyCalendarDateTime.SATURDAY, } - + DAY_NAME_TO_DAY = { - "Sun":PyCalendarDateTime.SUNDAY, - "Mon":PyCalendarDateTime.MONDAY, - "Tue":PyCalendarDateTime.TUESDAY, - "Wed":PyCalendarDateTime.WEDNESDAY, - "Thu":PyCalendarDateTime.THURSDAY, - "Fri":PyCalendarDateTime.FRIDAY, - "Sat":PyCalendarDateTime.SATURDAY, + "Sun": PyCalendarDateTime.SUNDAY, + "Mon": PyCalendarDateTime.MONDAY, + "Tue": PyCalendarDateTime.TUESDAY, + "Wed": PyCalendarDateTime.WEDNESDAY, + "Thu": PyCalendarDateTime.THURSDAY, + "Fri": PyCalendarDateTime.FRIDAY, + "Sat": PyCalendarDateTime.SATURDAY, } - + LASTDAY_NAME_TO_RDAY = { - "lastSun":definitions.eRecurrence_WEEKDAY_SU, - "lastMon":definitions.eRecurrence_WEEKDAY_MO, - "lastTue":definitions.eRecurrence_WEEKDAY_TU, - "lastWed":definitions.eRecurrence_WEEKDAY_WE, - "lastThu":definitions.eRecurrence_WEEKDAY_TH, - "lastFri":definitions.eRecurrence_WEEKDAY_FR, - "lastSat":definitions.eRecurrence_WEEKDAY_SA, + "lastSun": definitions.eRecurrence_WEEKDAY_SU, + "lastMon": definitions.eRecurrence_WEEKDAY_MO, + "lastTue": definitions.eRecurrence_WEEKDAY_TU, + "lastWed": definitions.eRecurrence_WEEKDAY_WE, + "lastThu": definitions.eRecurrence_WEEKDAY_TH, + "lastFri": definitions.eRecurrence_WEEKDAY_FR, + "lastSat": definitions.eRecurrence_WEEKDAY_SA, } - + DAY_NAME_TO_RDAY = { - PyCalendarDateTime.SUNDAY:definitions.eRecurrence_WEEKDAY_SU, - PyCalendarDateTime.MONDAY:definitions.eRecurrence_WEEKDAY_MO, - PyCalendarDateTime.TUESDAY:definitions.eRecurrence_WEEKDAY_TU, - PyCalendarDateTime.WEDNESDAY:definitions.eRecurrence_WEEKDAY_WE, - PyCalendarDateTime.THURSDAY:definitions.eRecurrence_WEEKDAY_TH, - PyCalendarDateTime.FRIDAY:definitions.eRecurrence_WEEKDAY_FR, - PyCalendarDateTime.SATURDAY:definitions.eRecurrence_WEEKDAY_SA, + PyCalendarDateTime.SUNDAY: definitions.eRecurrence_WEEKDAY_SU, + PyCalendarDateTime.MONDAY: definitions.eRecurrence_WEEKDAY_MO, + PyCalendarDateTime.TUESDAY: definitions.eRecurrence_WEEKDAY_TU, + PyCalendarDateTime.WEDNESDAY: definitions.eRecurrence_WEEKDAY_WE, + PyCalendarDateTime.THURSDAY: definitions.eRecurrence_WEEKDAY_TH, + PyCalendarDateTime.FRIDAY: definitions.eRecurrence_WEEKDAY_FR, + PyCalendarDateTime.SATURDAY: definitions.eRecurrence_WEEKDAY_SA, } - + MONTH_NAME_TO_POS = { - "Jan":1, - "Feb":2, - "Mar":3, - "Apr":4, - "May":5, - "Jun":6, - "Jul":7, - "Aug":8, - "Sep":9, - "Oct":10, - "Nov":11, - "Dec":12, + "Jan": 1, + "Feb": 2, + "Mar": 3, + "Apr": 4, + "May": 5, + "Jun": 6, + "Jul": 7, + "Aug": 8, + "Sep": 9, + "Oct": 10, + "Nov": 11, + "Dec": 12, } - - MONTH_POS_TO_NAME = ("", "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec") + + MONTH_POS_TO_NAME = ("", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec") + def __init__(self): self.name = "" @@ -172,10 +181,12 @@ self.atTime = 0 self.saveTime = 0 self.letter = "" - + + def __str__(self): return self.generate() + def __eq__(self, other): return other and ( self.name == other.name and @@ -189,17 +200,19 @@ self.letter == other.letter ) + def __ne__(self, other): return not self.__eq__(other) + def parse(self, line): """ Parse the Rule line from tzdata. - + @param line: the line to parse. @type line: C{str} """ - + # Simply split the bits up and store them in various properties splits = [x for x in line.expandtabs(1).split(" ") if len(x) > 0] assert len(splits) >= 10, "Wrong number of fields in Rule: '%s'" % (line,) @@ -212,11 +225,12 @@ self.atTime = splits[7] self.saveTime = splits[8] self.letter = splits[9] - + + def generate(self): """ Generate a Rule line. - + @return: a C{str} with the Rule. """ items = ( @@ -234,10 +248,11 @@ return "\t".join(items) + def getOffset(self): """ Return the specified rule offset in seconds. - + @return: C{int} """ splits = self.saveTime.split(":") @@ -248,13 +263,15 @@ minutes = 0 negative = hours < 0 if negative: - return -((-hours *60) + minutes) * 60 + return -((-hours * 60) + minutes) * 60 else: - return ((hours *60) + minutes) * 60 - + return ((hours * 60) + minutes) * 60 + + def startYear(self): return int(self.fromYear) - + + def endYear(self): if self.toYear == "only": return self.startYear() @@ -262,30 +279,31 @@ return 9999 else: return int(self.toYear) - + + def datetimeForYear(self, year): """ Given a specific year, determine the actual date/time of the transition @param year: the year to determine the transition for @type year: C{int} - + @return: C{tuple} of L{PyCalendarDateTime} and C{str} (which is the special tzdata mode character """ # Create a floating date-time dt = PyCalendarDateTime() - + # Setup base year/month/day dt.setYear(year) dt.setMonth(Rule.MONTH_NAME_TO_POS[self.inMonth]) dt.setDay(1) - + # Setup base hours/minutes splits = self.atTime.split(":") if len(splits) == 1: splits.append("0") - assert len(splits) == 2, "atTime format is wrong: %s, %s" %(self.atTime, self,) + assert len(splits) == 2, "atTime format is wrong: %s, %s" % (self.atTime, self,) hours = int(splits[0]) if len(splits[1]) > 2: minutes = int(splits[1][:2]) @@ -293,7 +311,7 @@ else: minutes = int(splits[1]) special = "" - + # Special case for 24:00 if hours == 24 and minutes == 0: dt.setHours(23) @@ -302,7 +320,7 @@ else: dt.setHours(hours) dt.setMinutes(minutes) - + # Now determine the actual start day if self.onDay in Rule.LASTDAY_NAME_TO_DAY: dt.setDayOfWeekInMonth(-1, Rule.LASTDAY_NAME_TO_DAY[self.onDay]) @@ -318,6 +336,7 @@ return dt, special + def getOnDayDetails(self, start, indicatedDay, indicatedOffset): """ Get RRULE BYxxx part items from the Rule data. @@ -329,19 +348,19 @@ @param indicatedOffset: the offset that the Rule indicates for recurrence @type indicatedOffset: C{int} """ - + month = start.getMonth() year = start.getYear() dayOfWeek = start.getDayOfWeek() # Need to check whether day has changed due to time shifting # e.g. if "u" mode is specified, the actual localtime may be - # shifted to the previous day if the offset is negative + # shifted to the previous day if the offset is negative if indicatedDay != dayOfWeek: difference = dayOfWeek - indicatedDay if difference in (1, -6,): indicatedOffset += 1 - + # Adjust the month down too if needed if start.getDay() == 1: month -= 1 @@ -352,7 +371,7 @@ indicatedOffset -= 1 else: assert False, "Unknown RRULE adjustment" - + try: # Form the appropriate RRULE bits day = Rule.DAY_NAME_TO_RDAY[dayOfWeek] @@ -379,9 +398,10 @@ offset = 0 except: assert False, "onDay value is not recognized: %s" % (self.onDay,) - + return offset, day, bymday + def expand(self, results, zoneinfo, maxYear): """ Expand the Rule into a set of transition date/offset pairs @@ -390,10 +410,10 @@ @type results: C{list} @param zoneinfo: the Zone in which this RuleSet is being used @type zoneinfo: L{ZoneRule} - @param maxYear: the maximum year to expand out to + @param maxYear: the maximum year to expand out to @type maxYear: C{int} """ - + if self.startYear() >= maxYear: return @@ -403,6 +423,7 @@ for dt in self.dt_cache: results.append((dt, zoneoffset + offset, self)) + def fullExpand(self, maxYear): """ Do a full recurrence expansion for each year in the Rule's range, upto @@ -422,10 +443,11 @@ dt = utils.DateTime(*self.datetimeForYear(year)) self.dt_cache.append(dt) + def vtimezone(self, vtz, zonerule, start, end, offsetfrom, offsetto, instanceCount): """ Generate a VTIMEZONE sub-component for this Rule. - + @param vtz: VTIMEZONE to add to @type vtz: L{PyCalendarVTimezone} @param zonerule: the Zone rule line being used @@ -441,7 +463,7 @@ @param instanceCount: the number of instances in the set @type instanceCount: C{int} """ - + # Determine type of component based on offset dstoffset = self.getOffset() if dstoffset == 0: @@ -455,14 +477,14 @@ comp.addProperty(PyCalendarProperty(definitions.cICalProperty_TZOFFSETFROM, tzoffsetfrom)) comp.addProperty(PyCalendarProperty(definitions.cICalProperty_TZOFFSETTO, tzoffsetto)) - + # Do TZNAME if zonerule.format.find("%") != -1: tzname = zonerule.format % (self.letter if self.letter != "-" else "",) else: tzname = zonerule.format comp.addProperty(PyCalendarProperty(definitions.cICalProperty_TZNAME, tzname)) - + # Do DTSTART comp.addProperty(PyCalendarProperty(definitions.cICalProperty_DTSTART, start)) @@ -473,21 +495,23 @@ rrule.setFreq(definitions.eRecurrence_YEARLY) rrule.setByMonth((Rule.MONTH_NAME_TO_POS[self.inMonth],)) if self.onDay in Rule.LASTDAY_NAME_TO_RDAY: - + # Need to check whether day has changed due to time shifting dayOfWeek = start.getDayOfWeek() indicatedDay = Rule.LASTDAY_NAME_TO_DAY[self.onDay] - + if dayOfWeek == indicatedDay: rrule.setByDay(((-1, Rule.LASTDAY_NAME_TO_RDAY[self.onDay]),)) elif dayOfWeek < indicatedDay or dayOfWeek == 6 and indicatedDay == 0: + # This is OK as we have moved back a day and thus no month transition + # could have occurred fakeOffset = daysInMonth(start.getMonth(), start.getYear()) - 6 offset, rday, bymday = self.getOnDayDetails(start, indicatedDay, fakeOffset) if bymday: rrule.setByMonthDay(bymday) rrule.setByDay(((offset, rday),)) else: - # This is bad news we have moved forward a day possibly into the next month + # This is bad news as we have moved forward a day possibly into the next month # What we do is switch to using a BYYEARDAY rule with offset from the end of the year rrule.setByMonth(()) daysBackStartOfMonth = ( @@ -497,22 +521,21 @@ rrule.setByDay( ((0, divmod(Rule.LASTDAY_NAME_TO_DAY[self.onDay] + 1, 7)[1]),), ) - - + elif self.onDay.find(">=") != -1: indicatedDay, dayoffset = self.onDay.split(">=") # Need to check whether day has changed due to time shifting dayOfWeek = start.getDayOfWeek() indicatedDay = Rule.DAY_NAME_TO_DAY[indicatedDay] - + if dayOfWeek == indicatedDay: offset, rday, bymday = self.getOnDayDetails(start, indicatedDay, int(dayoffset)) if bymday: rrule.setByMonthDay(bymday) rrule.setByDay(((offset, rday),)) - elif dayOfWeek < indicatedDay or dayOfWeek == 6 and indicatedDay == 0: - # This is bad news we have moved forward a day possibly into the next month + elif dayoffset == 1 and divmod(dayoffset - indicatedDay, 7)[1] == 6: + # This is bad news as we have moved backward a day possibly into the next month # What we do is switch to using a BYYEARDAY rule with offset from the end of the year rrule.setByMonth(()) daysBackStartOfMonth = ( @@ -523,6 +546,8 @@ ((0, divmod(indicatedDay + 1, 7)[1]),), ) else: + # This is OK as we have moved forward a day and thus no month transition + # could have occurred offset, rday, bymday = self.getOnDayDetails(start, indicatedDay, int(dayoffset)) if bymday: rrule.setByMonthDay(bymday) @@ -540,7 +565,7 @@ until.setTimezoneUTC(True) rrule.setUseUntil(True) rrule.setUntil(until) - + comp.addProperty(PyCalendarProperty(definitions.cICalProperty_RRULE, rrule)) else: comp.addProperty(PyCalendarProperty(definitions.cICalProperty_RDATE, start)) diff -Nru pycalendar-2.0~svn188/src/zonal/tests/__init__.py pycalendar-2.0~svn13177/src/zonal/tests/__init__.py --- pycalendar-2.0~svn188/src/zonal/tests/__init__.py 2011-05-02 15:49:55.000000000 +0000 +++ pycalendar-2.0~svn13177/src/zonal/tests/__init__.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,16 +1,15 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## - diff -Nru pycalendar-2.0~svn188/src/zonal/tests/test_rule.py pycalendar-2.0~svn13177/src/zonal/tests/test_rule.py --- pycalendar-2.0~svn188/src/zonal/tests/test_rule.py 2011-05-02 15:49:55.000000000 +0000 +++ pycalendar-2.0~svn13177/src/zonal/tests/test_rule.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,7 +19,7 @@ from pycalendar.datetime import PyCalendarDateTime class TestRule(unittest.TestCase): - + def test_parse(self): data = ( "Rule\tGuat\t2006\tonly\t-\tOct\t1\t0:00\t0\tS", @@ -27,41 +27,45 @@ "Rule\tEgypt\t1945\tonly\t-\tApr\t16\t0:00\t1:00\tS", "Rule\tGhana\t1936\t1942\t-\tSep\t1\t0:00\t0:20\tGHST", ) - + for ruletext in data: ruleitem = Rule() ruleitem.parse(ruletext) - + self.assertEqual(str(ruleitem), ruletext) - + + def test_datetimeforyear(self): data = ( ("Rule\tGuat\t2006\tonly\t-\tOct\t1\t0:00\t0\tS", 2006, PyCalendarDateTime(2006, 10, 1, 0, 0, 0), ""), ("Rule\tAlgeria\t1916\t1919\t-\tOct\tSun>=1\t23:00s\t0\t-", 1916, PyCalendarDateTime(1916, 10, 1, 23, 0, 0), "s"), ("Rule\tGhana\t1936\t1942\t-\tSep\t1\t0:00\t0:20\tGHST", 1937, PyCalendarDateTime(1937, 9, 1, 0, 0, 0), ""), ) - + for ruletext, year, dt, special in data: ruleitem = Rule() ruleitem.parse(ruletext) - + self.assertEqual(ruleitem.datetimeForYear(year), (dt, special)) - + + def test_getoffset(self): data = ( ("Rule\tGuat\t2006\tonly\t-\tOct\t1\t0:00\t0\tS", 0), ("Rule\tEgypt\t1945\tonly\t-\tApr\t16\t0:00\t1:00\tS", 60 * 60), ("Rule\tGhana\t1936\t1942\t-\tSep\t1\t0:00\t0:20\tGHST", 20 * 60), ) - + for ruletext, offset in data: ruleitem = Rule() ruleitem.parse(ruletext) - + self.assertEqual(ruleitem.getOffset(), offset) + + class TestRuleSet(unittest.TestCase): - + def test_parse(self): data = """Rule\tUS\t1918\t1919\t-\tMar\tlastSun\t2:00\t1:00\tD Rule\tUS\t1918\t1919\t-\tOct\tlastSun\t2:00\t0\tS @@ -76,7 +80,7 @@ Rule\tUS\t1987\t2006\t-\tApr\tSun>=1\t2:00\t1:00\tD Rule\tUS\t2007\tmax\t-\tMar\tSun>=8\t2:00\t1:00\tD Rule\tUS\t2007\tmax\t-\tNov\tSun>=1\t2:00\t0\tS""" - + ruleset = RuleSet() ruleset.parse(data) diff -Nru pycalendar-2.0~svn188/src/zonal/tests/test_zone.py pycalendar-2.0~svn13177/src/zonal/tests/test_zone.py --- pycalendar-2.0~svn188/src/zonal/tests/test_zone.py 2011-05-02 15:49:55.000000000 +0000 +++ pycalendar-2.0~svn13177/src/zonal/tests/test_zone.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,7 +20,7 @@ from pycalendar.calendar import PyCalendar class TestZone(unittest.TestCase): - + def test_parse(self): zonedef = """Zone America/New_York\t-4:56:02\t-\tLMT\t1883 Nov 18 12:03:58 \t\t\t-5:00\tUS\tE%sT\t1920 @@ -33,6 +33,7 @@ self.assertEqual(str(zone), zonedef) + def test_vtimezone(self): zonedef = """Zone America/New_York\t-4:56:02\t-\tLMT\t1883 Nov 18 12:03:58 diff -Nru pycalendar-2.0~svn188/src/zonal/tzconvert.py pycalendar-2.0~svn13177/src/zonal/tzconvert.py --- pycalendar-2.0~svn188/src/zonal/tzconvert.py 2011-10-11 17:07:18.000000000 +0000 +++ pycalendar-2.0~svn13177/src/zonal/tzconvert.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,13 +1,13 @@ #!/usr/bin/env python ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -35,15 +35,18 @@ ) class tzconvert(object): - - def __init__(self): + + def __init__(self, verbose=False): self.rules = {} self.zones = {} self.links = {} - + self.verbose = verbose + + def getZoneNames(self): return set(self.zones.keys()) + def parse(self, file): try: f = open(file, "r") @@ -71,12 +74,14 @@ except: print "Failed to parse file %s" % (file,) raise - + + def parseRule(self, line): ruleitem = rule.Rule() ruleitem.parse(line) self.rules.setdefault(ruleitem.name, rule.RuleSet()).rules.append(ruleitem) + def parseZone(self, line, f): os = StringIO.StringIO() os.write(line) @@ -95,16 +100,18 @@ zoneitem = zone.Zone() zoneitem.parse(os.getvalue()) self.zones[zoneitem.name] = zoneitem - + return last_line + def parseLink(self, line): - + splits = line.split() linkFrom = splits[1] linkTo = splits[2] self.links[linkTo] = linkFrom + def expandZone(self, zonename, minYear, maxYear=2018): """ Expand a zones transition dates up to the specified year. @@ -112,21 +119,23 @@ zone = self.zones[zonename] expanded = zone.expand(self.rules, minYear, maxYear) return [(item[0], item[1], item[2],) for item in expanded] - + + def vtimezones(self, minYear, maxYear=2018, filterzones=None): """ Generate iCalendar data for all VTIMEZONEs or just those specified """ - + cal = PyCalendar() for zone in self.zones.itervalues(): if filterzones and zone.name not in filterzones: continue vtz = zone.vtimezone(cal, self.rules, minYear, maxYear) cal.addComponent(vtz) - + return cal.getText() + def generateZoneinfoFiles(self, outputdir, minYear, maxYear=2018, links=True, filterzones=None): # Empty current directory @@ -145,42 +154,46 @@ cal = PyCalendar() vtz = zone.vtimezone(cal, self.rules, minYear, maxYear) cal.addComponent(vtz) - + icsdata = cal.getText() fpath = os.path.join(outputdir, zone.name + ".ics") if not os.path.exists(os.path.dirname(fpath)): os.makedirs(os.path.dirname(fpath)) with open(fpath, "w") as f: f.write(icsdata) - print "Write path: %s" % (fpath,) - + if self.verbose: + print "Write path: %s" % (fpath,) + if links: link_list = [] for linkTo, linkFrom in self.links.iteritems(): - + # Check for existing output file fromPath = os.path.join(outputdir, linkFrom + ".ics") if not os.path.exists(fromPath): print "Missing link from: %s to %s" % (linkFrom, linkTo,) continue - + with open(fromPath) as f: icsdata = f.read() icsdata = icsdata.replace(linkFrom, linkTo) - + toPath = os.path.join(outputdir, linkTo + ".ics") if not os.path.exists(os.path.dirname(toPath)): os.makedirs(os.path.dirname(toPath)) with open(toPath, "w") as f: f.write(icsdata) - print "Write link: %s" % (linkTo,) - + if self.verbose: + print "Write link: %s" % (linkTo,) + link_list.append("%s\t%s" % (linkTo, linkFrom,)) - + # Generate link mapping file linkPath = os.path.join(outputdir, "links.txt") open(linkPath, "w").write("\n".join(link_list)) - + + + def usage(error_msg=None): if error_msg: print error_msg @@ -207,11 +220,12 @@ else: sys.exit(0) + if __name__ == '__main__': # Set the PRODID value used in generated iCalendar data prodid = "-//mulberrymail.com//Zonal//EN" - rootdir = "../../2011g" + rootdir = "../../stuff/temp" startYear = 1800 endYear = 2018 @@ -252,7 +266,7 @@ "backward", ) - parser = tzconvert() + parser = tzconvert(verbose=True) for file in zonefiles: parser.parse(os.path.join(zonedir, file)) @@ -260,6 +274,7 @@ parser.generateZoneinfoFiles(os.path.join(rootdir, "zoneinfo"), startYear, endYear, filterzones=( #"America/Montevideo", #"Europe/Paris", + #"Africa/Cairo", )) if 0: @@ -267,24 +282,24 @@ parsed = parser.vtimezones(1800, 2018, filterzones=( checkName, )) - + icsdir = "../2008i/zoneinfo" cal = PyCalendar() for file in (checkName,): fin = open(os.path.join(icsdir, file + ".ics"), "r") cal.parse(fin) - + for vtz in cal.getVTimezoneDB(): #from pycalendar.vtimezoneelement import PyCalendarVTimezoneElement #vtz.mEmbedded.sort(PyCalendarVTimezoneElement.sort_dtstart) for embedded in vtz.mEmbedded: embedded.finalise() vtz.finalise() - + os = StringIO.StringIO() cal.generate(os, False) actual = os.getvalue() - + print "-- ACTUAL --" print actual print diff -Nru pycalendar-2.0~svn188/src/zonal/tzdump.py pycalendar-2.0~svn13177/src/zonal/tzdump.py --- pycalendar-2.0~svn188/src/zonal/tzdump.py 2011-05-02 15:49:55.000000000 +0000 +++ pycalendar-2.0~svn13177/src/zonal/tzdump.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,13 +1,13 @@ #!/usr/bin/env python ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,7 +23,7 @@ from pycalendar.exceptions import PyCalendarInvalidData def loadCalendar(file, verbose): - + cal = PyCalendar() if verbose: print "Parsing calendar data: %s" % (file,) @@ -35,13 +35,17 @@ raise return cal + + def getExpandedDates(cal, start, end): - + vtz = cal.getComponents()[0] expanded = vtz.expandAll(start, end) - expanded.sort(cmp=lambda x,y: PyCalendarDateTime.sort(x[0], y[0])) + expanded.sort(cmp=lambda x, y: PyCalendarDateTime.sort(x[0], y[0])) return expanded + + def formattedExpandedDates(expanded): items = [] for item in expanded: @@ -51,6 +55,8 @@ items.append((item[0], utc, secondsToTime(item[1]), secondsToTime(item[2]),)) return "\n".join(["(%s, %s, %s, %s)" % item for item in items]) + + def secondsToTime(seconds): if seconds < 0: seconds = -seconds @@ -58,13 +64,15 @@ else: negative = "" secs = divmod(seconds, 60)[1] - mins = divmod(seconds/60, 60)[1] - hours = divmod(seconds/(60*60), 60)[1] + mins = divmod(seconds / 60, 60)[1] + hours = divmod(seconds / (60 * 60), 60)[1] if secs: return "%s%02d:%02d:%02d" % (negative, hours, mins, secs,) else: return "%s%02d:%02d" % (negative, hours, mins,) + + def usage(error_msg=None): if error_msg: print error_msg @@ -90,13 +98,14 @@ else: sys.exit(0) + if __name__ == '__main__': verbose = False startYear = 1918 endYear = 2018 fpath = None - + options, args = getopt.getopt(sys.argv[1:], "hv", ["start=", "end=", ]) for option, value in options: diff -Nru pycalendar-2.0~svn188/src/zonal/tzverify.py pycalendar-2.0~svn13177/src/zonal/tzverify.py --- pycalendar-2.0~svn188/src/zonal/tzverify.py 2011-05-02 15:49:55.000000000 +0000 +++ pycalendar-2.0~svn13177/src/zonal/tzverify.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,13 +1,13 @@ #!/usr/bin/env python ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -24,7 +24,7 @@ from pycalendar.exceptions import PyCalendarInvalidData def loadCalendarFromZoneinfo(zoneinfopath, skips=(), verbose=False, quiet=False): - + if not quiet: print "Scanning for calendar data in: %s" % (zoneinfopath,) paths = [] @@ -47,8 +47,10 @@ print "Parsing calendar data in: %s" % (zoneinfopath,) return loadCalendar(paths, verbose) + + def loadCalendar(files, verbose): - + cal = PyCalendar() for file in files: if verbose: @@ -61,6 +63,8 @@ raise return CalendarZonesWrapper(calendar=cal) + + def parseTZData(zonedir, zonefiles): parser = tzconvert() for file in zonefiles: @@ -70,37 +74,43 @@ parser.parse(zonefile) return CalendarZonesWrapper(zones=parser) + + class CalendarZonesWrapper(object): - + def __init__(self, calendar=None, zones=None): self.calendar = calendar self.zones = zones assert self.calendar is not None or self.zones is not None - + + def getTZIDs(self): if self.calendar: return getTZIDs(self.calendar) elif self.zones: return self.zones.getZoneNames() - + + def expandTransitions(self, tzid, start, end): if self.calendar: return getExpandedDates(self.calendar, tzid, start, end) elif self.zones: return self.zones.expandZone(tzid, start.getYear(), end.getYear()) + + def compareCalendars(calendar1, calendar2, start, end, filterTzids=(), verbose=False, quiet=False): - + # Get all TZIDs from the calendar tzids1 = calendar1.getTZIDs() tzids2 = calendar2.getTZIDs() - + # Find TZIDs that do not have a corresponding zone missing = tzids1.difference(tzids2) if missing: print """TZIDs in calendar 1 not in calendar 2 files: %s These cannot be checked.""" % (", ".join(missing),) - + for tzid in tzids1.intersection(tzids2): if filterTzids and tzid not in filterTzids: continue @@ -130,7 +140,9 @@ print "In calendar 2 but not in calendar 1 tzid=%s: %s" % (tzid, formattedExpandedDates(d2),) if not d1 and not d2 and not quiet: print "Matched: %s" % (tzid,) - + + + def getTZIDs(cal): results = set() @@ -141,20 +153,28 @@ return results + + def getExpandedDates(cal, tzid, start, end): - + db = cal.getVTimezoneDB() return db[tzid].expandAll(start, end) + + def sortedList(setdata): l = list(setdata) - l.sort(cmp=lambda x,y: PyCalendarDateTime.sort(x[0], y[0])) + l.sort(cmp=lambda x, y: PyCalendarDateTime.sort(x[0], y[0])) return l + + def formattedExpandedDates(expanded): items = sortedList([(item[0], secondsToTime(item[1]), secondsToTime(item[2]),) for item in expanded]) return ", ".join(["(%s, %s, %s)" % item for item in items]) + + def secondsToTime(seconds): if seconds < 0: seconds = -seconds @@ -162,13 +182,15 @@ else: negative = "" secs = divmod(seconds, 60)[1] - mins = divmod(seconds/60, 60)[1] - hours = divmod(seconds/(60*60), 60)[1] + mins = divmod(seconds / 60, 60)[1] + hours = divmod(seconds / (60 * 60), 60)[1] if secs: return "%s%02d:%02d:%02d" % (negative, hours, mins, secs,) else: return "%s%02d:%02d" % (negative, hours, mins,) + + def usage(error_msg=None): if error_msg: print error_msg @@ -204,7 +226,7 @@ endYear = 2018 zonedir1 = None zonedir2 = None - + options, args = getopt.getopt(sys.argv[1:], "hvq", ["start=", "end=", ]) for option, value in options: @@ -239,7 +261,7 @@ "australasia", "antarctica", ) - + skips = ( #"Europe/Sofia", #"Africa/Cairo", @@ -253,8 +275,7 @@ checkcalendar2, start, end, - filterTzids= - ( + filterTzids=( #"America/Goose_Bay", ), verbose=verbose, diff -Nru pycalendar-2.0~svn188/src/zonal/utils.py pycalendar-2.0~svn13177/src/zonal/utils.py --- pycalendar-2.0~svn188/src/zonal/utils.py 2011-05-02 15:49:55.000000000 +0000 +++ pycalendar-2.0~svn13177/src/zonal/utils.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,17 +19,20 @@ A date-time object that wraps the tzdb wall-clock/utc style date-time information and that can generate appropriate localtime or UTC offsets based on Zone/Rule offsets. """ - - def __init__(self, dt, mode): + + def __init__(self, dt, mode): self.dt = dt self.mode = mode - + + def __repr__(self): return str(self.dt) + def compareDateTime(self, other): return self.dt.compareDateTime(other.dt) + def getLocaltime(self, offset, stdoffset): new_dt = self.dt.duplicate() if self.mode == "u": @@ -37,7 +40,8 @@ elif self.mode == "s": new_dt.offsetSeconds(-stdoffset + offset) return new_dt - + + def getUTC(self, offset, stdoffset): new_dt = self.dt.duplicate() if self.mode == "u": diff -Nru pycalendar-2.0~svn188/src/zonal/zone.py pycalendar-2.0~svn13177/src/zonal/zone.py --- pycalendar-2.0~svn188/src/zonal/zone.py 2011-05-02 15:49:55.000000000 +0000 +++ pycalendar-2.0~svn13177/src/zonal/zone.py 2013-01-26 18:24:59.000000000 +0000 @@ -1,12 +1,12 @@ ## -# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved. -# +# Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -36,34 +36,38 @@ """ A tzdata Zone object containing a set of ZoneRules """ - + def __init__(self): self.name = "" self.rules = [] + def __str__(self): return self.generate() + def __eq__(self, other): return other and ( self.name == other.name and self.rules == other.rules ) + def __ne__(self, other): return not self.__eq__(other) + def parse(self, lines): """ Parse the Zone lines from tzdata. - + @param lines: the lines to parse. @type lines: C{str} """ - + # Parse one line at a time splitlines = lines.split("\n") - + # First line is special line = splitlines[0] splits = [x for x in line.expandtabs(1).split(" ") if len(x) > 0] @@ -78,14 +82,15 @@ rule.parse(line, 2) if rule.gmtoff != "#": self.rules.append(rule) - + + def generate(self): """ Generate a partial Zone line. - + @return: a C{str} with the Rule. """ - + lines = [] for count, rule in enumerate(self.rules): if count == 0: @@ -102,7 +107,8 @@ ) lines.append("\t".join(items)) return "\n".join(lines) - + + def expand(self, rules, minYear, maxYear): """ Expand this zone into a set of transitions. @@ -113,7 +119,7 @@ @type minYear: C{int} @param maxYear: ending year @type maxYear: C{int} - + @return: C{list} of C{tuple} for ( transition date-time, offset to, @@ -121,13 +127,13 @@ associated rule, ) """ - + # Start at 1/1/1800 with the offset from the initial zone rule start = PyCalendarDateTime(year=1800, month=1, day=1, hours=0, minutes=0, seconds=0) start_offset = self.rules[0].getUTCOffset() start_stdoffset = self.rules[0].getUTCOffset() startdt = start.duplicate() - + # Now add each zone rules dates transitions = [] lastUntilDateUTC = start.duplicate() @@ -138,15 +144,15 @@ last_offset, last_stdoffset = zonerule.expand(rules, transitions, lastUntilDateUTC, last_offset, last_stdoffset, maxYear) lastUntilDate = zonerule.getUntilDate() lastUntilDateUTC = lastUntilDate.getUTC(last_offset, last_stdoffset) - + # We typically don't care about the initial one if first and len(self.rules) > 1: transitions = [] first = False # Sort the results by date - transitions.sort(cmp=lambda x,y: x[0].compareDateTime(y[0])) - + transitions.sort(cmp=lambda x, y: x[0].compareDateTime(y[0])) + # Now scan transitions looking for real changes and note those results = [] last_transition = (startdt, start_offset, start_offset) @@ -164,13 +170,14 @@ else: results.append((last_transition[0], last_transition[1], last_transition[2], zonerule, None)) last_transition = (dt, to_offset, last_transition[2], rule) - + return results + def vtimezone(self, calendar, rules, minYear, maxYear): """ Generate a VTIMEZONE for this Zone. - + @param calendar: the L{PyCalendar} object for the VCALENDAR in which the VTIMEZONE will be created. @param rules: the C{dict} containing the set of Rules currently defined. @@ -178,23 +185,24 @@ in the VTIMEZONE. @return: C{vtimezone} component. """ - + # Get a VTIMEZONE component vtz = PyCalendarVTimezone(parent=calendar) - + # Add TZID property vtz.addProperty(PyCalendarProperty(definitions.cICalProperty_TZID, self.name)) vtz.addProperty(PyCalendarProperty("X-LIC-LOCATION", self.name)) - + transitions = self.expand(rules, minYear, maxYear) - + # Group rules lastZoneRule = None ruleorder = [] rulemap = {} - + + def _generateRuleData(): - # Generate VTIMEZONE component for last set of rules + # Generate VTIMEZONE component for last set of rules for rule in ruleorder: if rule: # Accumulate rule portions with the same offset pairs @@ -207,14 +215,14 @@ vtz, lastZoneRule, rulemap[rule][startIndex][0], - rulemap[rule][index-1][0], + rulemap[rule][index - 1][0], rulemap[rule][startIndex][1], rulemap[rule][startIndex][2], index - startIndex, ) lastOffsetPair = (rulemap[rule][index][1], rulemap[rule][index][2],) startIndex = index - + rule.vtimezone( vtz, lastZoneRule, @@ -237,7 +245,7 @@ rulemap.clear() for dt, offsetfrom, offsetto, zonerule, rule in transitions: - + # Check for change of rule - we ignore LMT's if zonerule.format != "LMT": if lastZoneRule and lastZoneRule != zonerule: @@ -246,7 +254,7 @@ ruleorder.append(rule) rulemap.setdefault(rule, []).append((dt, offsetfrom, offsetto,)) lastZoneRule = zonerule - + # Do left overs _generateRuleData() @@ -254,7 +262,8 @@ vtz.finalise() return vtz - + + def _compressRDateComponents(self, vtz): """ Compress sub-components with RDATEs into a single component with multiple @@ -276,7 +285,7 @@ ) if item.hasProperty(definitions.cICalProperty_RDATE): similarMap.setdefault(key, []).append(item) - + # Merge similar for values in similarMap.itervalues(): if len(values) > 1: @@ -287,21 +296,25 @@ mergeTo.addProperty(prop) vtz.mComponents.remove(mergeFrom) + + class ZoneRule(object): """ A specific rule for a portion of a Zone """ - + def __init__(self, zone): self.zone = zone self.gmtoff = 0 self.rule = "" self.format = "" self.until = None - + + def __str__(self): return self.generate() + def __eq__(self, other): return other and ( self.gmtoff == other.gmtoff and @@ -310,16 +323,18 @@ self.until == other.until ) + def __ne__(self, other): return not self.__eq__(other) + def parse(self, line, offset): """ Parse the Zone line from tzdata. - + @param line: a C{str} containing the line to parse. """ - + splits = [x for x in line.expandtabs(1).split(" ") if len(x) > 0] assert len(splits) + offset >= 5, "Help: %s" % (line,) self.gmtoff = splits[2 - offset] @@ -327,11 +342,12 @@ self.format = splits[4 - offset] if len(splits) >= 6 - offset: self.until = " ".join(splits[5 - offset:]) - + + def generate(self): """ Generate a partial Zone line. - + @return: a C{str} with the Rule. """ items = ( @@ -343,8 +359,9 @@ items = items + (self.until,) return "\t".join(items) + def getUntilDate(self): - + if hasattr(self, "_cached_until"): return self._cached_until @@ -395,13 +412,14 @@ self._cached_until = utils.DateTime(dt, mode) return self._cached_until + def getUTCOffset(self): - + if hasattr(self, "_cached_utc_offset"): return self._cached_uutc_offset splits = self.gmtoff.split(":") - + hours = int(splits[0] if splits[0][0] != "-" else splits[0][1:]) minutes = int(splits[1]) if len(splits) > 1 else 0 seconds = int(splits[2]) if len(splits) > 2 else 0 @@ -411,20 +429,21 @@ self._cached_uutc_offset = -self._cached_uutc_offset return self._cached_uutc_offset + def expand(self, rules, results, lastUntilUTC, lastOffset, lastStdOffset, maxYear): - + # Expand the rule - assert self.rule == "-" or self.rule[0].isdigit() or rules.has_key(self.rule), "No rule '%s' found in cache. %s for %s" % (self.rule, self, self.zone,) + assert self.rule == "-" or self.rule[0].isdigit() or self.rule in rules, "No rule '%s' found in cache. %s for %s" % (self.rule, self, self.zone,) if self.rule == "-" or self.rule[0].isdigit(): return self.expand_norule(results, lastUntilUTC, maxYear) else: tempresults = [] - + ruleset = rules[self.rule] - ruleset.expand(tempresults, self, maxYear) + ruleset.expand(tempresults, self, maxYear) # Sort the results by date - tempresults.sort(cmp=lambda x,y: x[0].compareDateTime(y[0])) + tempresults.sort(cmp=lambda x, y: x[0].compareDateTime(y[0])) found_one = False found_start = False @@ -442,7 +461,7 @@ dtutc = dt.getUTC(last_offset, last_stdoffset) results.append((lastUntilUTC, last_offset, self, None)) found_start = True - + if dtutc >= finalUntil.getUTC(last_offset, last_stdoffset): break @@ -454,9 +473,10 @@ if found_start == 0: results.append((lastUntilUTC, last_offset, self, None)) - + return last_offset, last_stdoffset + def expand_norule(self, results, lastUntil, maxYear): to_offset = 0 if self.rule[0].isdigit(): @@ -469,8 +489,9 @@ results.append((lastUntil, self.getUTCOffset() + to_offset, self, None)) return (self.getUTCOffset() + to_offset, self.getUTCOffset()) + def vtimezone(self, vtz, zonerule, start, end, offsetfrom, offsetto): - + # Determine type of component based on offset comp = PyCalendarVTimezoneStandard(parent=vtz) @@ -480,14 +501,14 @@ comp.addProperty(PyCalendarProperty(definitions.cICalProperty_TZOFFSETFROM, tzoffsetfrom)) comp.addProperty(PyCalendarProperty(definitions.cICalProperty_TZOFFSETTO, tzoffsetto)) - + # Do TZNAME if self.format.find("%") != -1: tzname = self.format % ("S",) else: tzname = self.format comp.addProperty(PyCalendarProperty(definitions.cICalProperty_TZNAME, tzname)) - + # Do DTSTART comp.addProperty(PyCalendarProperty(definitions.cICalProperty_DTSTART, start))