[CalendarServer-changes] [10554] PyCalendar/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Sat Jan 26 10:24:59 PST 2013
Revision: 10554
http://trac.calendarserver.org//changeset/10554
Author: cdaboo at apple.com
Date: 2013-01-26 10:24:59 -0800 (Sat, 26 Jan 2013)
Log Message:
-----------
Merge /branches/server to trunk.
Modified Paths:
--------------
PyCalendar/trunk/setup.py
PyCalendar/trunk/src/pycalendar/__init__.py
PyCalendar/trunk/src/pycalendar/attribute.py
PyCalendar/trunk/src/pycalendar/caladdressvalue.py
PyCalendar/trunk/src/pycalendar/calendar.py
PyCalendar/trunk/src/pycalendar/component.py
PyCalendar/trunk/src/pycalendar/componentbase.py
PyCalendar/trunk/src/pycalendar/componentexpanded.py
PyCalendar/trunk/src/pycalendar/componentrecur.py
PyCalendar/trunk/src/pycalendar/datetime.py
PyCalendar/trunk/src/pycalendar/datetimevalue.py
PyCalendar/trunk/src/pycalendar/definitions.py
PyCalendar/trunk/src/pycalendar/duration.py
PyCalendar/trunk/src/pycalendar/durationvalue.py
PyCalendar/trunk/src/pycalendar/freebusy.py
PyCalendar/trunk/src/pycalendar/integervalue.py
PyCalendar/trunk/src/pycalendar/itipdefinitions.py
PyCalendar/trunk/src/pycalendar/locale.py
PyCalendar/trunk/src/pycalendar/manager.py
PyCalendar/trunk/src/pycalendar/multivalue.py
PyCalendar/trunk/src/pycalendar/outputfilter.py
PyCalendar/trunk/src/pycalendar/period.py
PyCalendar/trunk/src/pycalendar/periodvalue.py
PyCalendar/trunk/src/pycalendar/plaintextvalue.py
PyCalendar/trunk/src/pycalendar/property.py
PyCalendar/trunk/src/pycalendar/recurrence.py
PyCalendar/trunk/src/pycalendar/recurrenceset.py
PyCalendar/trunk/src/pycalendar/recurrencevalue.py
PyCalendar/trunk/src/pycalendar/stringutils.py
PyCalendar/trunk/src/pycalendar/tests/__init__.py
PyCalendar/trunk/src/pycalendar/textvalue.py
PyCalendar/trunk/src/pycalendar/timezone.py
PyCalendar/trunk/src/pycalendar/urivalue.py
PyCalendar/trunk/src/pycalendar/utcoffsetvalue.py
PyCalendar/trunk/src/pycalendar/utils.py
PyCalendar/trunk/src/pycalendar/valarm.py
PyCalendar/trunk/src/pycalendar/value.py
PyCalendar/trunk/src/pycalendar/vevent.py
PyCalendar/trunk/src/pycalendar/vfreebusy.py
PyCalendar/trunk/src/pycalendar/vjournal.py
PyCalendar/trunk/src/pycalendar/vtimezone.py
PyCalendar/trunk/src/pycalendar/vtimezonedaylight.py
PyCalendar/trunk/src/pycalendar/vtimezoneelement.py
PyCalendar/trunk/src/pycalendar/vtimezonestandard.py
PyCalendar/trunk/src/pycalendar/vtodo.py
Added Paths:
-----------
PyCalendar/trunk/src/pycalendar/adr.py
PyCalendar/trunk/src/pycalendar/adrvalue.py
PyCalendar/trunk/src/pycalendar/available.py
PyCalendar/trunk/src/pycalendar/binaryvalue.py
PyCalendar/trunk/src/pycalendar/exceptions.py
PyCalendar/trunk/src/pycalendar/icalendar/
PyCalendar/trunk/src/pycalendar/icalendar/__init__.py
PyCalendar/trunk/src/pycalendar/icalendar/tests/
PyCalendar/trunk/src/pycalendar/icalendar/tests/__init__.py
PyCalendar/trunk/src/pycalendar/icalendar/tests/test_validation.py
PyCalendar/trunk/src/pycalendar/icalendar/validation.py
PyCalendar/trunk/src/pycalendar/n.py
PyCalendar/trunk/src/pycalendar/nvalue.py
PyCalendar/trunk/src/pycalendar/orgvalue.py
PyCalendar/trunk/src/pycalendar/parser.py
PyCalendar/trunk/src/pycalendar/requeststatusvalue.py
PyCalendar/trunk/src/pycalendar/tests/test_adr.py
PyCalendar/trunk/src/pycalendar/tests/test_adrvalue.py
PyCalendar/trunk/src/pycalendar/tests/test_calendar.py
PyCalendar/trunk/src/pycalendar/tests/test_componentrecur.py
PyCalendar/trunk/src/pycalendar/tests/test_datetime.py
PyCalendar/trunk/src/pycalendar/tests/test_duration.py
PyCalendar/trunk/src/pycalendar/tests/test_i18n.py
PyCalendar/trunk/src/pycalendar/tests/test_multivalue.py
PyCalendar/trunk/src/pycalendar/tests/test_n.py
PyCalendar/trunk/src/pycalendar/tests/test_nvalue.py
PyCalendar/trunk/src/pycalendar/tests/test_orgvalue.py
PyCalendar/trunk/src/pycalendar/tests/test_property.py
PyCalendar/trunk/src/pycalendar/tests/test_recurrence.py
PyCalendar/trunk/src/pycalendar/tests/test_requeststatus.py
PyCalendar/trunk/src/pycalendar/tests/test_timezone.py
PyCalendar/trunk/src/pycalendar/tests/test_urivalue.py
PyCalendar/trunk/src/pycalendar/tests/test_utils.py
PyCalendar/trunk/src/pycalendar/tests/test_validation.py
PyCalendar/trunk/src/pycalendar/tests/test_xml.py
PyCalendar/trunk/src/pycalendar/timezonedb.py
PyCalendar/trunk/src/pycalendar/unknownvalue.py
PyCalendar/trunk/src/pycalendar/validation.py
PyCalendar/trunk/src/pycalendar/valueutils.py
PyCalendar/trunk/src/pycalendar/vavailability.py
PyCalendar/trunk/src/pycalendar/vcard/
PyCalendar/trunk/src/pycalendar/vcard/__init__.py
PyCalendar/trunk/src/pycalendar/vcard/card.py
PyCalendar/trunk/src/pycalendar/vcard/definitions.py
PyCalendar/trunk/src/pycalendar/vcard/property.py
PyCalendar/trunk/src/pycalendar/vcard/tests/
PyCalendar/trunk/src/pycalendar/vcard/tests/__init__.py
PyCalendar/trunk/src/pycalendar/vcard/tests/test_card.py
PyCalendar/trunk/src/pycalendar/vcard/tests/test_property.py
PyCalendar/trunk/src/pycalendar/vcard/tests/test_validation.py
PyCalendar/trunk/src/pycalendar/vcard/validation.py
PyCalendar/trunk/src/pycalendar/vunknown.py
PyCalendar/trunk/src/pycalendar/xmldefs.py
PyCalendar/trunk/src/zonal/
PyCalendar/trunk/src/zonal/__init__.py
PyCalendar/trunk/src/zonal/rule.py
PyCalendar/trunk/src/zonal/tests/
PyCalendar/trunk/src/zonal/tests/__init__.py
PyCalendar/trunk/src/zonal/tests/test_rule.py
PyCalendar/trunk/src/zonal/tests/test_zone.py
PyCalendar/trunk/src/zonal/tzconvert.py
PyCalendar/trunk/src/zonal/tzdump.py
PyCalendar/trunk/src/zonal/tzverify.py
PyCalendar/trunk/src/zonal/utils.py
PyCalendar/trunk/src/zonal/zone.py
Removed Paths:
-------------
PyCalendar/trunk/src/pycalendar/componentdb.py
PyCalendar/trunk/src/pycalendar/dummyvalue.py
PyCalendar/trunk/src/pycalendar/icalendar/__init__.py
PyCalendar/trunk/src/pycalendar/icalendar/tests/
PyCalendar/trunk/src/pycalendar/icalendar/tests/__init__.py
PyCalendar/trunk/src/pycalendar/icalendar/tests/test_validation.py
PyCalendar/trunk/src/pycalendar/icalendar/validation.py
PyCalendar/trunk/src/pycalendar/tests/calendar.py
PyCalendar/trunk/src/pycalendar/tests/duration.py
PyCalendar/trunk/src/pycalendar/vcard/__init__.py
PyCalendar/trunk/src/pycalendar/vcard/card.py
PyCalendar/trunk/src/pycalendar/vcard/definitions.py
PyCalendar/trunk/src/pycalendar/vcard/property.py
PyCalendar/trunk/src/pycalendar/vcard/tests/
PyCalendar/trunk/src/pycalendar/vcard/tests/__init__.py
PyCalendar/trunk/src/pycalendar/vcard/tests/test_card.py
PyCalendar/trunk/src/pycalendar/vcard/tests/test_property.py
PyCalendar/trunk/src/pycalendar/vcard/tests/test_validation.py
PyCalendar/trunk/src/pycalendar/vcard/validation.py
PyCalendar/trunk/src/zonal/__init__.py
PyCalendar/trunk/src/zonal/rule.py
PyCalendar/trunk/src/zonal/tests/
PyCalendar/trunk/src/zonal/tests/__init__.py
PyCalendar/trunk/src/zonal/tests/test_rule.py
PyCalendar/trunk/src/zonal/tests/test_zone.py
PyCalendar/trunk/src/zonal/tzconvert.py
PyCalendar/trunk/src/zonal/tzdump.py
PyCalendar/trunk/src/zonal/tzverify.py
PyCalendar/trunk/src/zonal/utils.py
PyCalendar/trunk/src/zonal/zone.py
Property Changed:
----------------
PyCalendar/trunk/
Property changes on: PyCalendar/trunk
___________________________________________________________________
Added: svn:ignore
+ stuff
.settings
Added: svn:mergeinfo
+ /PyCalendar/branches/duplicate-items:10464-10465
/PyCalendar/branches/revised-api-126:10457-10463
/PyCalendar/branches/server:10466-10553
Modified: PyCalendar/trunk/setup.py
===================================================================
--- PyCalendar/trunk/setup.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/setup.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2007 Cyrus Daboo. All rights reserved.
+# 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.
@@ -18,10 +18,14 @@
setup (
name = "pycalendar",
- version = "1.0",
- description = "iCalendar Library",
+ version = "2.0",
+ description = "iCalendar/vCard Library",
+ license = "Apache 2.0",
+ platforms = ["any"],
package_dir={'': 'src'},
packages = [
'pycalendar',
+ 'pycalendar.icalendar',
+ 'pycalendar.vcard',
]
)
Modified: PyCalendar/trunk/src/pycalendar/__init__.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/__init__.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/__init__.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -16,17 +16,20 @@
__all__ = [
"attribute",
+ "available",
+ "binaryvalue",
"caladdressvalue",
"calendar",
"datetime",
"datetimevalue",
"definitions",
- "dummyvalue",
"duration",
"durationvalue",
+ "exceptions",
"freebusy",
"integervalue",
"locale",
+ "manager",
"multivalue",
"period",
"periodvalue",
@@ -34,21 +37,39 @@
"property",
"recurrence",
"recurrencevalue",
+ "requeststatusvalue",
"textvalue",
"timezone",
+ "timezonedb",
+ "unknownvalue",
"urivalue",
"utcoffsetvalue",
- "utils",
+ "valarm",
"value",
"vevent",
"vfreebusy",
- "stringutils"
+ "vjournal",
+ "vtimezone",
+ "vtimezonedaylight",
+ "vtimezonestandard",
+ "vtodo",
+ "vunknown",
]
# Import these to register the values
-import caladdressvalue, datetimevalue, dummyvalue, durationvalue, \
- integervalue, multivalue, periodvalue, recurrencevalue, \
- 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
Copied: PyCalendar/trunk/src/pycalendar/adr.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/adr.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/adr.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/adr.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,127 @@
+##
+# 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.
+##
+
+# vCard ADR value
+
+from pycalendar import utils
+from pycalendar.valueutils import ValueMixin
+
+class Adr(ValueMixin):
+ """
+ mValue is a tuple of seven str or tuples of str
+ """
+
+ (
+ POBOX,
+ EXTENDED,
+ STREET,
+ LOCALITY,
+ REGION,
+ POSTALCODE,
+ COUNTRY,
+ MAXITEMS
+ ) = range(8)
+
+ 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):
+ 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
Copied: PyCalendar/trunk/src/pycalendar/adrvalue.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/adrvalue.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/adrvalue.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/adrvalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,51 @@
+##
+# 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.
+##
+
+# vCard ADR value
+
+from pycalendar.adr import Adr
+from pycalendar.value import PyCalendarValue
+
+class AdrValue(PyCalendarValue):
+
+ 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
+
+PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_ADR, AdrValue, None)
Modified: PyCalendar/trunk/src/pycalendar/attribute.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/attribute.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/attribute.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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,83 +18,111 @@
ICalendar attribute.
The attribute can consist of one or more values, all string.
-We optimise for the usual case of a single value by having a single string
-attribute in the class, and then an array for multi-values, which is None
-unless there is more than one value.
"""
+from pycalendar import xmldefs
+from pycalendar.utils import encodeParameterValue
+import xml.etree.cElementTree as XML
+
class PyCalendarAttribute(object):
- def __init__( self, name = None, value = None, copyit = None ):
-
- if name and value:
- self.mName = name
- self.mValue = value
- self.mValues = None
- elif copyit:
- self.mName = copyit.mName
- self.mValue = copyit.mValue
- if copyit.mValues is not None:
- self.mValues = [i for i in copyit.mValues]
- else:
- self.mValues = None
+ def __init__(self, name, value=None):
+ self.mName = name
+ if value is None:
+ self.mValues = []
+ elif isinstance(value, basestring):
+ self.mValues = [value]
else:
- self.mName = ''
- self.mValue = ''
- self.mValues = None
+ self.mValues = value
- def getName( self ):
+
+ def duplicate(self):
+ 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 __eq__(self, other):
+ 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 ):
+
+ def setName(self, name):
self.mName = name
- def getFirstValue( self ):
- if self.mValues is not None:
- return self.mValues[0]
- else:
- return self.mValue
- def getValues( self ):
+ def getFirstValue(self):
+ return self.mValues[0]
+
+
+ def getValues(self):
return self.mValues
- def setValues( self, values ):
+
+ def setValues(self, values):
self.mValues = values
- def addValue( self, value ):
- # See if switch from single to multi-value is needed
- if self.mValues is None:
- self.mValues = []
- self.mValues.append( self.mValue )
- self.mValue = None
- self.mValues.append( value )
- def generate( self, os ):
+ 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 )
- os.write( "=" )
+ os.write(self.mName)
- if self.mValues is None:
- # Write with quotation if required
- self.generateValue( os, self.mValue )
- else:
+ # 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( "," )
+ os.write(",")
# Write with quotation if required
- self.generateValue( os, s )
+ self.generateValue(os, s)
except:
# We ignore errors
pass
-
- def generateValue( self, os, str ):
+
+
+ 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,) )
+ if str.find(":") != -1 or str.find(";") != -1 or str.find(",") != -1:
+ os.write("\"%s\"" % (str,))
else:
- os.write( str )
+ os.write(str)
+
+
+ def writeXML(self, node, namespace):
+ param = XML.SubElement(node, xmldefs.makeTag(namespace, self.getName()))
+ for value in self.getValues():
+ # TODO: need to figure out proper value types
+ text = XML.SubElement(param, xmldefs.makeTag(namespace, xmldefs.value_text))
+ text.text = value
Copied: PyCalendar/trunk/src/pycalendar/available.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/available.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/available.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/available.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,90 @@
+##
+# 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.
+##
+
+from pycalendar import definitions
+from pycalendar.componentrecur import PyCalendarComponentRecur
+from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS
+
+class PyCalendarAvailable(PyCalendarComponentRecur):
+
+ propertyCardinality_1 = (
+ definitions.cICalProperty_DTSTAMP,
+ definitions.cICalProperty_DTSTART,
+ definitions.cICalProperty_UID,
+ )
+
+ propertyCardinality_0_1 = (
+ definitions.cICalProperty_CREATED,
+ definitions.cICalProperty_DESCRIPTION,
+ definitions.cICalProperty_GEO,
+ definitions.cICalProperty_LAST_MODIFIED,
+ definitions.cICalProperty_LOCATION,
+ definitions.cICalProperty_RECURRENCE_ID,
+ definitions.cICalProperty_RRULE,
+ definitions.cICalProperty_SEQUENCE,
+ definitions.cICalProperty_SUMMARY,
+ definitions.cICalProperty_DTEND,
+ definitions.cICalProperty_DURATION,
+ )
+
+ propertyValueChecks = ICALENDAR_VALUE_CHECKS
+
+ 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.
+ """
+
+ fixed, unfixed = super(PyCalendarAvailable, self).validate(doFix)
+
+ # 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
+ logProblem = "[%s] Properties must not both be present: %s, %s" % (
+ self.getType(),
+ definitions.cICalProperty_DTEND,
+ definitions.cICalProperty_DURATION,
+ )
+ if doFix:
+ self.removeProperties(definitions.cICalProperty_DTEND)
+ fixed.append(logProblem)
+ else:
+ unfixed.append(logProblem)
+
+ return fixed, unfixed
+
+
+ def sortedPropertyKeyOrder(self):
+ return (
+ definitions.cICalProperty_UID,
+ definitions.cICalProperty_RECURRENCE_ID,
+ definitions.cICalProperty_DTSTART,
+ definitions.cICalProperty_DURATION,
+ definitions.cICalProperty_DTEND,
+ )
Copied: PyCalendar/trunk/src/pycalendar/binaryvalue.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/binaryvalue.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/binaryvalue.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/binaryvalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,28 @@
+##
+# 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.
+##
+
+# iCalendar Binary value
+
+from pycalendar import xmldefs
+from pycalendar.plaintextvalue import PyCalendarPlainTextValue
+from pycalendar.value import PyCalendarValue
+
+class PyCalendarBinaryValue(PyCalendarPlainTextValue):
+
+ def getType(self):
+ return PyCalendarValue.VALUETYPE_BINARY
+
+PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_BINARY, PyCalendarBinaryValue, xmldefs.value_binary)
Modified: PyCalendar/trunk/src/pycalendar/caladdressvalue.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/caladdressvalue.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/caladdressvalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -16,12 +16,13 @@
# iCalendar UTC Offset value
-from plaintextvalue import PyCalendarPlainTextValue
-from value import PyCalendarValue
+from pycalendar import xmldefs
+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)
+PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_CALADDRESS, PyCalendarCalAddressValue, xmldefs.value_cal_address)
Modified: PyCalendar/trunk/src/pycalendar/calendar.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/calendar.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/calendar.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,25 +14,33 @@
# limitations under the License.
##
-from component import PyCalendarComponent
-from componentbase import PyCalendarComponentBase
-from componentdb import PyCalendarComponentDB
-from componentexpanded import PyCalendarComponentExpanded
-from componentrecur import PyCalendarComponentRecur
-from datetime import PyCalendarDateTime
-from freebusy import PyCalendarFreeBusy
-from period import PyCalendarPeriod
-from property import PyCalendarProperty
-from utils import readFoldedLine
-from valarm import PyCalendarVAlarm
-from vevent import PyCalendarVEvent
-from vfreebusy import PyCalendarVFreeBusy
-from vjournal import PyCalendarVJournal
-from vtimezone import PyCalendarVTimezone
-from vtimezonedaylight import PyCalendarVTimezoneDaylight
-from vtimezonestandard import PyCalendarVTimezoneStandard
-from vtodo import PyCalendarVToDo
-import definitions
+from cStringIO import StringIO
+from pycalendar import definitions, xmldefs
+from pycalendar.available import PyCalendarAvailable
+from pycalendar.componentbase import PyCalendarComponentBase
+from pycalendar.componentexpanded import PyCalendarComponentExpanded
+from pycalendar.componentrecur import PyCalendarComponentRecur
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.exceptions import PyCalendarInvalidData, \
+ PyCalendarValidationError
+from pycalendar.freebusy import PyCalendarFreeBusy
+from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS
+from pycalendar.parser import ParserContext
+from pycalendar.period import PyCalendarPeriod
+from pycalendar.property import PyCalendarProperty
+from pycalendar.utils import readFoldedLine
+from pycalendar.valarm import PyCalendarVAlarm
+from pycalendar.vavailability import PyCalendarVAvailability
+from pycalendar.vevent import PyCalendarVEvent
+from pycalendar.vfreebusy import PyCalendarVFreeBusy
+from pycalendar.vjournal import PyCalendarVJournal
+from pycalendar.vtimezone import PyCalendarVTimezone
+from pycalendar.vtimezonedaylight import PyCalendarVTimezoneDaylight
+from pycalendar.vtimezonestandard import PyCalendarVTimezoneStandard
+from pycalendar.vtodo import PyCalendarVToDo
+from pycalendar.vunknown import PyCalendarUnknownComponent
+import collections
+import xml.etree.cElementTree as XML
class PyCalendar(PyCalendarComponentBase):
@@ -43,133 +51,95 @@
FIND_EXACT = 0
FIND_MASTER = 1
- VEVENT = 0
- VTODO = 1
- VJOURNAL = 2
- VFREEBUSY = 3
- VTIMEZONE = 4
- MAXV = 5
-
- # static attributes
- sICalendars = {}
- sICalendarRefCtr = 1
- sICalendar = None
-
sProdID = "-//mulberrymail.com//Mulberry v4.0//EN"
sDomain = "mulberrymail.com"
@staticmethod
- def getICalendar(ref):
- if PyCalendar.sICalendars.has_key(ref):
- return PyCalendar.sICalendars[ref]
- else:
- return None
-
- @staticmethod
- def loadStatics():
- PyCalendar.initComponents()
-
- @staticmethod
def setPRODID(prodid):
PyCalendar.sProdID = prodid
+
@staticmethod
def setDomain(domain):
PyCalendar.sDomain = domain
- def __init__(self):
- super(PyCalendar, self).__init__()
+ propertyCardinality_1 = (
+ definitions.cICalProperty_PRODID,
+ definitions.cICalProperty_VERSION,
+ )
- self.mICalendarRef = ++PyCalendar.sICalendarRefCtr
- PyCalendar.sICalendars[self.mICalendarRef] = self
-
- self.mReadOnly = False
- self.mDirty = False
- self.mTotalReplace = False
+ propertyCardinality_0_1 = (
+ definitions.cICalProperty_CALSCALE,
+ definitions.cICalProperty_METHOD,
+ )
+ propertyValueChecks = ICALENDAR_VALUE_CHECKS
+
+ def __init__(self, parent=None, add_defaults=True):
+ super(PyCalendar, self).__init__(None)
+
self.mName = ""
self.mDescription = ""
-
- self.addDefaultProperties()
-
- # Special init for static item
- if self is PyCalendar.sICalendar:
- self.initDefaultTimezones()
+ self.mMasterComponentsByTypeAndUID = collections.defaultdict(lambda: collections.defaultdict(list))
+ self.mOverriddenComponentsByUID = collections.defaultdict(list)
- self.mV = []
- for _ignore_i in range(PyCalendar.MAXV):
- self.mV.append(PyCalendarComponentDB())
+ if add_defaults:
+ self.addDefaultProperties()
- # Special init for static item
- if self is PyCalendar.sICalendar:
- self.initDefaultTimezones()
-
- def close(self):
- # Clean up the map items
- for v in self.mV:
- v.close()
- def getRef(self):
- return self.mICalendarRef
+ def duplicate(self):
+ other = super(PyCalendar, self).duplicate()
+ other.mName = self.mName
+ other.mDescription = self.mDescription
+ return other
- def remove(self):
- # Broadcast closing before removing components
- # Broadcast_Message(eBroadcast_Closed, this)
-
- # Clean up the map items
- for v in self.mV:
- v.removeAllComponents()
-
- del PyCalendar.sICalendars[self.mICalendarRef]
+ 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 name.length():
+ if len(name):
self.ddProperty(PyCalendarProperty(definitions.cICalProperty_XWRCALNAME, name))
-
- # Mark as dirty
- self.setDirty()
-
- # Broadcast change
- #Broadcast_Message(eBroadcast_Edit, this)
+
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 description.length():
+ if len(description):
self.addProperty(PyCalendarProperty(definitions.cICalProperty_XWRCALDESC, description))
-
- # Mark as dirty
- self.setDirty()
-
- # Broadcast change
- #Broadcast_Message(eBroadcast_Edit, this)
+
def getMethod(self):
result = ""
if self.hasProperty(definitions.cICalProperty_METHOD):
@@ -179,397 +149,359 @@
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
+ a tuple containing two lists: the first describes problems that were fixed, the
+ 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,
+ definitions.cICalComponent_VEVENT,
+ definitions.cICalComponent_VTODO,
+ definitions.cICalComponent_VJOURNAL,
+ definitions.cICalComponent_VFREEBUSY,
+ definitions.cICalComponent_VAVAILABILITY,
+ )
+
+
+ def sortedPropertyKeyOrder(self):
+ return (
+ definitions.cICalProperty_VERSION,
+ definitions.cICalProperty_CALSCALE,
+ definitions.cICalProperty_METHOD,
+ 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
- GET_COMPONENT_PROPERTY = 2
- GET_SUB_COMPONENT_PROPERTY = 3
-
+
state = LOOK_FOR_VCALENDAR
-
+
# Get lines looking for start of calendar
- lines = ["", ""]
- comp = None
+ lines = [None, None]
+ comp = self
compend = None
- prevcomp = None
- prevcompend = None
- compmap = None
-
+ componentstack = []
+
while readFoldedLine(ins, lines):
-
+
line = lines[0]
+
if state == LOOK_FOR_VCALENDAR:
# Look for start
- if line == definitions.cICalComponent_BEGINVCALENDAR:
+ if line == self.getBeginDelimiter():
# Next state
state = GET_PROPERTY_OR_COMPONENT
-
+
# Indicate success at this point
result = True
+ # Handle blank line
+ elif len(line) == 0:
+ # 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)
+
elif state == GET_PROPERTY_OR_COMPONENT:
# Parse property or look for start of component
- if PyCalendar.sComponents.has_key(line):
+ if line.startswith("BEGIN:"):
+ # Push previous details to stack
+ componentstack.append((comp, compend,))
+
# Start a new component
- comp = PyCalendar.makeComponent(PyCalendar.sComponents[line].mID, self.getRef())
+ comp = PyCalendar.makeComponent(line[6:], comp)
compend = comp.getEndDelimiter()
-
- # Set the marker for the end of this component and the map to store it in
- compmap = self.getComponents(PyCalendar.sComponents[line].mType)
-
- # Change state
- state = GET_COMPONENT_PROPERTY
- elif line == definitions.cICalComponent_ENDVCALENDAR:
+ # Look for end of object
+ elif line == self.getEndDelimiter():
# Finalise the current calendar
self.finalise()
-
+
# Change state
state = LOOK_FOR_VCALENDAR
- else:
-
- # Parse attribute/value for top-level calendar item
- prop = PyCalendarProperty()
- if prop.parse(line):
-
- # Check for valid property
- if not self.validProperty(prop):
- return False
- elif not self.ignoreProperty(prop):
- self.addProperty(prop)
-
- elif state in (GET_COMPONENT_PROPERTY, GET_SUB_COMPONENT_PROPERTY):
-
# Look for end of current component
- if line == compend:
+ elif line == compend:
# Finalise the component (this caches data from the properties)
comp.finalise()
-
- # Check whether this is embedded
- if prevcomp is not None:
- # Embed component in parent and reset to use parent
- prevcomp.addComponent(comp)
- comp = prevcomp
- compend = prevcompend
- prevcomp = None
- prevcompend = None
-
- # Reset state
- state = GET_COMPONENT_PROPERTY
+ # Embed component in parent and reset to use parent
+ componentstack[-1][0].addComponent(comp)
+ comp, compend = componentstack.pop()
- else:
+ # Blank line
+ elif len(line) == 0:
+ # Raise if requested, otherwise just ignore
+ if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
+ raise PyCalendarInvalidData("iCalendar data has blank lines")
- # Check for valid component
- compmap.addComponent(comp)
- comp = None
- compend = None
- compmap = None
-
- # Reset state
- state = GET_PROPERTY_OR_COMPONENT
-
+ # Must be a property
else:
- # Look for start of embedded component (can only do once)
- if (state != GET_SUB_COMPONENT_PROPERTY) and PyCalendar.sEmbeddedComponents.has_key(line):
+ # Parse attribute/value for top-level calendar item
+ prop = PyCalendarProperty()
+ if prop.parse(line):
- # Start a new component (saving off the current one)
- prevcomp = comp
- prevcompend = compend
- comp = PyCalendar.makeComponent(PyCalendar.sEmbeddedComponents[line].mID, self.getRef())
- compend = comp.getEndDelimiter()
-
- # Reset state
- state = GET_SUB_COMPONENT_PROPERTY
+ # Check for valid property
+ if comp is self:
+ if not comp.validProperty(prop):
+ raise PyCalendarInvalidData("Invalid property", str(prop))
+ elif not comp.ignoreProperty(prop):
+ comp.addProperty(prop)
+ else:
+ comp.addProperty(prop)
- else:
+ # Check for truncated data
+ if state != LOOK_FOR_VCALENDAR:
+ raise PyCalendarInvalidData("iCalendar data not complete")
- # Parse attribute/value and store in component
- prop = PyCalendarProperty()
- if prop.parse(line):
- comp.addProperty(prop)
-
# We need to store all timezones in the static object so they can be accessed by any date object
- if self is not PyCalendar.sICalendar:
- PyCalendar.sICalendar.mergeTimezones(self)
-
+ 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, rurl, etag):
-
+
+
+ def parseComponent(self, ins):
+
result = None
-
+
LOOK_FOR_VCALENDAR = 0
GET_PROPERTY_OR_COMPONENT = 1
- GET_COMPONENT_PROPERTY = 2
- GET_SUB_COMPONENT_PROPERTY = 3
GOT_VCALENDAR = 4
-
+
state = LOOK_FOR_VCALENDAR
-
+
# Get lines looking for start of calendar
- lines = ["", ""]
- comp = None
- prevcomp = None
- compmap = None
+ lines = [None, None]
+ comp = self
+ compend = None
+ componentstack = []
got_timezone = False
-
+
while readFoldedLine(ins, lines):
if state == LOOK_FOR_VCALENDAR:
# Look for start
- if lines[0] == definitions.cICalComponent_BEGINVCALENDAR:
+ if lines[0] == self.getBeginDelimiter():
# Next state
state = GET_PROPERTY_OR_COMPONENT
+ # Handle blank line
+ elif len(lines[0]) == 0:
+ # 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])
+
elif state == GET_PROPERTY_OR_COMPONENT:
# Parse property or look for start of component
- if PyCalendar.sComponents.has_key(lines[0]):
+ if lines[0].startswith("BEGIN:"):
+ # Push previous details to stack
+ componentstack.append((comp, compend,))
+
# Start a new component
- comp = PyCalendar.makeComponent(PyCalendar.sComponents[lines[0]].mID, self.getRef())
-
+ 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
-
- # Set the marker for the end of this component and the map to store it in
- compmap = self.getComponents(PyCalendar.sComponents[lines[0]].mType)
-
+
# Look for timezone component to trigger timezone merge only if one is present
- if PyCalendar.sComponents[lines[0]].mType == PyCalendarComponent.eVTIMEZONE:
+ if comp.getType() == definitions.cICalComponent_VTIMEZONE:
got_timezone = True
-
- # Change state
- state = GET_COMPONENT_PROPERTY
- elif lines[0] == definitions.cICalComponent_ENDVCALENDAR:
+ elif lines[0] == self.getEndDelimiter():
# Change state
state = GOT_VCALENDAR
- else:
-
- # Ignore top-level items
- pass
-
- elif state in (GET_COMPONENT_PROPERTY, GET_SUB_COMPONENT_PROPERTY):
# Look for end of current component
- if lines[0] == comp.getEndDelimiter():
+ elif lines[0] == compend:
# Finalise the component (this caches data from the properties)
comp.finalise()
- comp.setRURL(rurl)
- comp.setETag(etag)
-
- # Check whether this is embedded
- if prevcomp is not None:
- # Embed component in parent and reset to use parent
- if not prevcomp.addComponent(comp):
- comp = None
- comp = prevcomp
- prevcomp = None
-
- # Reset state
- state = GET_COMPONENT_PROPERTY
+ # Embed component in parent and reset to use parent
+ componentstack[-1][0].addComponent(comp)
+ comp, compend = componentstack.pop()
- else:
+ # Blank line
+ elif len(lines[0]) == 0:
+ # Raise if requested, otherwise just ignore
+ if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
+ raise PyCalendarInvalidData("iCalendar data has blank lines")
- # Check for valid component
- if not compmap.addComponent(comp):
+ # Ignore top-level items
+ elif comp is self:
+ pass
- if result == comp:
- result = None
- comp = None
-
- comp = None
- compmap = None
-
- # Reset state
- state = GET_PROPERTY_OR_COMPONENT
-
+ # Must be a property
else:
- # Look for start of embedded component (can only do once)
- if (state != GET_SUB_COMPONENT_PROPERTY) and PyCalendar.sEmbeddedComponents.has_key(lines[0]):
+ # Parse attribute/value for top-level calendar item
+ prop = PyCalendarProperty()
+ if prop.parse(lines[0]):
- # Start a new component (saving off the current one)
- prevcomp = comp
- comp = PyCalendar.makeComponent(PyCalendar.sEmbeddedComponents[lines[0]].mID, self.getRef())
-
- # Reset state
- state = GET_SUB_COMPONENT_PROPERTY
-
- else:
-
- # Parse attribute/value and store in component
- prop = PyCalendarProperty
- if prop.parse(lines[0]):
+ # 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 and (self is not PyCalendar.sICalendar):
- PyCalendar.sICalendar.mergeTimezones(self)
-
+ if got_timezone:
+ from timezonedb import PyCalendarTimezoneDatabase
+ PyCalendarTimezoneDatabase.mergeTimezones(self, self.getComponents(definitions.cICalComponent_VTIMEZONE))
+
return result
-
- def generate(self, os, for_cache = False):
- # Make sure all required timezones are in this object
- self.includeTimezones()
-
- # Write header
- os.write(definitions.cICalComponent_BEGINVCALENDAR)
- os.write("\n")
-
- # Write properties (we always handle PRODID & VERSION)
- self.writeProperties(os)
-
- # Write out each type of component (not VALARMS which are embedded in others)
- # Do VTIMEZONES at the start
- self.generateDB(os, self.mV[PyCalendar.VTIMEZONE], for_cache)
- self.generateDB(os, self.mV[PyCalendar.VEVENT], for_cache)
- self.generateDB(os, self.mV[PyCalendar.VTODO], for_cache)
- self.generateDB(os, self.mV[PyCalendar.VJOURNAL], for_cache)
- self.generateDB(os, self.mV[PyCalendar.VFREEBUSY], for_cache)
-
- # Write trailer
- os.write(definitions.cICalComponent_ENDVCALENDAR)
- os.write("\n")
-
- def generateOne(self, os, comp):
- # Write header
- os.write(definitions.cICalComponent_BEGINVCALENDAR)
- os.write("\n")
-
- # Write properties (we always handle PRODID & VERSION)
- self.writeProperties(os)
-
- # Make sure each timezone is written out
- tzids = set()
- comp.getTimezones(tzids)
- for tzid in tzids:
- tz = self.getTimezone(tzid)
- if tz == None:
- # Find it in the static object
- tz = PyCalendar.sICalendar.getTimezone(tzid)
-
- if tz is not None:
- tz.generate(os, False)
-
- # Check for recurring component and potentially write out all instances
- if isinstance(comp, PyCalendarComponentRecur):
+ def addComponent(self, component):
+ """
+ Override to track components by UID.
+ """
+ super(PyCalendar, self).addComponent(component)
- # Write this one out first
- comp.generate(os, False)
-
- # Get list of all instances
- instances = self.getRecurrenceInstances(comp.getType(), comp.getUID())
-
- # Write each instance out
- for r in instances:
- # Write the component
- r.generate(os. False)
+ if isinstance(component, PyCalendarComponentRecur):
+ uid = component.getUID()
+ rid = component.getRecurrenceID()
+ if rid:
+ self.mOverriddenComponentsByUID[uid].append(component)
+ else:
+ self.mMasterComponentsByTypeAndUID[component.getType()][uid] = component
- else:
- # Write the component
- comp.generate(os, False)
-
- # Write trailer
- os.write(definitions.cICalComponent_ENDVCALENDAR)
- os.write("\n")
- # Get components
- def getVEventsDB(self):
- return self.mV[PyCalendar.VEVENT]
+ def removeComponent(self, component):
+ """
+ Override to track components by UID.
+ """
+ super(PyCalendar, self).removeComponent(component)
- def getVToDosDB(self):
- return self.mV[PyCalendar.VTODO]
+ if isinstance(component, PyCalendarComponentRecur):
+ uid = component.getUID()
+ rid = component.getRecurrenceID()
+ if rid:
+ self.mOverriddenComponentsByUID[uid].remove(component)
+ else:
+ del self.mMasterComponentsByTypeAndUID[component.getType()][uid]
- def getVJournalsDB(self):
- return self.mV[PyCalendar.VJOURNAL]
- def getVFreeBusyDB(self):
- return self.mV[PyCalendar.VFREEBUSY]
+ def getText(self, includeTimezones=False):
+ s = StringIO()
+ self.generate(s, includeTimezones=includeTimezones)
+ return s.getvalue()
- def getVTimezoneDB(self):
- return self.mV[PyCalendar.VTIMEZONE]
- def getAllDBs(self, list):
-
- list.extend(self.mV)
+ def generate(self, os, includeTimezones=False):
+ # Make sure all required timezones are in this object
+ if includeTimezones:
+ self.includeTimezones()
+ super(PyCalendar, self).generate(os)
- # Disconnected support
- def getETag(self):
- return self.mETag
- def setETag(self, etag):
- self.mETag = etag
+ def getTextXML(self, includeTimezones=False):
+ node = self.writeXML(includeTimezones)
+ return xmldefs.toString(node)
-# def getRecording(self):
-# return self.mRecordDB
-#
-# def clearRecording(self):
-# self.mRecordDB.clear()
-#
-# def needsSync(self):
-# return not self.mRecordDB.empty()
- #void ParseCache(istream& is)
- #void GenerateCache(ostream& os) const
+ 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.mV[PyCalendar.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.mV[PyCalendar.VTODO]:
+ for vtodo in self.getComponents(definitions.cICalComponent_VTODO):
# Filter out done (that were complted more than a day ago) or cancelled to dos if required
if only_due:
@@ -578,31 +510,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
- self.getComponents(type).getRecurrenceInstancesItems(uid, items)
+ items.extend(self.mOverriddenComponentsByUID.get(uid, ()))
+
def getRecurrenceInstancesIds(self, type, uid, ids):
# Get instances from list
- self.getComponents(type).getRecurrenceInstancesIds(uid, ids)
+ 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.mV[PyCalendar.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
@@ -610,7 +546,7 @@
self.getVEvents(period, list)
if len(list) == 0:
return
-
+
# Get start/end list for each non-all-day expanded components
dtstart = []
dtend = []
@@ -619,19 +555,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()
@@ -645,37 +581,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):
@@ -690,278 +627,44 @@
# 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)
-
- # Timezone lookups
- def mergeTimezones(self, cal):
- # Merge each timezone from other calendar
- for tz in cal.mV[PyCalendar.VTIMEZONE]:
- # See whether matching item is already installed
- if not self.mV[PyCalendar.VTIMEZONE].has_key(tz.getMapKey()):
- # Item does not already exist - so copy and add it
- copy = PyCalendarVTimezone(copyit=tz)
- self.mV[PyCalendar.VTIMEZONE].addComponent(copy)
-
- else:
- # Merge similar items
- self.mV[PyCalendar.VTIMEZONE][tz.getMapKey()].mergeTimezone(tz)
-
- def getTimezoneOffsetSeconds(self, timezone, dt):
+ def getTimezoneOffsetSeconds(self, tzid, dt):
# Find timezone that matches the name (which is the same as the map key)
- if self.mV[PyCalendar.VTIMEZONE].has_key(timezone):
- return self.mV[PyCalendar.VTIMEZONE][timezone].getTimezoneOffsetSeconds(dt)
- else:
- return 0
+ timezone = self.getTimezone(tzid)
+ return timezone.getTimezoneOffsetSeconds(dt) if timezone else 0
- def getTimezoneDescriptor(self, timezone, dt):
+
+ def getTimezoneDescriptor(self, tzid, dt):
# Find timezone that matches the name (which is the same as the map key)
- if self.mV[PyCalendar.VTIMEZONE].has_key(timezone):
- return self.mV[PyCalendar.VTIMEZONE][timezone].getTimezoneDescriptor(dt)
- else:
- return ""
+ timezone = self.getTimezone(tzid)
+ return timezone.getTimezoneDescriptor(dt) if timezone else ""
- def getTimezones(self, tzids):
- # Get all timezones in a list for sorting
- sorted = {}
- for tz in self.mV[PyCalendar.VTIMEZONE]:
- sorted.setdefault(tz.getSortKey(), []).append(tz)
-
- # Now add to list in sorted order
- for tzs in sorted.itervalues():
- for tz in tzs:
- tzids.append(tz.getID())
-
+
def getTimezone(self, tzid):
# Find timezone that matches the name (which is the same as the map key)
- if self.mV[PyCalendar.VTIMEZONE].has_key(tzid):
- return self.mV[PyCalendar.VTIMEZONE][tzid]
+ for timezone in self.getComponents(definitions.cICalComponent_VTIMEZONE):
+ if timezone.getID() == tzid:
+ return timezone
else:
return None
- # Add/remove components
- def changedComponent(self, comp):
- #Calendar has changed
- self.setDirty()
-
- # Record change
- #PyCalendarComponentRecord.recordAction(self.mRecordDB, comp, PyCalendarComponentRecord.eChanged)
-
- # Broadcast change
- #CComponentAction action(CComponentAction::eChanged, *this, *comp)
- #Broadcast_Message(eBroadcast_ChangedComponent, &action)
-
- def addNewVEvent(self, vevent, moved = False):
- # Do not init props if moving
- if not moved:
-
- # Make sure UID is set and unique
- uid = ""
- vevent.setUID(uid)
-
- # Init DTSTAMP to now
- vevent.initDTSTAMP()
-
- self.mV[PyCalendar.VEVENT].addComponent(vevent)
-
- # Calendar has changed
- self.setDirty()
-
- #Record change
- #PyICalendarComponentRecord.recordAction(self.mRecordDB, vevent, PyCalendarComponentRecord.eAdded)
-
- # Broadcast change
- #action = CComponentAction(CComponentAction.eAdded, self, vevent)
- #Broadcast_Message(eBroadcast_AddedComponent, &action)
-
- def removeVEvent(self, vevent, delete_it = True):
- # Record change before delete occurs
- #PyCalendarComponentRecord.recordAction(self.mRecordDB, vevent, PyCalendarComponentRecord.eRemoved)
-
- # Remove from the map (do not delete here - wait until after broadcast)
- self.mVEvent.removeComponent(vevent, False)
-
- # Calendar has changed
- self.setDirty()
-
- # Broadcast change
- #action = CComponentAction(CComponentAction.eRemoved, self, vevent)
- #Broadcast_Message(eBroadcast_RemovedComponent, &action)
-
- # Delete it here after all changes
- if delete_it:
- vevent = None
-
- def removeRecurringVEvent(self, vevent, recur):
- # Determine how to delete
- if recur == PyCalendar.REMOVE_ALL:
- # Remove the top-level master event
- self.removeVEvent(vevent.getTrueMaster())
-
- elif recur == PyCalendar.REMOVE_ONLY_THIS:
- # Simply exclude this instance from the top-level master vevent -
- # this works even if this instance is the top-level (first) one
- master = vevent.getTrueMaster()
-
- # NB the vevent will be deleted as part of this so cache the instance start before
- exclude = PyCalendarDateTime(copyit=vevent.getInstanceStart())
-
- # The start instance is the RECURRENCE-ID to exclude
- master.excludeRecurrence(exclude)
-
- # Tell it it has changed (i.e. bump sequence)
- master.changed()
-
- elif recur == PyCalendar.REMOVE_THIS_AND_FUTURE:
- # Simply exclude this instance from the master vevent
- master = vevent.getTrueMaster()
-
- # NB the vevent will be deleted as part of this so cache the instance start before
- exclude = PyCalendarDateTime(copyit=vevent.getInstanceStart())
-
- # The DTSTART specifies the recurrence that we exclude
- master.excludeFutureRecurrence(exclude)
-
- # Tell it it has changed (i.e. bump sequence)
- master.changed()
-
- # Calendar has changed
- self.setDirty()
-
- # Broadcast change
- #Broadcast_Message(eBroadcast_Edit, this)
-
-
- def addNewVToDo(self, vtodo, moved = False):
- # Do not init props if moving
- if not moved:
-
- # Make sure UID is set and unique
- uid = ""
- vtodo.setUID(uid)
-
- # Init DTSTAMP to now
- vtodo.initDTSTAMP()
-
- self.mVToDo.addComponent(vtodo)
-
- # Calendar has changed
- self.setDirty()
-
- # Record change
- #PyCalendarComponentRecord.recordAction(self.mRecordDB, vtodo, PyCalendarComponentRecord.eAdded)
-
- # Broadcast change
- #action = CComponentAction(CComponentAction.eAdded, self, vtodo)
- #Broadcast_Message(eBroadcast_AddedComponent, &action)
-
- def removeVToDo(self, vtodo, delete_it = True):
- # Record change before delete occurs
- #PyCalendarComponentRecord.recordAction(self.mRecordDB, vtodo, PyCalendarComponentRecord.eRemoved)
-
- # Remove from the map (do not delete here - wait until after broadcast)
- self.mVToDo.removeComponent(vtodo, False)
-
- # Calendar has changed
- self.setDirty()
-
- # Broadcast change
- #action = CComponentAction(CComponentAction.eRemoved, self, vtodo)
- #Broadcast_Message(eBroadcast_RemovedComponent, &action)
-
- # Delete it here after all changes
- if delete_it:
- vtodo = None
-
- def findComponent(self, orig, find = FIND_EXACT):
- # Based on original component type. If we have a component of one type with the same UID
- # as a component of another type something is really f*cked up!
- index = self.getComponentIndex(orig.getType())
- if index != -1:
- return self.findComponentDB(self.mV[index], orig, find)
- else:
- return None
-
- def addComponent(self, comp):
- # Based on original component type. If we have a component of one type with the same UID
- # as a component of another type something is really f*cked up!
- index = self.getComponentIndex(comp.getType())
- if index != -1:
- self.addComponentDB(self.mV[index], comp)
- else:
- comp = None
-
- def getComponentByKey(self, mapkey):
-
- for compdb in self.mV:
- result = self.getComponentByKeyDB(compdb, mapkey)
- if result is not None:
- return result
- else:
- return None
-
- def removeComponentByKey(self, mapkey):
-
- for compdb in self.mV:
- if self.removeComponentByKeyDB(compdb, mapkey):
- return
-
-
- def isReadOnly(self):
- return self.mReadOnly
-
- def setReadOnly(self, ro = True):
- self.mReadOnly = ro
-
- # Change state
- def isDirty(self):
- return self.mDirty
-
- def setDirty(self, dirty = True):
- self.mDirty = dirty
-
- def isTotalReplace(self):
- return self.mTotalReplace
-
- def setTotalReplace(self, replace = True):
- self.mTotalReplace = replace
-
- def getComponents(self, type):
- return self.mV[self.getComponentIndex(type)]
-
- def getComponentIndex(self, type):
- if type == PyCalendarComponent.eVEVENT:
- return PyCalendar.VEVENT
- elif type == PyCalendarComponent.eVTODO:
- return PyCalendar.VTODO
- elif type == PyCalendarComponent.eVJOURNAL:
- return PyCalendar.VJOURNAL
- elif type == PyCalendarComponent.eVFREEBUSY:
- return PyCalendar.VFREEBUSY
- elif type == PyCalendarComponent.eVTIMEZONE:
- return PyCalendar.VTIMEZONE
- else:
- return -1
-
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 generateDB(self, os, components, for_cache):
- for comp in components:
- comp.generate(os, for_cache)
+
def validProperty(self, prop):
if prop.getName() == definitions.cICalProperty_VERSION:
@@ -976,124 +679,49 @@
return False
return True
-
+
+
def ignoreProperty(self, prop):
- return prop.getName() in (definitions.cICalProperty_VERSION, definitions.cICalProperty_CALSCALE, definitions.cICalProperty_PRODID)
+ 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()
- self.includeTimezonesDB(self.mV[PyCalendar.VEVENT], tzids)
- self.includeTimezonesDB(self.mV[PyCalendar.VTODO], tzids)
- self.includeTimezonesDB(self.mV[PyCalendar.VJOURNAL], tzids)
- self.includeTimezonesDB(self.mV[PyCalendar.VFREEBUSY], tzids)
-
+ 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:
-
tz = self.getTimezone(tzid)
if tz is None:
-
# Find it in the static object
- tz = PyCalendar.sICalendar.getTimezone(tzid)
+ tz = PyCalendarTimezoneDatabase.getTimezone(tzid)
if tz is not None:
+ dup = tz.duplicate()
+ self.addComponent(dup)
- dup = PyCalendarVTimezone(copyit=tz)
- self.mV[PyCalendar.VTIMEZONE].addComponent(dup)
-
- def includeTimezonesDB(self, components, tzids):
- for comp in components:
- comp.getTimezones(tzids)
- def findComponentDB(self, db, orig, find = FIND_EXACT):
- if find == PyCalendar.FIND_EXACT:
- key = orig.getMapKey()
- else:
- key = orig.getMasterKey()
-
- if db.has_key(key):
- return db[key]
- else:
- return None
-
- def addComponentDB(self, db, comp):
- # Just add it without doing anything as this is a copy being made during sync'ing
- if not db.addComponent(comp):
- comp = None
-
- def getComponentByKeyDB(self, db, mapkey):
-
- if db.has_key(mapkey):
- return db[mapkey]
- else:
- return None
-
- def removeComponentByKeyDB(self, db, mapkey):
- if db.has_key(mapkey):
- db.removeComponent(mapkey, True)
- return True
- else:
- return False
-
- class SComponentRegister(object):
-
-
- def __init__(self, id, type):
- self.mID = id
- self.mType = type
-
- sComponents = {}
- sEmbeddedComponents = {}
-
@staticmethod
- def initComponents():
+ def makeComponent(compname, parent):
- if len(PyCalendar.sComponents) == 0:
- PyCalendar.sComponents[PyCalendarVEvent.getVBegin()] = PyCalendar.SComponentRegister(0, PyCalendarComponent.eVEVENT)
- PyCalendar.sComponents[PyCalendarVToDo.getVBegin()] = PyCalendar.SComponentRegister(1, PyCalendarComponent.eVTODO)
- PyCalendar.sComponents[PyCalendarVJournal.getVBegin()] = PyCalendar.SComponentRegister(2, PyCalendarComponent.eVJOURNAL)
- PyCalendar.sComponents[PyCalendarVFreeBusy.getVBegin()] = PyCalendar.SComponentRegister(3, PyCalendarComponent.eVFREEBUSY)
- PyCalendar.sComponents[PyCalendarVTimezone.getVBegin()] = PyCalendar.SComponentRegister(4, PyCalendarComponent.eVTIMEZONE)
-
- if len(PyCalendar.sEmbeddedComponents) == 0:
- PyCalendar.sEmbeddedComponents[PyCalendarVAlarm.getVBegin()] = PyCalendar.SComponentRegister(5, PyCalendarComponent.eVALARM)
- PyCalendar.sEmbeddedComponents[PyCalendarVTimezoneStandard.getVBegin()] = PyCalendar.SComponentRegister(6, PyCalendarComponent.eVTIMEZONE)
- PyCalendar.sEmbeddedComponents[PyCalendarVTimezoneDaylight.getVBegin()] = PyCalendar.SComponentRegister(7, PyCalendarComponent.eVTIMEZONE)
+ 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,
+ }
- @staticmethod
- def makeComponent(id, calendar):
-
- if id == 0:
- return PyCalendarVEvent(calendar=calendar)
-
- elif id == 1:
- return PyCalendarVToDo(calendar=calendar)
-
- elif id == 2:
- return PyCalendarVJournal(calendar=calendar)
-
- elif id == 3:
- return PyCalendarVFreeBusy(calendar=calendar)
-
- elif id == 4:
- return PyCalendarVTimezone(calendar=calendar)
-
- elif id == 5:
- return PyCalendarVAlarm(calendar=calendar)
-
- elif id == 6:
- return PyCalendarVTimezoneStandard(calendar=calendar)
-
- elif id == 7:
- return PyCalendarVTimezoneDaylight(calendar=calendar)
-
- else:
- return None
-
- @staticmethod
- def initDefaultTimezones():
- # Add default timezones to this (static) calendar
- pass
-
-PyCalendar.loadStatics()
-PyCalendar.sICalendar = PyCalendar()
+ try:
+ cls = mapper[compname]
+ return cls(parent=parent)
+ except KeyError:
+ return PyCalendarUnknownComponent(parent=parent, comptype=compname)
Modified: PyCalendar/trunk/src/pycalendar/component.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/component.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/component.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,134 +14,70 @@
# limitations under the License.
##
-from componentbase import PyCalendarComponentBase
-from datetime import PyCalendarDateTime
-from property import PyCalendarProperty
+from pycalendar import definitions
+from pycalendar import stringutils
+from pycalendar.componentbase import PyCalendarComponentBase
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.property import PyCalendarProperty
import os
-import definitions
-import stringutils
import time
+import uuid
class PyCalendarComponent(PyCalendarComponentBase):
uid_ctr = 1
- eVCALENDAR = -1
- eVEVENT = 0
- eVTODO = 1
- eVJOURNAL = 2
- eVFREEBUSY = 3
- eVTIMEZONE = 4
- eVALARM = 5
+ def __init__(self, parent=None):
- # Pseudo components
- eVTIMEZONESTANDARD = 6
- eVTIMEZONEDAYLIGHT = 7
+ super(PyCalendarComponent, self).__init__(parent)
+ self.mUID = ""
+ self.mSeq = 0
+ self.mOriginalSeq = 0
+ self.mChanged = False
- def __init__(self, calendar=None, copyit=None):
-
- if calendar:
- super(PyCalendarComponent, self).__init__()
- self.mCalendarRef = calendar
- self.mUID = ""
- self.mSeq = 0
- self.mOriginalSeq = 0
- self.mEmbedder = None
- self.mEmbedded = None
- self.mETag = None
- self.mRURL = None
- self.mChanged = False
- elif copyit:
- super(PyCalendarComponent, self).__init__(copyit=copyit)
- self.mCalendarRef = copyit.mCalendarRef
- self.mUID = copyit.mUID
- self.mSeq = copyit.mSeq
- self.mOriginalSeq = copyit.mOriginalSeq
-
- self.mEmbedder = None
- self.mEmbedded = None
- if copyit.mEmbedded != None:
- # Do deep copy of element list
- self.mEmbedded = []
- for iter in copyit.mEmbedded:
- self.mEmbedded.append(iter.clone_it())
- self.mEmbedded[-1].setEmbedder(self)
- self.mETag = copyit.mETag
- self.mRURL = copyit.mRURL
- self.mChanged = copyit.mChanged
+ def duplicate(self, parent=None, **args):
- def close(self):
+ other = super(PyCalendarComponent, self).duplicate(parent=parent, **args)
+ other.mUID = self.mUID
+ other.mSeq = self.mSeq
+ other.mOriginalSeq = self.mOriginalSeq
- self.mEmbedder = None
+ other.mChanged = self.mChanged
- # Also close sub-components if present
- if (self.mEmbedded != None):
- for iter in self.mEmbedded:
- iter.close()
- self.mEmbedded = None
+ return other
- def clone_it(self):
- raise NotImplemented
- def getType(self):
- raise NotImplemented
+ def __repr__(self):
+ return "%s: UID: %s" % (self.getType(), self.getMapKey(),)
- def getBeginDelimiter(self):
- raise NotImplemented
- def getEndDelimiter(self):
- raise NotImplemented
-
def getMimeComponentName(self):
- raise NotImplemented
+ raise NotImplementedError
- def addComponent(self, comp): #@UnusedVariable
- # Sub-classes decide what can be embedded
- return False
- def removeComponent(self, comp):
- if self.mEmbedded != None:
- try:
- self.mEmbedded.remove(comp)
- except ValueError:
- pass
+ def getMapKey(self):
+ if hasattr(self, "mMapKey"):
+ return self.mMapKey
+ elif self.mUID:
+ return self.mUID
+ else:
+ self.mMapKey = str(uuid.uuid4())
+ return self.mMapKey
- def hasEmbeddedComponent(self, type):
- if self.mEmbedded != None:
- for iter in self.mEmbedded:
- if iter.getType() == type:
- return True
- return False
- def getFirstEmbeddedComponent(self, type):
- if self.mEmbedded != None:
- for iter in self.mEmbedded:
- if iter.getType() == type:
- return iter
- return None
+ def getSortKey(self):
+ return self.getMapKey()
- def setEmbedder(self, embedder):
- self.mEmbedder = embedder
- def getEmbedder(self):
- return self.mEmbedder
-
- def setCalendar(self, ref):
- self.mCalendarRef = ref
-
- def getCalendar(self):
- return self.mCalendarRef
-
- def getMapKey(self):
- return self.mUID
-
def getMasterKey(self):
return self.mUID
+
def getUID(self):
return self.mUID
+
def setUID(self, uid):
if uid:
self.mUID = uid
@@ -181,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
@@ -192,52 +130,19 @@
prop = PyCalendarProperty(definitions.cICalProperty_SEQUENCE, self.mSeq)
self.addProperty(prop)
+
def getOriginalSeq(self):
return self.mOriginalSeq
- def getRURL(self):
- return self.mRURL
- def setRURL(self, rurl):
- self.mRURL = rurl
-
- def generateRURL(self):
- # Format is:
- #
- # <<hash code>> *("-0"> .ics
- if (self.mRURL is None) or (len(self.mRURL) == 0):
- # Generate hash code
- hash = ""
- hash += self.getMapKey()
- hash += ":"
- hash += str(self.getSeq())
- hash += ":"
-
- dt = self.loadValueDateTime(definitions.cICalProperty_DTSTAMP)
- if dt is not None:
- hash += dt.getText()
-
- self.mRURL = stringutils.md5digest(hash)
- else:
- # Strip off .ics
- if self.mRURL.endswith(".ics"):
- self.mRURL = self.mRURL[:-4]
-
- # Add trailer
- self.mRURL += "-0.ics"
-
- def getETag(self):
- return self.mETag
-
- def setETag(self, etag):
- self.mETag = etag
-
def getChanged(self):
return self.mChanged
+
def setChanged(self, changed):
self.mChanged = changed
+
def initDTSTAMP(self):
self.removeProperties(definitions.cICalProperty_DTSTAMP)
@@ -245,6 +150,7 @@
PyCalendarDateTime.getNowUTC())
self.addProperty(prop)
+
def updateLastModified(self):
self.removeProperties(definitions.cICalProperty_LAST_MODIFIED)
@@ -252,73 +158,16 @@
PyCalendarDateTime.getNowUTC())
self.addProperty(prop)
- def added(self):
- # Also add sub-components if present
- if self.mEmbedded != None:
- for iter in self.mEmbedded:
- iter.added()
- self.mChanged = True
-
- def removed(self):
- # Also remove sub-components if present
- if self.mEmbedded != None:
- for iter in self.mEmbedded:
- iter.removed()
-
- self.mChanged = True
-
- def duplicated(self):
- # Remove SEQ, UID, DTSTAMP
- # These will be re-created when it is added to the calendar
- self.removeProperties(definitions.cICalProperty_UID)
- self.removeProperties(definitions.cICalProperty_SEQUENCE)
- self.removeProperties(definitions.cICalProperty_DTSTAMP)
-
- # Remove the cached values as well
- self.mUID = ""
- self.mSeq = 0
- self.mOriginalSeq = 0
-
- # Also duplicate sub-components if present
- if self.mEmbedded != None:
- for iter in self.mEmbedded:
- iter.duplicated()
-
- # Reset CalDAV items
- self.mETag = None
- self.mRURL = None
- self.mChanged = True
-
- def changed(self):
- # Bump the sequence
- self.setSeq(self.getSeq() + 1)
-
- # Update last-modified
- self.updateLastModified()
-
- # Also change sub-components
- if self.mEmbedded != None:
- for iter in self.mEmbedded:
- iter.changed()
-
- self.mChanged = True
-
- # Mark calendar as dirty
- from calendar import PyCalendar
- cal = PyCalendar.getICalendar(self.getCalendar())
- if cal != None:
- cal.changedComponent(self)
-
def finalise(self):
# Get UID
temps = self.loadValueString(definitions.cICalProperty_UID)
- if temps != None:
+ if temps is not None:
self.mUID = temps
# Get SEQ
temp = self.loadValueInteger(definitions.cICalProperty_SEQUENCE)
- if temp != None:
+ if temp is not None:
self.mSeq = temp
# Cache the original sequence when the component is read in.
@@ -326,70 +175,11 @@
# same calendar
self.mOriginalSeq = self.mSeq
- # Get CalDAV info if present
- temps = self.loadPrivateValue(definitions.cICalProperty_X_PRIVATE_RURL)
- if temps != None:
- self.mRURL = temps
- temps = self.loadPrivateValue(definitions.cICalProperty_X_PRIVATE_ETAG)
- if temps != None:
- self.mETag = temps
def canGenerateInstance(self):
return True
- def generate(self, os, for_cache):
- # Header
- os.write(self.getBeginDelimiter())
- os.write("\n")
- # Write each property
- self.writeProperties(os)
-
- # Do private properties if caching
- if for_cache:
- if self.mRURL:
- self.writePrivateProperty(os,
- definitions.cICalProperty_X_PRIVATE_RURL,
- self.mRURL)
- if self.mETag:
- self.writePrivateProperty(os,
- definitions.cICalProperty_X_PRIVATE_ETAG,
- self.mETag)
-
- # Write each embedded component
- if self.mEmbedded != None:
- for iter in self.mEmbedded:
- iter.generate(os, for_cache)
-
- # Footer
- os.write(self.getEndDelimiter())
- os.write("\n")
-
- def generateFiltered(self, os, filter):
- # Header
- os.write(self.getBeginDelimiter())
- os.write("\n")
-
- # Write each property
- self.writePropertiesFiltered(os, filter)
-
- # Write each embedded component
- if self.mEmbedded != None:
- # Shortcut for alll sub-components
- if filter.isAllSubComponents():
- for iter in self.mEmbedded:
- iter.generate(os, False)
- elif filter.hasSubComponentFilters():
- for iter in self.mEmbedded:
- subcomp = iter
- subfilter = filter.getSubComponentFilter(self.getType())
- if subfilter != None:
- subcomp.generateFiltered(os, subfilter)
-
- # Footer
- os.write(self.getEndDelimiter())
- os.write("\n")
-
def getTimezones(self, tzids):
# Look for all date-time properties
for props in self.mProperties.itervalues():
Modified: PyCalendar/trunk/src/pycalendar/componentbase.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/componentbase.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/componentbase.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,123 +14,488 @@
# limitations under the License.
##
-from datetimevalue import PyCalendarDateTimeValue
-from periodvalue import PyCalendarPeriodValue
-from property import PyCalendarProperty
-from value import PyCalendarValue
+from cStringIO import StringIO
+from pycalendar import xmldefs
+from pycalendar.datetimevalue import PyCalendarDateTimeValue
+from pycalendar.periodvalue import PyCalendarPeriodValue
+from pycalendar.property import PyCalendarProperty
+from pycalendar.value import PyCalendarValue
+import xml.etree.cElementTree as XML
class PyCalendarComponentBase(object):
- def __init__(self, copyit=None):
- if copyit:
- self.mProperties = {}
- for propname, props in copyit.mProperties.iteritems():
- for prop in props:
- self.mProperties.setdefault(propname, []).append(PyCalendarProperty(prop))
+ # These are class attributes for sets of properties for testing cardinality constraints. The sets
+ # must contain property names.
+ propertyCardinality_1 = () # Must be present
+ 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 = []
+ self.mProperties = {}
+
+ # This is the set of checks we do by default for components
+ self.cardinalityChecks = (
+ self.check_cardinality_1,
+ self.check_cardinality_1_Fix_Empty,
+ self.check_cardinality_0_1,
+ self.check_cardinality_1_More,
+ )
+
+
+ def duplicate(self, **args):
+ other = self.__class__(**args)
+
+ for component in self.mComponents:
+ other.addComponent(component.duplicate(parent=other))
+
+ other.mProperties = {}
+ 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 __eq__(self, other):
+ 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:
+ theirs.remove(another)
+ break
+ else:
+ 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:
- self.mProperties = {}
+ return None
- def getProperties(self):
- return self.mProperties
+ def removeComponentByKey(self, key):
+
+ for component in self.mComponents:
+ if component.getMapKey() == key:
+ 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()
+ for component in tuple(self.mComponents):
+ if component.getType().upper() == compname:
+ self.removeComponent(component)
+ else:
+ self.mComponents = []
+
+
+ def sortedComponentNames(self):
+ return ()
+
+
+ def compareProperties(self, other):
+ mine = set()
+ for props in self.mProperties.values():
+ mine.update(props)
+ theirs = set()
+ for props in other.mProperties.values():
+ 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(), []).append(prop)
+ self.mProperties.setdefault(prop.getName().upper(), []).append(prop)
- def hasProperty(self, prop):
- return self.mProperties.has_key(prop)
- def countProperty(self, prop):
- return len(self.mProperties.get(prop, []))
+ def hasProperty(self, propname):
+ return propname.upper() in self.mProperties
- def findFirstProperty(self, prop):
- return self.mProperties.get(prop, [None])[0]
- def removeProperties(self, prop):
- if self.mProperties.has_key(prop):
- del self.mProperties[prop]
+ def countProperty(self, propname):
+ return len(self.mProperties.get(propname.upper(), []))
- def getPropertyInteger(self, prop, type = None):
+
+ def findFirstProperty(self, propname):
+ return self.mProperties.get(propname.upper(), [None])[0]
+
+
+ def removeProperty(self, prop):
+ 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 propname.upper() in self.mProperties:
+ del self.mProperties[propname.upper()]
+
+
+ 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 generate(self, os, for_cache):
- raise NotImplemented
+ def validate(self, doFix=False):
+ """
+ Validate the data in this component and optionally fix any problems. Return
+ a tuple containing two lists: the first describes problems that were fixed, the
+ 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():
+ for property in properties:
+ propname = property.getName().upper()
+ if propname in self.propertyValueChecks:
+ if not self.propertyValueChecks[propname](property):
+ # Cannot fix a bad property value
+ logProblem = "[%s] Property value incorrect: %s" % (self.getType(), propname,)
+ unfixed.append(logProblem)
+
+ # Validate all subcomponents
+ for component in self.mComponents:
+ morefixed, moreunfixed = component.validate(doFix)
+ fixed.extend(morefixed)
+ unfixed.extend(moreunfixed)
+
+ 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
+ logProblem = "[%s] Too many required property: %s" % (self.getType(), propname)
+ unfixed.append(logProblem)
+ elif self.countProperty(propname) == 0: # Possibly fix by adding empty property
+ logProblem = "[%s] Missing required property: %s" % (self.getType(), propname)
+ if doFix:
+ self.addProperty(PyCalendarProperty(propname, ""))
+ fixed.append(logProblem)
+ 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())
+ os.write("\r\n")
+
+ # Write each property
+ self.writeProperties(os)
+
+ # Write each embedded component based on specific order
+ self.writeComponents(os)
+
+ # Footer
+ os.write(self.getEndDelimiter())
+ os.write("\r\n")
+
+
def generateFiltered(self, os, filter):
- raise NotImplemented
+ # Header
+ os.write(self.getBeginDelimiter())
+ os.write("\r\n")
+ # Write each property
+ self.writePropertiesFiltered(os, filter)
+
+ # Write each embedded component based on specific order
+ self.writeComponentsFiltered(os, filter)
+
+ # Footer
+ 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()):
+ sortedcomponents.append(component)
+
+ # 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
+ for component in self.sortedComponents():
+ component.generate(os)
+
+
+ def writeComponentsFiltered(self, os, filter):
+ # Shortcut for all sub-components
+ if filter.isAllSubComponents():
+ self.writeComponents(os)
+ elif filter.hasSubComponentFilters():
+ for subcomp in self.sortedcomponents():
+ 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
+ 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)
+ elif filter.hasSubComponentFilters():
+ for subcomp in self.sortedcomponents():
+ subfilter = filter.getSubComponentFilter(subcomp.getType())
+ if subfilter is not None:
+ subcomp.writeFilteredXML(comps, namespace, subfilter)
+
+
def loadValue(self, value_name):
- if self.getProperties().has_key(value_name):
- return self.getProperties()[value_name][0]
+ if self.hasProperty(value_name):
+ return self.findFirstProperty(value_name)
return None
+
def loadValueInteger(self, value_name, type=None):
if type:
- if self.getProperties().has_key(value_name):
+ if self.hasProperty(value_name):
if type == PyCalendarValue.VALUETYPE_INTEGER:
- ivalue = self.getProperties()[value_name][0].getIntegerValue()
- if ivalue != None:
+ ivalue = self.findFirstProperty(value_name).getIntegerValue()
+ if ivalue is not None:
return ivalue.getValue()
elif type == PyCalendarValue.VALUETYPE_UTC_OFFSET:
- uvalue = self.getProperties()[value_name][0].getUTCOffsetValue()
- if (uvalue != None):
+ 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.getProperties().has_key(value_name):
- tvalue = self.getProperties()[value_name][0].getTextValue()
- if (tvalue != None):
+ if self.hasProperty(value_name):
+ tvalue = self.findFirstProperty(value_name).getTextValue()
+ if (tvalue is not None):
return tvalue.getValue()
return None
+
def loadValueDateTime(self, value_name):
- if self.getProperties().has_key(value_name):
- dtvalue = self.getProperties()[value_name][0].getDateTimeValue()
- if dtvalue != None:
+ if self.hasProperty(value_name):
+ dtvalue = self.findFirstProperty(value_name).getDateTimeValue()
+ if dtvalue is not None:
return dtvalue.getValue()
return None
+
def loadValueDuration(self, value_name):
- if self.getProperties().has_key(value_name):
- dvalue = self.getProperties()[value_name][0].getDurationValue()
- if (dvalue != None):
+ if self.hasProperty(value_name):
+ dvalue = self.findFirstProperty(value_name).getDurationValue()
+ if (dvalue is not None):
return dvalue.getValue()
return None
+
def loadValuePeriod(self, value_name):
- if self.getProperties().has_key(value_name):
- pvalue = self.getProperties()[value_name][0].getPeriodValue()
- if (pvalue != None):
+ if self.hasProperty(value_name):
+ pvalue = self.findFirstProperty(value_name).getPeriodValue()
+ if (pvalue is not None):
return pvalue.getValue()
return None
+
def loadValueRRULE(self, value_name, value, add):
# Get RRULEs
- if self.getProperties().has_key(value_name):
+ if self.hasProperty(value_name):
items = self.getProperties()[value_name]
for iter in items:
rvalue = iter.getRecurrenceValue()
- if (rvalue != None):
+ if (rvalue is not None):
if add:
value.addRule(rvalue.getValue())
else:
@@ -139,13 +504,13 @@
else:
return False
+
def loadValueRDATE(self, value_name, value, add):
# Get RDATEs
- if self.getProperties().has_key(value_name):
- items = self.getProperties()[value_name]
- for iter in items:
+ if self.hasProperty(value_name):
+ for iter in self.getProperties(value_name):
mvalue = iter.getMultiValue()
- if (mvalue != None):
+ if (mvalue is not None):
for obj in mvalue.getValues():
# cast to date-time
if isinstance(obj, PyCalendarDateTimeValue):
@@ -155,53 +520,101 @@
value.subtractDT(obj.getValue())
elif isinstance(obj, PyCalendarPeriodValue):
if add:
- value.addDT(obj.getValue().getStart())
+ value.addPeriod(obj.getValue().getStart())
else:
- value.subtractDT(obj.getValue().getStart())
+ value.subtractPeriod(obj.getValue().getStart())
return True
else:
return False
+
+ def sortedPropertyKeys(self):
+ keys = self.mProperties.keys()
+ keys.sort()
+
+ results = []
+ for skey in self.sortedPropertyKeyOrder():
+ if skey in keys:
+ results.append(skey)
+ keys.remove(skey)
+ results.extend(keys)
+ return results
+
+
+ def sortedPropertyKeyOrder(self):
+ return ()
+
+
def writeProperties(self, os):
# Sort properties by name
- keys = self.mProperties.keys()
- keys.sort()
+ keys = self.sortedPropertyKeys()
for key in keys:
props = self.mProperties[key]
for prop in props:
prop.generate(os)
+
def writePropertiesFiltered(self, os, filter):
# Sort properties by name
- keys = self.mProperties.keys()
- keys.sort()
+ keys = self.sortedPropertyKeys()
# Shortcut for all properties
if filter.isAllProperties():
for key in keys:
- props = self.mProperties[key]
- for prop in props:
+ for prop in self.getProperties(key):
prop.generate(os)
elif filter.hasPropertyFilters():
for key in keys:
- props = self.mProperties[key]
- for prop in props:
+ 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:
+ props = self.mProperties[key]
+ 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()
+
+ # Shortcut for all properties
+ if filter.isAllProperties():
+ for key in keys:
+ for prop in self.getProperties(key):
+ prop.writeXML(props, namespace)
+ elif filter.hasPropertyFilters():
+ for key in keys:
+ 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
result = self.loadValueString(value_name)
- if (result != None):
+ if (result is not None):
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
Deleted: PyCalendar/trunk/src/pycalendar/componentdb.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/componentdb.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/componentdb.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,178 +0,0 @@
-##
-# Copyright (c) 2007 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 componentrecur import PyCalendarComponentRecur
-from datetime import PyCalendarDateTime
-
-class PyCalendarComponentDB(object):
-
- def __init__(self, copyit=None):
- self.mItems = {}
- if copyit:
- self.mRecurMap = None
- if (copyit.mRecurMap != None):
- self.mRecurMap = {}
- for key, value in copyit.mRecurMap.items():
- list = []
- for item in value:
- list.append(PyCalendarDateTime(copyit=item))
- self.mRecurMap[key] = list
- else:
- self.mRecurMap = None
-
- def __iter__(self):
- return self.mItems.itervalues()
-
- def __getitem__(self, i):
- return self.mItems[i]
-
- def size(self):
- return len(self.mItems)
-
- def has_key(self, k):
- return self.mItems.has_key(k)
-
- def close(self):
- for value in self.mItems.values():
- # Tell component it is removed and delete it
- value.close()
-
- self.mItems.clear()
-
- def addComponent(self, comp):
- # Must have valid UID
- if (len(comp.getMapKey()) == 0):
- return False
-
- # Add the component to the calendar
- bresult = True
-
- # See if duplicate
- if self.mItems.has_key(comp.getMapKey()):
- result = self.mItems.get(comp.getMapKey())
-
- # Replace existing if sequence is higher
- if comp.getSeq() > result.getSeq():
- self.mItems[comp.getMapKey()] = comp
- bresult = True
- else:
- bresult = False
- else:
- self.mItems[comp.getMapKey()] = comp
- bresult = True
-
- # Now look for a recurrence component if it was added
- if bresult and isinstance(comp, PyCalendarComponentRecur):
-
- recur = comp
-
- # Add each overridden instance to the override map
- if recur.isRecurrenceInstance():
- # Look for existiung UID->RID map entry
- if self.mRecurMap == None:
- self.mRecurMap = {}
- found = self.mRecurMap.get(recur.getUID(), None)
- if found != None:
- # Add to existing list
- found.append(recur.getRecurrenceID())
- else:
- # Create new entry for this UID
- temp = []
- temp.append(recur.getRecurrenceID())
- self.mRecurMap[recur.getUID()] = temp
-
- # Now try and find the master component if it currently exists
- found2 = self.mItems.get(recur.getUID(), None)
- if found2 != None:
- # Tell the instance who its master is
- recur.setMaster(found2)
-
- # Make sure the master is sync'd with any instances that may have
- # been
- # added before it, those added after
- # will be sync'd when they are added
- elif self.mRecurMap != None:
- # See if master has an entry in the UID->RID map
- found = self.mRecurMap.get(comp.getUID(), None)
- if found != None:
- # Make sure each instance knows about its master
- for iter in found:
- # Get the instance
- instance = self.getRecurrenceInstance(comp.getUID(), iter)
- if instance != None:
- # Tell the instance who its master is
- instance.setMaster(recur)
-
- # Tell component it has now been added
- if bresult:
- comp.added()
-
- return bresult
-
- def removeComponent(self, comp):
- # Tell component it is removed
- comp.removed()
-
- # Only if present
- if self.mItems.has_key(comp.getMapKey()):
- del self.mItems[comp.getMapKey()]
-
- def removeAllComponents(self):
- for comp in self.mItems.values():
- # Tell component it is removed and delete it
- comp.removed()
- comp.close()
-
- self.clear()
-
- def changedComponent(self, comp):
- # Tell component it is changed
- comp.changed()
-
- def getRecurrenceInstancesIds(self, uid, ids):
- if self.mRecurMap == None:
- return
-
- # Look for matching UID in recurrence instance map
- found = self.mRecurMap.get(uid, None)
- if found != None:
- # Return the recurrence ids
- ids.extend(found)
- return
-
- return
-
- def getRecurrenceInstancesItems(self, uid, items):
- if self.mRecurMap == None:
- return
-
- # Look for matching UID in recurrence instance map
- found = self.mRecurMap.get(uid, None)
- if found != None:
- # Return all the recurrence ids
- for iter in found:
- # Look it up
- recur = self.getRecurrenceInstance(uid, iter)
- if recur != None:
- items.append(recur)
-
- def getRecurrenceInstance(self, uid, rid):
- found = self.mItems.get(PyCalendarComponentRecur.mapKey(uid, rid.getText()), None)
- if found != None:
- # Tell the instance who its master is
- return found
-
- return None
Modified: PyCalendar/trunk/src/pycalendar/componentexpanded.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/componentexpanded.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/componentexpanded.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,7 +14,7 @@
# limitations under the License.
##
-from datetime import PyCalendarDateTime
+from pycalendar.datetime import PyCalendarDateTime
class PyCalendarComponentExpanded(object):
@@ -37,6 +37,7 @@
else:
return e1.mInstanceStart < e2.mInstanceStart
+
@staticmethod
def sort_by_dtstart(e1, e2):
if e1.mInstanceStart == e2.mInstanceStart:
@@ -48,41 +49,56 @@
else:
return e1.mInstanceStart < e2.mInstanceStart
- def __init__(self, owner=None, rid=None, copyit=None):
- if owner is not None:
- self.mOwner = owner
- self.initFromOwner(rid)
- elif copyit is not None:
- self._copy_PyCalendarComponentExpanded(copyit)
+ 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:
#
@@ -116,7 +132,7 @@
if self.mOwner.hasEnd():
self.mInstanceEnd = self.mInstanceStart + (self.mOwner.getEnd() - self.mOwner.getStart())
else:
- self.mInstanceEnd = PyCalendarDateTime(copyit=self.mInstanceStart)
+ self.mInstanceEnd = self.mInstanceStart.duplicate()
self.mRecurring = True
@@ -140,9 +156,3 @@
self.mInstanceEnd = self.mInstanceStart + (self.mOwner.getEnd() - self.mOwner.getStart())
self.mRecurring = True
-
- def _copy_ICalendarComponentExpanded(self, copy):
- self.mOwner = copy.self.mOwner
- self.mInstanceStart = PyCalendarDateTime(copyit=copy.mInstanceStart)
- self.mInstanceEnd = PyCalendarDateTime(copyit=copy.mInstanceEnd)
- self.mRecurring = copy.mRecurring
Modified: PyCalendar/trunk/src/pycalendar/componentrecur.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/componentrecur.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/componentrecur.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,23 +14,33 @@
# limitations under the License.
##
-from component import PyCalendarComponent
-from componentexpanded import PyCalendarComponentExpanded
-from datetime import PyCalendarDateTime
-from property import PyCalendarProperty
-from recurrenceset import PyCalendarRecurrenceSet
-from utils import set_difference
-import definitions
+from pycalendar import definitions
+from pycalendar.component import PyCalendarComponent
+from pycalendar.componentexpanded import PyCalendarComponentExpanded
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.property import PyCalendarProperty
+from pycalendar.recurrenceset import PyCalendarRecurrenceSet
+from pycalendar.timezone import PyCalendarTimezone
+from pycalendar.utils import set_difference
+import uuid
class PyCalendarComponentRecur(PyCalendarComponent):
+ propertyCardinality_STATUS_Fix = (
+ definitions.cICalProperty_STATUS,
+ )
+
@staticmethod
- def mapKey(uid, rid = None):
- result = "u:" + uid
- if rid is not None:
- result += rid
- return result
+ def mapKey(uid, rid=None):
+ if uid:
+ result = "u:" + uid
+ if rid is not None:
+ result += rid
+ return result
+ else:
+ return None
+
@staticmethod
def sort_by_dtstart_allday(e1, e2):
@@ -50,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:
@@ -61,75 +72,90 @@
else:
return e1.self.mStart < e2.self.mStart
- def __init__(self, calendar=None, copyit=None):
- if calendar is not None:
- super(PyCalendarComponentRecur, self).__init__(calendar=calendar)
- self.mMaster = self
- self.mMapKey = None
- self.mSummary = None
- self.mStamp = PyCalendarDateTime()
- self.mHasStamp = False
- self.mStart = PyCalendarDateTime()
- self.mHasStart = False
- self.mEnd = PyCalendarDateTime()
- self.mHasEnd = False
- self.mDuration = False
- self.mHasRecurrenceID = False
- self.mAdjustFuture = False
- self.mAdjustPrior = False
- self.mRecurrenceID = None
- self.mRecurrences = None
- elif copyit is not None:
- super(PyCalendarComponentRecur, self).__init__(copyit)
-
- # Special determination of master
- if copyit.recurring():
- self.mMaster = copyit.mMaster
- else:
- self.mMaster = self
-
- self.mMapKey = copyit.mMapKey
-
- self.mSummary = copyit.mSummary
-
- if (copyit.mStamp != None):
- self.mStamp = PyCalendarDateTime(copyit=copyit.mStamp)
- self.mHasStamp = copyit.mHasStamp
-
- self.mStart = PyCalendarDateTime(copyit=copyit.mStart)
- self.mHasStart = copyit.mHasStart
- self.mEnd = PyCalendarDateTime(copyit=copyit.mEnd)
- self.mHasEnd = copyit.mHasEnd
- self.mDuration = copyit.mDuration
-
- self.mHasRecurrenceID = copyit.mHasRecurrenceID
- self.mAdjustFuture = copyit.mAdjustFuture
- self.mAdjustPrior = copyit.mAdjustPrior
- if copyit.mRecurrenceID != None:
- self.mRecurrenceID = PyCalendarDateTime(copyit=copyit.mRecurrenceID)
-
- if copyit.mRecurrences != None:
- self.mRecurrences = PyCalendarRecurrenceSet(copyit=copyit.mRecurrences)
+ def __init__(self, parent=None):
+ super(PyCalendarComponentRecur, self).__init__(parent=parent)
+ self.mMaster = self
+ self.mMapKey = None
+ self.mSummary = None
+ self.mStamp = PyCalendarDateTime()
+ self.mHasStamp = False
+ self.mStart = PyCalendarDateTime()
+ self.mHasStart = False
+ self.mEnd = PyCalendarDateTime()
+ self.mHasEnd = False
+ self.mDuration = False
+ self.mHasRecurrenceID = False
+ self.mAdjustFuture = False
+ 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)
+
+ # Special determination of master
+ other.mMaster = self.mMaster if self.recurring() else self
+
+ other.mMapKey = self.mMapKey
+
+ other.mSummary = self.mSummary
+
+ if (self.mStamp is not None):
+ other.mStamp = self.mStamp.duplicate()
+ other.mHasStamp = self.mHasStamp
+
+ other.mStart = self.mStart.duplicate()
+ other.mHasStart = self.mHasStart
+ other.mEnd = self.mEnd.duplicate()
+ other.mHasEnd = self.mHasEnd
+ other.mDuration = self.mDuration
+
+ other.mHasRecurrenceID = self.mHasRecurrenceID
+ other.mAdjustFuture = self.mAdjustFuture
+ other.mAdjustPrior = self.mAdjustPrior
+ if self.mRecurrenceID is not None:
+ other.mRecurrenceID = self.mRecurrenceID.duplicate()
+
+ other._resetRecurrenceSet()
+
+ return other
+
+
def canGenerateInstance(self):
return not self.mHasRecurrenceID
+
def recurring(self):
- return (self.mMaster != None) and (self.mMaster != 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()
@@ -139,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 != None) and self.mRecurrences.hasRecurrence()
+ 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)
@@ -188,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)
@@ -202,6 +245,7 @@
else:
return ""
+
def getLocation(self):
# Get LOCATION
txt = self.loadValueString(definitions.cICalProperty_LOCATION)
@@ -210,6 +254,7 @@
else:
return ""
+
def finalise(self):
super(PyCalendarComponentRecur, self).finalise()
@@ -237,16 +282,13 @@
else:
# If no end or duration then use the start
self.mHasEnd = False
- self.mEnd = PyCalendarDateTime(copyit=self.mStart)
+ self.mEnd = self.mStart.duplicate()
self.mDuration = False
else:
self.mHasEnd = True
self.mEnd = temp
self.mDuration = False
- # Make sure start/end values are sensible
- self.FixStartEnd()
-
# Get SUMMARY
temp = self.loadValueString(definitions.cICalProperty_SUMMARY)
if temp is not None:
@@ -263,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:
@@ -272,35 +314,94 @@
else:
self.mMapKey = self.mapKey(self.mUID)
+ self._resetRecurrenceSet()
+
+
+ def validate(self, doFix=False):
+ """
+ Validate the data in this component and optionally fix any problems. Return
+ a tuple containing two lists: the first describes problems that were fixed, the
+ 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)
+
+ # Check that any UNTIL value matches that for DTSTART
+ if self.mHasStart and self.mRecurrences:
+ dtutc = self.mStart.duplicateAsUTC()
+ for rrule in self.mRecurrences.getRules():
+ if rrule.getUseUntil():
+ if rrule.getUntil().isDateOnly() ^ self.mStart.isDateOnly():
+ logProblem = "[%s] Value types must match: %s, %s" % (
+ self.getType(),
+ definitions.cICalProperty_DTSTART,
+ definitions.cICalValue_RECUR_UNTIL,
+ )
+ if doFix:
+ rrule.getUntil().setDateOnly(self.mStart.isDateOnly())
+ if not self.mStart.isDateOnly():
+ rrule.getUntil().setHHMMSS(dtutc.getHours(), dtutc.getMinutes(), dtutc.getSeconds())
+ rrule.getUntil().setTimezone(PyCalendarTimezone(utc=True))
+ self.mRecurrences.changed()
+ fixed.append(logProblem)
+ else:
+ unfixed.append(logProblem)
+
+ return fixed, unfixed
+
+
+ def check_cardinality_STATUS_Fix(self, fixed, unfixed, doFix):
+ """
+ Special for bug with STATUS where STATUS:CANCELLED is added alongside
+ another STATUS. In this case we want STATUS:CANCELLED to win.
+ """
+ for propname in self.propertyCardinality_STATUS_Fix:
+ if self.countProperty(propname) > 1:
+ logProblem = "[%s] Too many properties: %s" % (self.getType(), propname)
+ if doFix:
+ # Check that one of them is STATUS:CANCELLED
+ for prop in self.getProperties(propname):
+ if prop.getTextValue().getValue().upper() == definitions.cICalProperty_STATUS_CANCELLED:
+ self.removeProperties(propname)
+ self.addProperty(PyCalendarProperty(propname, definitions.cICalProperty_STATUS_CANCELLED))
+ fixed.append(logProblem)
+ break
+ else:
+ unfixed.append(logProblem)
+ else:
+ unfixed.append(logProblem)
+
+
+ def _resetRecurrenceSet(self):
# May need to create items
- 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_EXDATE) != 0)):
- if self.mRecurrences is None:
- self.mRecurrences = PyCalendarRecurrenceSet()
+ 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_EXDATE) != 0)):
+ self.mRecurrences = PyCalendarRecurrenceSet()
+
# Get RRULEs
- self.loadValueRRULE(definitions.cICalProperty_RRULE,
- self.mRecurrences, True)
+ self.loadValueRRULE(definitions.cICalProperty_RRULE, self.mRecurrences, True)
# Get RDATEs
- self.loadValueRDATE(definitions.cICalProperty_RDATE,
- self.mRecurrences, True)
+ self.loadValueRDATE(definitions.cICalProperty_RDATE, self.mRecurrences, True)
# Get EXRULEs
- self.loadValueRRULE(definitions.cICalProperty_EXRULE,
- self.mRecurrences, False)
+ self.loadValueRRULE(definitions.cICalProperty_EXRULE, self.mRecurrences, False)
# Get EXDATEs
- self.loadValueRDATE(definitions.cICalProperty_EXDATE,
- self.mRecurrences, False)
+ 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:
# Use the start
- self.mEnd = PyCalendarDateTime(copyit=self.mStart)
+ self.mEnd = self.mStart.duplicate()
self.mDuration = False
# Adjust to approriate non-inclusive end point
@@ -314,7 +415,8 @@
self.mEnd.offsetDay(1)
self.mEnd.setHHMMSS(0, 0, 0)
- def expandPeriod(self, period, list):
+
+ def expandPeriod(self, period, results):
# Check for recurrence and True master
if ((self.mRecurrences is not None) and self.mRecurrences.hasRecurrence()
and not self.isRecurrenceInstance()):
@@ -323,12 +425,11 @@
self.mRecurrences.expand(self.mStart, period, items)
# Look for overridden recurrence items
- from pycalendar.calendar import PyCalendar
- cal = PyCalendar.getICalendar(self.getCalendar())
+ cal = self.mParentComponent
if cal is not None:
# Remove recurrence instances from the list of items
recurs = []
- cal.getRecurrenceInstancesIds(PyCalendarComponent.eVEVENT, self.getUID(), recurs)
+ cal.getRecurrenceInstancesIds(definitions.cICalComponent_VEVENT, self.getUID(), recurs)
recurs.sort()
if len(recurs) != 0:
temp = []
@@ -337,7 +438,7 @@
# Now get actual instances
instances = []
- cal.getRecurrenceInstancesItems(PyCalendarComponent.eVEVENT, self.getUID(), instances)
+ cal.getRecurrenceInstancesItems(definitions.cICalComponent_VEVENT, self.getUID(), instances)
# Get list of each ones with RANGE
prior = []
@@ -352,7 +453,7 @@
if len(prior) + len(future) == 0:
# Add each expanded item
for iter in items:
- list.append(self.createExpanded(self, iter))
+ results.append(self.createExpanded(self, iter))
else:
# Sort each list first
prior.sort(self.sort_by_dtstart)
@@ -381,22 +482,23 @@
if slave is None:
slave = self
- list.append(self.createExpanded(slave, iter1))
+ results.append(self.createExpanded(slave, iter1))
else:
# Add each expanded item
for iter in items:
- list.append(self.createExpanded(self, iter))
+ results.append(self.createExpanded(self, iter))
elif self.withinPeriod(period):
if self.isRecurrenceInstance():
rid = self.mRecurrenceID
else:
rid = None
- list.append(PyCalendarComponentExpanded(self, rid))
+ results.append(PyCalendarComponentExpanded(self, rid))
+
def withinPeriod(self, period):
# Check for recurrence
- if ((self.mRecurrences != None) and self.mRecurrences.hasRecurrence()):
+ if ((self.mRecurrences is not None) and self.mRecurrences.hasRecurrence()):
items = []
self.mRecurrences.expand(self.mStart, period, items)
return len(items) != 0
@@ -408,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
@@ -421,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
@@ -441,6 +547,7 @@
self.removeProperties(definitions.cICalProperty_DURATION)
self.removeProperties(definitions.cICalProperty_DUE)
+
def editTimingDue(self, due):
# Updated cached values
self.mHasStart = False
@@ -459,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
@@ -477,12 +585,13 @@
self.addProperty(prop)
# If its an all day event and the end one day after the start, ignore it
- temp = PyCalendarDateTime(copyit=start)
+ temp = start.duplicate()
temp.offsetDay(1)
if not start.isDateOnly() or end != temp:
prop = PyCalendarProperty(definitions.cICalProperty_DTEND, end)
self.addProperty(prop)
+
def editTimingStartDuration(self, start, duration):
# Updated cached values
self.mHasStart = True
@@ -507,6 +616,7 @@
prop = PyCalendarProperty(definitions.cICalProperty_DURATION, duration)
self.addProperty(prop)
+
def editRecurrenceSet(self, recurs):
# Must have items
if self.mRecurrences is None:
@@ -535,6 +645,7 @@
prop = PyCalendarProperty(definitions.cICalProperty_EXDATE, iter)
self.addProperty(prop)
+
def excludeRecurrence(self, start):
# Must have items
if self.mRecurrences is None:
@@ -547,6 +658,7 @@
prop = PyCalendarProperty(definitions.cICalProperty_EXDATE, start)
self.addProperty(prop)
+
def excludeFutureRecurrence(self, start):
# Must have items
if self.mRecurrences is None:
@@ -567,6 +679,7 @@
prop = PyCalendarProperty(definitions.cICalProperty_RDATE, iter)
self.addProperty(prop)
+
def initFromMaster(self):
# Only if not master
if self.recurring():
@@ -596,5 +709,6 @@
temp = self.loadValueDuration(definitions.cICalProperty_DURATION)
self.mEnd = self.mStart + temp
+
def createExpanded(self, master, recurid):
return PyCalendarComponentExpanded(master, recurid)
Modified: PyCalendar/trunk/src/pycalendar/datetime.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/datetime.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/datetime.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,17 +14,19 @@
# limitations under the License.
##
+from pycalendar import definitions
+from pycalendar import locale
+from pycalendar import utils
+from pycalendar import xmldefs
+from pycalendar.duration import PyCalendarDuration
+from pycalendar.timezone import PyCalendarTimezone
+from pycalendar.valueutils import ValueMixin
import cStringIO as StringIO
import time
+import xml.etree.cElementTree as XML
-from duration import PyCalendarDuration
-from timezone import PyCalendarTimezone
-import definitions
-import locale
-import utils
+class PyCalendarDateTime(ValueMixin):
-class PyCalendarDateTime(object):
-
SUNDAY = 0
MONDAY = 1
TUESDAY = 2
@@ -32,7 +34,7 @@
THURSDAY = 4
FRIDAY = 5
SATURDAY = 6
-
+
FULLDATE = 0
ABBREVDATE = 1
NUMERICDATE = 2
@@ -45,8 +47,9 @@
return e1.compareDateTime(e2)
- def __init__( self, year = None, month = None, day = None, hours = None, minutes = None, seconds = None, tzid = None, packed = None, copyit = 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
@@ -57,8 +60,9 @@
self.mDateOnly = False
- self.mTZUTC = True
+ self.mTZUTC = False
self.mTZID = None
+ self.mTZOffset = None
self.mPosixTimeCached = False
self.mPosixTime = 0
@@ -76,100 +80,118 @@
if tzid:
self.mTZUTC = tzid.getUTC()
self.mTZID = tzid.getTimezoneID()
- elif (packed is not None):
- # Unpack a packed date
- self.mYear = utils.unpackDateYear( packed )
- self.mMonth = utils.unpackDateMonth( packed )
- self.mDay = utils.unpackDateDay( packed )
- if self.mDay < 0:
- self.mDay = -self.mDay
- self.mDateOnly = True
-
- if tzid:
- self.mTZUTC = tzid.getUTC()
- self.mTZID = tzid.getTimezoneID()
-
- self.normalise()
- elif (copyit is not None):
- self.mYear = copyit.mYear
- self.mMonth = copyit.mMonth
- self.mDay = copyit.mDay
-
- self.mHours = copyit.mHours
- self.mMinutes = copyit.mMinutes
- self.mSeconds = copyit.mSeconds
-
- self.mDateOnly = copyit.mDateOnly
-
- self.mTZUTC = copyit.mTZUTC
- self.mTZID = copyit.mTZID
-
- self.mPosixTimeCached = copyit.mPosixTimeCached
- self.mPosixTime = copyit.mPosixTime
-
+
+
+ def duplicate(self):
+ other = PyCalendarDateTime(self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds)
+
+ other.mDateOnly = self.mDateOnly
+
+ 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 self.getText()
+ return "PyCalendarDateTime: %s" % (self.getText(),)
+
def __hash__(self):
- return self.getPosixTime()
-
+ return hash(self.getPosixTime())
+
+
# Operators
- def __add__( self, duration ):
+ def __add__(self, duration):
# Add duration seconds to temp object and normalise it
- result = PyCalendarDateTime(copyit=self)
+ result = self.duplicate()
result.mSeconds += duration.getTotalSeconds()
- result.changed()
result.normalise()
return result
-
- def __sub__( self, date ):
- # Look for floating
- if self.floating() or date.floating():
- # Adjust the floating ones to fixed
- copy1 = PyCalendarDateTime(copyit=self)
- copy2 = PyCalendarDateTime(copyit=date)
- if copy1.floating() and copy2.floating():
- # Set both to UTC and do comparison
- copy1.setTimezoneUTC( True )
- copy2.setTimezoneUTC( True )
- return copy1 - copy2
- elif copy1.floating():
- # Set to be the same
- copy1.setTimezoneUTC( copy2.getTimezoneUTC() )
- copy1.setTimezoneID( copy2.getTimezoneID() )
- return copy1 - copy2
+
+ 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)
+ return copy1 - copy2
+ elif copy1.floating():
+ # Set to be the same
+ 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())
+ return copy1 - copy2
+
else:
- # Set to be the same
- 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)
+ # 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 __ge__( self, comp ):
- return self.compareDateTime( comp ) >= 0
+ def __ne__(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 __ge__(self, comp):
+ return self.compareDateTime(comp) >= 0
- def __lt__( self, comp ):
- return self.compareDateTime( comp ) < 0
- def compareDateTime( self, comp ):
+ 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 compareDateTime(self, comp):
+ if comp is None:
+ return 1
# If either are date only, then just do date compare
if self.mDateOnly or comp.mDateOnly:
if self.mYear == comp.mYear:
@@ -191,10 +213,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:
@@ -243,17 +265,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
@@ -261,56 +285,68 @@
# Adjust for timezone offset
result -= self.timeZoneSecondsOffset()
- # Now indcate cache state
+ # 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):
# 1 .. 366 offset from start, or
# -1 .. -366 offset from end
@@ -336,10 +372,12 @@
# Normalise to get proper year/month/day values
self.normalise()
- def getYearDay( self ):
- return self.mDay + utils.daysUptoMonth( self.mMonth, self.mYear )
- def setMonthDay( self, day ):
+ def getYearDay(self):
+ return self.mDay + utils.daysUptoMonth(self.mMonth, self.mYear)
+
+
+ def setMonthDay(self, day):
# 1 .. 31 offset from start, or
# -1 .. -31 offset from end
@@ -363,15 +401,17 @@
# Normalise to get proper year/month/day values
self.normalise()
- 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 ):
+
+ def setWeekNo(self, weekno):
# This is the iso 8601 week number definition
# What day does the current year start on
@@ -379,13 +419,14 @@
first_day = temp.getDayOfWeek()
# 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 )
+ 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)
- def getWeekNo( self ):
+
+ def getWeekNo(self):
# This is the iso 8601 week number definition
# What day does the current year start on
@@ -395,13 +436,14 @@
# Get days upto the current one
yearday = self.getYearDay()
- 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
+ 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:
@@ -411,7 +453,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
@@ -420,24 +463,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):
# Set to first day in month
self.mDay = 1
@@ -445,17 +489,17 @@
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
@@ -463,7 +507,8 @@
self.normalise()
- def setNextDayOfWeek( self, start, day ):
+
+ def setNextDayOfWeek(self, start, day):
# Set to first day in month
self.mDay = start
@@ -474,10 +519,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
@@ -488,13 +534,14 @@
# Create temp date-time with the appropriate parameters and then
# compare
- temp = PyCalendarDateTime(copyit=self)
- temp.setDayOfWeekInMonth( offset, day )
+ temp = self.duplicate()
+ 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
@@ -503,155 +550,196 @@
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 setHHMMSS( self, hours, minutes, seconds ):
- if ( self.mHours != hours ) or ( self.mMinutes != minutes ) or ( self.mSeconds != seconds ):
+ 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):
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 ):
- self.mTZUTC = utc
- def getTimezoneID( self ):
+ def setTimezoneUTC(self, utc):
+ if self.mTZUTC != utc:
+ self.mTZUTC = utc
+ self.changed()
+
+
+ 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
- #public ICalendarTimezone getTimezone() {
- # return mTimezone
- #}
- def setTimezone( self, tzid ):
+ 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):
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 ):
+ if (tzid.getUTC() != self.mTZUTC) or (s1 != self.mTZID):
offset_from = self.timeZoneSecondsOffset()
- self.setTimezone( tzid )
+ self.setTimezone(tzid)
offset_to = self.timeZoneSecondsOffset()
- self.offsetSeconds( offset_to - offset_from )
+ self.offsetSeconds(offset_to - offset_from)
+ return self
- def adjustToUTC( self ):
- if not self.mTZUTC:
+
+ 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_to - offset_from )
+ self.offsetSeconds(-offset_from)
- #def getAdjustedTime( self, tzid = PyCalendarManager.sPyCalendarManager.getDefaultTimezone() ):
- def getAdjustedTime( self, tzid = None ):
+ if tempPosix is not None:
+ self.mPosixTimeCached = True
+ self.mPosixTime = tempPosix
+
+ self.mTZOffset = 0
+
+ return self
+
+
+ def getAdjustedTime(self, tzid=None):
# Copy this and adjust to input timezone
- adjusted = PyCalendarDateTime(copyit=self)
- adjusted.adjustTimezone( tzid )
+ adjusted = self.duplicate()
+ 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 ):
+ 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, PyCalendarTimezone()][tzid == 0] )
+ 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)
-
- temp = 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 )
- return temp
- 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):
# Add appropriate interval
if freq == definitions.eRecurrence_SECONDLY:
self.mSeconds += interval
@@ -671,7 +759,7 @@
# the same day number as the current one.
self.mMonth += interval
self.normalise()
- while self.mDay > utils.daysInMonth( self.mMonth, self.mYear ):
+ while self.mDay > utils.daysInMonth(self.mMonth, self.mYear):
self.mMonth += interval
self.normalise()
elif freq == definitions.eRecurrence_YEARLY:
@@ -680,53 +768,55 @@
# 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
@@ -739,111 +829,235 @@
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:
- buf = "%4d%02d%02d" % ( self.mYear, self.mMonth, self.mDay )
+ return "%04d%02d%02d" % (self.mYear, self.mMonth, self.mDay)
else:
if self.mTZUTC:
- buf = "%4d%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)
else:
- buf = "%4d%02d%02dT%02d%02d%02d" % ( self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds )
- return buf
+ return "%04d%02d%02dT%02d%02d%02d" % (self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds)
- def parse( self, data ):
- # parse format YYYYMMDD[THHMMSS[Z]]
+ def getXMLText(self):
- # Size must be at least 8
- dlen = len(data)
- if dlen < 8:
- return
+ if self.mDateOnly:
+ 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)
+ 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)
+ else:
+ 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):
+ dt = cls()
+ dt.parse(data, fullISO)
+ return dt
+
+
+ def parseDate(self, data, fullISO):
# Get year
- buf = data[0:4]
- self.mYear = int( buf )
+ self.mYear = int(data[0:4])
+ index = 4
+ if fullISO and data[index] == "-":
+ index += 1
# Get month
- buf = data[4:6]
- self.mMonth = int( buf )
+ self.mMonth = int(data[index:index + 2])
+ index += 2
+ if fullISO and data[index] == "-":
+ index += 1
# Get day
- buf = data[6:8]
- self.mDay = int( buf )
+ self.mDay = int(data[index:index + 2])
+ index += 2
- # Now look for more
- if ( dlen >= 15 ) and ( data[8] == 'T' ):
- # Get hours
- buf = data[9:11]
- self.mHours = int( buf )
+ if ' ' in data[:index]:
+ raise ValueError
+ if self.mYear < 0 or self.mMonth < 0 or self.mDay < 0:
+ raise ValueError
+ return index
- # Get minutes
- buf = data[11:13]
- self.mMinutes = int( buf )
- # Get seconds
- buf = data[13:15]
- self.mSeconds = int( buf )
+ def parseTime(self, data, index, fullISO):
+ # Get hours
+ self.mHours = int(data[index:index + 2])
+ index += 2
+ if fullISO and data[index] == ":":
+ index += 1
- self.mDateOnly = False
+ # Get minutes
+ self.mMinutes = int(data[index:index + 2])
+ index += 2
+ if fullISO and data[index] == ":":
+ index += 1
- if dlen > 15:
- self.mTZUTC = ( data[15] == 'Z' )
+ # Get seconds
+ self.mSeconds = int(data[index:index + 2])
+ index += 2
+
+ return index
+
+
+ def parseFractionalAndUTCOffset(self, data, index, dlen):
+ # Optional fraction
+ if data[index] == ",":
+ index += 1
+ if not data[index].isdigit():
+ raise ValueError
+ while index < dlen and data[index].isdigit():
+ index += 1
+
+ # Optional timezone descriptor
+ if index < dlen:
+ if data[index] == "Z":
+ index += 1
+ self.mTZUTC = True
else:
- self.mTZUTC = False
- else:
- self.mDateOnly = True
+ if data[index] == "+":
+ sign = 1
+ elif data[index] == "-":
+ sign = -1
+ else:
+ raise ValueError
+ index += 1
+ # Get hours
+ hours_offset = int(data[index:index + 2])
+ index += 2
+ if data[index] == ":":
+ index += 1
+
+ # Get minutes
+ minutes_offset = int(data[index:index + 2])
+ index += 2
+
+ self.mTZID = sign * (hours_offset * 60 + minutes_offset) * 60
+
+ if index < dlen:
+ raise ValueError
+ return index
+
+
+ def parse(self, data, fullISO=False):
+
+ # iCalendar:
+ # parse format YYYYMMDD[THHMMSS[Z]]
+ # vCard (fullISO)
+ # parse format YYYY[-]MM[-]DD[THH[:]MM[:]SS[(Z/(+/-)HHMM]]
+
+ 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)
+ else:
+ if index < dlen:
+ if data[index] != 'Z':
+ raise ValueError
+ index += 1
+ self.mTZUTC = True
+ if index < dlen:
+ raise ValueError
+ else:
+ self.mTZUTC = False
+ else:
+ self.mDateOnly = True
+ except IndexError:
+ raise ValueError
+
# 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 normalise( self ):
+
+ def writeXML(self, node, namespace):
+ value = XML.SubElement(
+ node,
+ xmldefs.makeTag(
+ namespace,
+ xmldefs.value_date if self.isDateOnly() else xmldefs.value_date_time
+ ))
+ value.text = self.getXMLText()
+
+
+ def normalise(self):
# Normalise seconds
normalised_secs = self.mSeconds % 60
adjustment_mins = self.mSeconds / 60
@@ -878,9 +1092,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
@@ -888,8 +1102,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
@@ -900,34 +1114,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):
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)
+ 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 ):
- # Add days betweenn 1970 and current year (ignoring leap days)
- result = ( self.mYear - 1970 ) * 365
+ def daysSince1970(self):
+ # Add days between 1970 and current year (ignoring leap days)
+ 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
Modified: PyCalendar/trunk/src/pycalendar/datetimevalue.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/datetimevalue.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/datetimevalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,33 +14,42 @@
# limitations under the License.
##
-from datetime import PyCalendarDateTime
-from value import PyCalendarValue
+from pycalendar import xmldefs
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.value import PyCalendarValue
class PyCalendarDateTimeValue(PyCalendarValue):
- def __init__(self, value = None, copyit = None):
- if value:
- self.mValue = value
- elif copyit:
- self.mValue = PyCalendarDateTime(copyit=copyit.mValue)
- else:
- self.mValue = PyCalendarDateTime()
+ 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):
- self.mValue.parse(data)
+ 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
-PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_DATE, PyCalendarDateTimeValue)
-PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_DATETIME, PyCalendarDateTimeValue)
+PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_DATE, PyCalendarDateTimeValue, xmldefs.value_date)
+PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_DATETIME, PyCalendarDateTimeValue, xmldefs.value_date_time)
Modified: PyCalendar/trunk/src/pycalendar/definitions.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/definitions.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/definitions.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,32 +14,21 @@
# limitations under the License.
##
-# 2445 Component Header/Footer
+# 5545 Components
-cICalComponent_BEGINVCALENDAR = "BEGIN:VCALENDAR"
-cICalComponent_ENDVCALENDAR = "END:VCALENDAR"
-cICalComponent_BEGINVEVENT = "BEGIN:VEVENT"
-cICalComponent_ENDVEVENT = "END:VEVENT"
-cICalComponent_BEGINVTODO = "BEGIN:VTODO"
-cICalComponent_ENDVTODO = "END:VTODO"
-cICalComponent_BEGINVJOURNAL = "BEGIN:VJOURNAL"
-cICalComponent_ENDVJOURNAL = "END:VJOURNAL"
-cICalComponent_BEGINVFREEBUSY = "BEGIN:VFREEBUSY"
-cICalComponent_ENDVFREEBUSY = "END:VFREEBUSY"
-cICalComponent_BEGINVTIMEZONE = "BEGIN:VTIMEZONE"
-cICalComponent_ENDVTIMEZONE = "END:VTIMEZONE"
-cICalComponent_BEGINVALARM = "BEGIN:VALARM"
-cICalComponent_ENDVALARM = "END:VALARM"
+cICalComponent_VCALENDAR = "VCALENDAR"
+cICalComponent_VEVENT = "VEVENT"
+cICalComponent_VTODO = "VTODO"
+cICalComponent_VJOURNAL = "VJOURNAL"
+cICalComponent_VFREEBUSY = "VFREEBUSY"
+cICalComponent_VTIMEZONE = "VTIMEZONE"
+cICalComponent_VALARM = "VALARM"
+cICalComponent_STANDARD = "STANDARD"
+cICalComponent_DAYLIGHT = "DAYLIGHT"
-# Pseudo components
-cICalComponent_BEGINSTANDARD = "BEGIN:STANDARD"
-cICalComponent_ENDSTANDARD = "END:STANDARD"
-cICalComponent_BEGINDAYLIGHT = "BEGIN:DAYLIGHT"
-cICalComponent_ENDDAYLIGHT = "END:DAYLIGHT"
+# 5545 Calendar Property Attributes
-# 2445 Calendar Property Atrributes
-
-# 2445 Section 4.2
+# 5545 Section 3.2
cICalAttribute_ALTREP = "ALTREP"
cICalAttribute_CN = "CN"
cICalAttribute_CUTYPE = "CUTYPE"
@@ -63,13 +52,13 @@
cICalAttribute_TZID = "TZID"
cICalAttribute_VALUE = "VALUE"
-# 2445 Section 4.2.9
+# 5545 Section 3.2.9
cICalAttribute_FBTYPE_FREE = "FREE"
cICalAttribute_FBTYPE_BUSY = "BUSY"
cICalAttribute_FBTYPE_BUSYUNAVAILABLE = "BUSY-UNAVAILABLE"
cICalAttribute_FBTYPE_BUSYTENTATIVE = "BUSY-TENTATIVE"
-# 2445 Section 4.2.12
+# 5545 Section 3.2.12
ePartStat_NeedsAction = 0
ePartStat_Accepted = 1
ePartStat_Declined = 2
@@ -86,26 +75,26 @@
cICalAttribute_PARTSTAT_COMPLETED = "COMPLETE"
cICalAttribute_PARTSTAT_INPROCESS = "IN-PROCESS"
-# 2445 Section 4.2.13
+# 5545 Section 3.2.13
cICalAttribute_RANGE_THISANDFUTURE = "THISANDFUTURE"
-cICalAttribute_RANGE_THISANDPRIOR = "THISANDPRIOR"
+cICalAttribute_RANGE_THISANDPRIOR = "THISANDPRIOR" # 2445 only
-# 2445 Section 4.2.14
+# 5545 Section 3.2.14
cICalAttribute_RELATED_START = "START"
cICalAttribute_RELATED_END = "END"
-# 2445 Section 4.2.16
+# 5545 Section 3.2.16
ePartRole_Chair = 0
ePartRole_Required = 1
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"
-# 2445 Section 4.2.3
+# 5545 Section 3.2.3
eCutype_Individual = 0
eCutype_Group = 1
eCutype_Resource = 2
@@ -113,14 +102,14 @@
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"
-# 2445 Value types
+# 5545 Value types
-# 2445 Section 4.3
+# 5545 Section 3.3
cICalValue_BINARY = "BINARY"
cICalValue_BOOLEAN = "BOOLEAN"
cICalValue_CAL_ADDRESS = "CAL-ADDRESS"
@@ -136,9 +125,9 @@
cICalValue_URI = "URI"
cICalValue_UTC_OFFSET = "UTC-OFFSET"
-# 2445 Calendar Properties
+# 5545 Calendar Properties
-# 2445 Section 4.7
+# 5545 Section 3.7
cICalProperty_CALSCALE = "CALSCALE"
cICalProperty_METHOD = "METHOD"
@@ -148,10 +137,11 @@
# Apple Extensions
cICalProperty_XWRCALNAME = "X-WR-CALNAME"
cICalProperty_XWRCALDESC = "X-WR-CALDESC"
+cICalProperty_XWRALARMUID = "X-WR-ALARMUID"
-# 2445 Componenty Property names
+# 5545 Component Property names
-# 2445 Section 4.8.1
+# 5545 Section 3.8.1
cICalProperty_ATTACH = "ATTACH"
cICalProperty_CATEGORIES = "CATEGORIES"
cICalProperty_CLASS = "CLASS"
@@ -165,7 +155,7 @@
cICalProperty_STATUS = "STATUS"
cICalProperty_SUMMARY = "SUMMARY"
-# 2445 Section 4.8.2
+# 5545 Section 3.8.2
cICalProperty_COMPLETED = "COMPLETED"
cICalProperty_DTEND = "DTEND"
cICalProperty_DUE = "DUE"
@@ -176,14 +166,14 @@
cICalProperty_OPAQUE = "OPAQUE"
cICalProperty_TRANSPARENT = "TRANSPARENT"
-# 2445 Section 4.8.3
+# 5545 Section 3.8.3
cICalProperty_TZID = "TZID"
cICalProperty_TZNAME = "TZNAME"
cICalProperty_TZOFFSETFROM = "TZOFFSETFROM"
cICalProperty_TZOFFSETTO = "TZOFFSETTO"
cICalProperty_TZURL = "TZURL"
-# 2445 Section 4.8.4
+# 5545 Section 3.8.4
cICalProperty_ATTENDEE = "ATTENDEE"
cICalProperty_CONTACT = "CONTACT"
cICalProperty_ORGANIZER = "ORGANIZER"
@@ -192,30 +182,30 @@
cICalProperty_URL = "URL"
cICalProperty_UID = "UID"
-# 2445 Section 4.8.5
+# 5545 Section 3.8.5
cICalProperty_EXDATE = "EXDATE"
-cICalProperty_EXRULE = "EXRULE"
+cICalProperty_EXRULE = "EXRULE" # 2445 only
cICalProperty_RDATE = "RDATE"
cICalProperty_RRULE = "RRULE"
-# 2445 Section 4.8.6
+# 5545 Section 3.8.6
cICalProperty_ACTION = "ACTION"
cICalProperty_REPEAT = "REPEAT"
cICalProperty_TRIGGER = "TRIGGER"
-# 2445 Section 4.8.7
+# 5545 Section 3.8.7
cICalProperty_CREATED = "CREATED"
cICalProperty_DTSTAMP = "DTSTAMP"
cICalProperty_LAST_MODIFIED = "LAST-MODIFIED"
cICalProperty_SEQUENCE = "SEQUENCE"
-# 2445 Section 4.8.8.2
+# 5545 Section 3.8.8.3
cICalProperty_REQUEST_STATUS = "REQUEST-STATUS"
# Enums
# Use ascending order for sensible sorting
-# 2445 Section 4.3.10
+# 5545 Section 3.3.10
eRecurrence_SECONDLY = 0
eRecurrence_MINUTELY = 1
@@ -225,7 +215,22 @@
eRecurrence_MONTHLY = 5
eRecurrence_YEARLY = 6
-cICalValue_RECUR_FREQ = "FREQ="
+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
+
+cICalValue_RECUR_FREQ = "FREQ"
cICalValue_RECUR_FREQ_LEN = 5
cICalValue_RECUR_SECONDLY = "SECONDLY"
@@ -236,20 +241,20 @@
cICalValue_RECUR_MONTHLY = "MONTHLY"
cICalValue_RECUR_YEARLY = "YEARLY"
-cICalValue_RECUR_UNTIL = "UNTIL="
-cICalValue_RECUR_COUNT = "COUNT="
+cICalValue_RECUR_UNTIL = "UNTIL"
+cICalValue_RECUR_COUNT = "COUNT"
-cICalValue_RECUR_INTERVAL = "INTERVAL="
-cICalValue_RECUR_BYSECOND = "BYSECOND="
-cICalValue_RECUR_BYMINUTE = "BYMINUTE="
-cICalValue_RECUR_BYHOUR = "BYHOUR="
-cICalValue_RECUR_BYDAY = "BYDAY="
-cICalValue_RECUR_BYMONTHDAY = "BYMONTHDAY="
-cICalValue_RECUR_BYYEARDAY = "BYYEARDAY="
-cICalValue_RECUR_BYWEEKNO = "BYWEEKNO="
-cICalValue_RECUR_BYMONTH = "BYMONTH="
-cICalValue_RECUR_BYSETPOS = "BYSETPOS="
-cICalValue_RECUR_WKST = "WKST="
+cICalValue_RECUR_INTERVAL = "INTERVAL"
+cICalValue_RECUR_BYSECOND = "BYSECOND"
+cICalValue_RECUR_BYMINUTE = "BYMINUTE"
+cICalValue_RECUR_BYHOUR = "BYHOUR"
+cICalValue_RECUR_BYDAY = "BYDAY"
+cICalValue_RECUR_BYMONTHDAY = "BYMONTHDAY"
+cICalValue_RECUR_BYYEARDAY = "BYYEARDAY"
+cICalValue_RECUR_BYWEEKNO = "BYWEEKNO"
+cICalValue_RECUR_BYMONTH = "BYMONTH"
+cICalValue_RECUR_BYSETPOS = "BYSETPOS"
+cICalValue_RECUR_WKST = "WKST"
eRecurrence_WEEKDAY_SU = 0
eRecurrence_WEEKDAY_MO = 1
@@ -267,7 +272,7 @@
cICalValue_RECUR_WEEKDAY_FR = "FR"
cICalValue_RECUR_WEEKDAY_SA = "SA"
-# 2445 Section 4.8.1.11
+# 5545 Section 3.8.1.11
eStatus_VEvent_None = 0
eStatus_VEvent_Confirmed = 1
eStatus_VEvent_Tentative = 2
@@ -293,7 +298,7 @@
cICalProperty_STATUS_DRAFT = "DRAFT"
cICalProperty_STATUS_FINAL = "FINAL"
-# 2445 Section 4.8.6.1
+# 5545 Section 3.8.6.1
eAction_VAlarm_Audio = 0
eAction_VAlarm_Display = 1
eAction_VAlarm_Email = 2
@@ -305,11 +310,28 @@
cICalProperty_ACTION_EMAIL = "EMAIL"
cICalProperty_ACTION_PROCEDURE = "PROCEDURE"
+# Extensions: draft-daboo-calendar-availability-02
+# Section 3.1
+cICalComponent_VAVAILABILITY = "VAVAILABILITY"
+cICalComponent_AVAILABLE = "AVAILABLE"
+
+# Section 3.2
+cICalProperty_BUSYTYPE = "BUSYTYPE"
+
+# Extensions: draft-daboo-valarm-extensions-03
+
+# Section 5
+eAction_VAlarm_URI = 5
+cICalProperty_ACTION_URI = "URI"
+
+# Section 7.1
+cICalProperty_ACKNOWLEDGED = "ACKNOWLEDGED"
+
+eAction_VAlarm_None = 6
+cICalProperty_ACTION_NONE = "NONE"
+
# Mulberry extensions
-cICalProperty_X_PRIVATE_RURL = "X-MULBERRY-PRIVATE-RURL"
-cICalProperty_X_PRIVATE_ETAG = "X-MULBERRY-PRIVATE-ETAG"
-
cICalProperty_ACTION_X_SPEAKTEXT = "X-MULBERRY-SPEAK-TEXT"
cICalProperty_ALARM_X_LASTTRIGGER = "X-MULBERRY-LAST-TRIGGER"
Deleted: PyCalendar/trunk/src/pycalendar/dummyvalue.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/dummyvalue.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/dummyvalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,50 +0,0 @@
-##
-# Copyright (c) 2007 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 value import PyCalendarValue
-
-class PyCalendarDummyValue( PyCalendarValue ):
-
- def __init__( self, type=None, copyit=None ):
- if type is not None:
- self.mType = type
- self.mValue = ''
- else:
- self.mType = copyit.mType
- self.mValue = copyit.mValue
-
- 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 getValue( self ):
- return self.mValue
-
- def setValue( self, value ):
- self.mValue = value
-
-PyCalendarValue.registerType("DUMMY", PyCalendarDummyValue)
\ No newline at end of file
Modified: PyCalendar/trunk/src/pycalendar/duration.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/duration.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/duration.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,11 +14,13 @@
# limitations under the License.
##
-import re
+from pycalendar.parser import ParserContext
+from pycalendar.stringutils import strtoul
+from pycalendar.valueutils import ValueMixin
-class PyCalendarDuration(object):
+class PyCalendarDuration(ValueMixin):
- def __init__(self, duration=None, copyit=None):
+ def __init__(self, duration=None, weeks=0, days=0, hours=0, minutes=0, seconds=0):
self.mForward = True
self.mWeeks = 0
@@ -27,23 +29,47 @@
self.mHours = 0
self.mMinutes = 0
self.mSeconds = 0
-
- if (duration is not None):
- self.setDuration(duration)
- elif (copyit is not None):
- self.mForward = copyit.mForward
-
- self.mWeeks = copyit.mWeeks
- self.mDays = copyit.mDays
-
- self.mHours = copyit.mHours
- self.mMinutes = copyit.mMinutes
- self.mSeconds = copyit.mSeconds
+ 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
+
+ other.mWeeks = self.mWeeks
+ other.mDays = self.mDays
+
+ other.mHours = self.mHours
+ other.mMinutes = self.mMinutes
+ other.mSeconds = self.mSeconds
+
+ return other
+
+
+ def __hash__(self):
+ return hash(self.getTotalSeconds())
+
+
+ def __eq__(self, comp):
+ return self.getTotalSeconds() == comp.getTotalSeconds()
+
+
+ def __gt__(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
@@ -76,135 +102,143 @@
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)
- regex = re.compile(r'([+/-]?)(P)([0-9]*)([DW]?)([T]?)([0-9]*)([HMS]?)([0-9]*)([MS]?)([0-9]*)([S]?)')
- st = regex.search(data).groups()
- st = [x for x in st if x != '']
- st.reverse()
- if len(st) == 0:
- return
- token = st.pop()
+ try:
+ offset = 0
+ maxoffset = len(data)
- # Look for +/-
- self.mForward = True
- if token == "-":
- self.mForward = False
- if len(st) == 0:
- return
- token = st.pop()
- elif token == "+":
- if len(st) == 0:
- return
- token = st.pop()
+ # Look for +/-
+ self.mForward = True
+ if data[offset] in ('-', '+'):
+ self.mForward = data[offset] == '+'
+ offset += 1
- # Must have a 'P'
- if token != "P":
- return
-
- # Look for time
- if len(st) == 0:
- return
- token = st.pop()
- if token != "T":
- # Must have a number
- num = int(token)
+ # Must have a 'P'
+ if data[offset] != "P":
+ raise ValueError
+ offset += 1
- # Now look at character
- if len(st) == 0:
- return
- token = st.pop()
- if token == "W":
- # Have a number of weeks
- self.mWeeks = num
+ # Look for time
+ if data[offset] != "T":
+ # Must have a number
+ num, offset = strtoul(data, offset)
- # There cannot bew anything else after this so just exit
- return
- elif token == "D":
- # Have a number of days
- self.mDays = num
+ # Now look at character
+ if data[offset] == "W":
+ # Have a number of weeks
+ self.mWeeks = num
+ offset += 1
- # Look for more data - exit if none
- if len(st) == 0:
+ # There cannot be anything else after this so just exit
+ if offset != maxoffset:
+ if ParserContext.INVALID_DURATION_VALUE != ParserContext.PARSER_RAISE:
+ return
+ raise ValueError
return
- token = st.pop()
+ elif data[offset] == "D":
+ # Have a number of days
+ self.mDays = num
+ offset += 1
- # Look for time - exit if none
- if token != "T":
+ # 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
+
+ # Strictly speaking T must always be followed by time values, but some clients
+ # send T with no additional text
+ if offset == maxoffset:
+ if ParserContext.INVALID_DURATION_VALUE == ParserContext.PARSER_RAISE:
+ raise ValueError
+ else:
return
- else:
- # Error in format
- return
+ num, offset = strtoul(data, offset)
- # Have time
- if len(st) == 0:
- return
- token = st.pop()
- num = int(token)
+ # Look for hour
+ if data[offset] == "H":
+ # Get hours
+ self.mHours = num
+ offset += 1
- # Look for hour
- if len(st) == 0:
- return
- token = st.pop()
- if token == "H":
- # Get hours
- self.mHours = num
+ # Look for more data - exit if none
+ if offset == maxoffset:
+ return
- # Look for more data - exit if none
- if len(st) == 0:
- return
+ # Parse the next number
+ num, offset = strtoul(data, offset)
- # Parse the next number
- token = st.pop()
- num = int(token)
+ # Look for minute
+ if data[offset] == "M":
+ # Get hours
+ self.mMinutes = num
+ offset += 1
- # Parse the next char
- if len(st) == 0:
- return
- token = st.pop()
+ # Look for more data - exit if none
+ if offset == maxoffset:
+ return
- # Look for minute
- if token == "M":
- # Get hours
- self.mMinutes = num
+ # Parse the next number
+ num, offset = strtoul(data, offset)
- # Look for more data - exit if none
- if len(st) == 0:
- return
+ # Look for seconds
+ if data[offset] == "S":
+ # Get hours
+ self.mSeconds = num
+ offset += 1
- # Parse the next number
- token = st.pop()
- num = int(token)
+ # No more data - exit
+ if offset == maxoffset:
+ return
- # Parse the next char
- if len(st) == 0:
- return
- token = st.pop()
+ raise ValueError
- # Look for seconds
- if token == "S":
- # Get hours
- self.mSeconds = num
+ 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):
@@ -222,13 +256,17 @@
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:
os.write("T0S")
except:
pass
+
+
+ def writeXML(self, node, namespace):
+ node.text = self.getText()
Modified: PyCalendar/trunk/src/pycalendar/durationvalue.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/durationvalue.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/durationvalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,32 +14,42 @@
# limitations under the License.
##
-from duration import PyCalendarDuration
-from value import PyCalendarValue
+from pycalendar import xmldefs
+from pycalendar.duration import PyCalendarDuration
+from pycalendar.value import PyCalendarValue
class PyCalendarDurationValue(PyCalendarValue):
- def __init__(self, value = None, copyit = None):
- if value:
- self.mValue = value
- elif copyit:
- self.mValue = PyCalendarDuration(duration=copyit.mValue)
- else:
- self.mValue = PyCalendarDuration()
+ 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)
+PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_DURATION, PyCalendarDurationValue, xmldefs.value_duration)
Copied: PyCalendar/trunk/src/pycalendar/exceptions.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/exceptions.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/exceptions.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/exceptions.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,44 @@
+##
+# 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.
+##
+
+class PyCalendarError(Exception):
+
+ def __init__(self, reason, data=""):
+ 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):
+ self.mTZDBpath = dbpath
+ self.mTZID = tzid
Modified: PyCalendar/trunk/src/pycalendar/freebusy.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/freebusy.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/freebusy.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,8 +14,6 @@
# limitations under the License.
##
-from period import PyCalendarPeriod
-
class PyCalendarFreeBusy(object):
FREE = 0
@@ -23,34 +21,36 @@
BUSYUNAVAILABLE = 2
BUSY = 3
- def __init__( self, type = None, period = None, copyit = None ):
-
- if type and period:
- self.mType = type
- self.mPeriod = PyCalendarPeriod( copyit=period )
- elif type:
- self.mType = type
- elif copyit:
- self.mType = copyit.mType
- self.mPeriod = PyCalendarPeriod( copyit=copyit.mPeriod )
- else:
- self.mType = PyCalendarFreeBusy.FREE
+ def __init__(self, type=None, period=None):
- def setType( self, type ):
+ 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):
self.mType = type
- def getType( self ):
+
+ def getType(self):
return self.mType
- def setPeriod( self, period ):
- self.mPeriod = PyCalendarPeriod( copyit=period )
- def getPeriod( self ):
+ def setPeriod(self, period):
+ self.mPeriod = period.duplicate()
+
+
+ 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
Deleted: PyCalendar/trunk/src/pycalendar/icalendar/__init__.py
===================================================================
--- PyCalendar/branches/server/src/pycalendar/icalendar/__init__.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/icalendar/__init__.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,15 +0,0 @@
-#
-# 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.
-##
Copied: PyCalendar/trunk/src/pycalendar/icalendar/__init__.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/icalendar/__init__.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/icalendar/__init__.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/icalendar/__init__.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,15 @@
+#
+# 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.
+##
Deleted: PyCalendar/trunk/src/pycalendar/icalendar/tests/__init__.py
===================================================================
--- PyCalendar/branches/server/src/pycalendar/icalendar/tests/__init__.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/icalendar/tests/__init__.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,15 +0,0 @@
-#
-# 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.
-##
Copied: PyCalendar/trunk/src/pycalendar/icalendar/tests/__init__.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/icalendar/tests/__init__.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/icalendar/tests/__init__.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/icalendar/tests/__init__.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,15 @@
+#
+# 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.
+##
Deleted: PyCalendar/trunk/src/pycalendar/icalendar/tests/test_validation.py
===================================================================
--- PyCalendar/branches/server/src/pycalendar/icalendar/tests/test_validation.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/icalendar/tests/test_validation.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,3189 +0,0 @@
-##
-# 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.
-##
-
-from pycalendar.calendar import PyCalendar
-from pycalendar.exceptions import PyCalendarValidationError
-import unittest
-
-class TestValidation(unittest.TestCase):
-
- def test_basic(self):
-
- data = (
- (
- "No problems",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "No PRODID",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VCALENDAR] Missing or too many required property: PRODID",
- )),
- ),
- )
-
- 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",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "Unfixable only",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VCALENDAR] Missing or too many required property: PRODID",
- )),
- ),
- (
- "Fixable only",
- """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
-DURATION:P1D
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VEVENT] Properties must not both be present: DTEND, DURATION",
- )),
- ),
- (
- "Fixable and unfixable",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VCALENDAR] Missing or too many required property: PRODID",
- "[VEVENT] Properties must not both be present: DTEND, DURATION",
- )),
- ),
- )
-
- for title, test_old, test_new, test_fixed, test_unfixed in data:
- cal = PyCalendar.parseText(test_old)
- fixed, unfixed = cal.validate(doFix=False, doRaise=False)
- 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,))
-
-
- def test_mode_fix_no_raise(self):
-
- data = (
- (
- "OK",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "Unfixable only",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VCALENDAR] Missing or too many required property: PRODID",
- )),
- ),
- (
- "Fixable only",
- """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
-DURATION:P1D
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set((
- "[VEVENT] Properties must not both be present: DTEND, DURATION",
- )),
- set(),
- ),
- (
- "Fixable and unfixable",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set((
- "[VEVENT] Properties must not both be present: DTEND, DURATION",
- )),
- set((
- "[VCALENDAR] Missing or too many required property: PRODID",
- )),
- ),
- )
-
- for title, test_old, test_new, test_fixed, test_unfixed in data:
- cal = PyCalendar.parseText(test_old)
- fixed, unfixed = cal.validate(doFix=True, doRaise=False)
- 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,))
-
-
- def test_mode_no_fix_raise(self):
-
- data = (
- (
- "OK",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- False,
- ),
- (
- "Unfixable only",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VCALENDAR] Missing or too many required property: PRODID",
- )),
- True,
- ),
- (
- "Fixable only",
- """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
-DURATION:P1D
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VEVENT] Properties must not both be present: DTEND, DURATION",
- )),
- True,
- ),
- (
- "Fixable and unfixable",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VCALENDAR] Missing or too many required property: PRODID",
- "[VEVENT] Properties must not both be present: DTEND, DURATION",
- )),
- True,
- ),
- )
-
- for title, test_old, test_new, test_fixed, test_unfixed, test_raises in data:
- cal = PyCalendar.parseText(test_old)
- if test_raises:
- self.assertRaises(PyCalendarValidationError, cal.validate, doFix=False, doRaise=True)
- else:
- try:
- fixed, unfixed = cal.validate(doFix=False, doRaise=False)
- except:
- self.fail(msg="Failed test: %s" % (title,))
- 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,))
-
-
- def test_mode_fix_raise(self):
-
- data = (
- (
- "OK",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- False,
- ),
- (
- "Unfixable only",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VCALENDAR] Missing or too many required property: PRODID",
- )),
- True,
- ),
- (
- "Fixable only",
- """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
-DURATION:P1D
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set((
- "[VEVENT] Properties must not both be present: DTEND, DURATION",
- )),
- set(),
- False,
- ),
- (
- "Fixable and unfixable",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set((
- "[VEVENT] Properties must not both be present: DTEND, DURATION",
- )),
- set((
- "[VCALENDAR] Missing or too many required property: PRODID",
- )),
- True,
- ),
- )
-
- for title, test_old, test_new, test_fixed, test_unfixed, test_raises in data:
- cal = PyCalendar.parseText(test_old)
- if test_raises:
- self.assertRaises(PyCalendarValidationError, cal.validate, doFix=False, doRaise=True)
- else:
- try:
- fixed, unfixed = cal.validate(doFix=True, doRaise=False)
- except:
- self.fail(msg="Failed test: %s" % (title,))
- 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,))
-
-
- def test_vevent(self):
- data = (
- (
- "No problem",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "Missing required",
- """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
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VEVENT] Missing or too many required property: DTSTAMP",
- )),
- ),
- (
- "Too many",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3253
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-SUMMARY:New Year's Eve
-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
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3253
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-SUMMARY:New Year's Eve
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VEVENT] Missing or too many required property: UID",
- "[VEVENT] Too many properties present: SUMMARY",
- )),
- ),
- (
- "PROP value",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VEVENT] Property value incorrect: DTSTAMP",
- )),
- ),
- (
- "No DTSTART without METHOD",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-METHOD:CANCEL
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTAMP:20020101T000000Z
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-METHOD:CANCEL
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTAMP:20020101T000000Z
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "Combo fix",
- """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
-DURATION:P1D
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set((
- "[VEVENT] Properties must not both be present: DTEND, DURATION",
- )),
- set(),
- ),
- (
- "Mismatch UNTIL as DATE-TIME",
- """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
-DURATION:P1D
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231T120000;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set((
- "[VEVENT] Value types must match: DTSTART, UNTIL",
- )),
- set(),
- ),
- (
- "Mismatch UNTIL as DATE",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART:20020101T121212Z
-DURATION:P1D
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART:20020101T121212Z
-DURATION:P1D
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231T121212Z;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set((
- "[VEVENT] Value types must match: DTSTART, UNTIL",
- )),
- set(),
- ),
- )
-
- for title, test_old, test_new, test_fixed, test_unfixed in data:
- cal = PyCalendar.parseText(test_old)
- fixed, unfixed = cal.validate(doFix=True)
- 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,))
-
-
- def test_vfreebusy(self):
- data = (
- (
- "No problem",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VFREEBUSY
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-END:VFREEBUSY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VFREEBUSY
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-END:VFREEBUSY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "Missing required",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VFREEBUSY
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-END:VFREEBUSY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VFREEBUSY
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-END:VFREEBUSY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VFREEBUSY] Missing or too many required property: DTSTAMP",
- )),
- ),
- )
-
- for title, test_old, test_new, test_fixed, test_unfixed in data:
- cal = PyCalendar.parseText(test_old)
- fixed, unfixed = cal.validate(doFix=True)
- 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,))
-
-
- def test_vjournal(self):
- data = (
- (
- "No problem",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VJOURNAL
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTSTAMP:20020101T000000Z
-END:VJOURNAL
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VJOURNAL
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTSTAMP:20020101T000000Z
-END:VJOURNAL
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "Missing required",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VJOURNAL
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-END:VJOURNAL
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VJOURNAL
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-END:VJOURNAL
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VJOURNAL] Missing or too many required property: DTSTAMP",
- )),
- ),
- )
-
- for title, test_old, test_new, test_fixed, test_unfixed in data:
- cal = PyCalendar.parseText(test_old)
- fixed, unfixed = cal.validate(doFix=True)
- 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,))
-
-
- def test_vtimezone(self):
- data = (
- (
- "No problem",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTIMEZONE
-TZID:America/New_York
-LAST-MODIFIED:20050809T050000Z
-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
-END:VTIMEZONE
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTIMEZONE
-TZID:America/New_York
-LAST-MODIFIED:20050809T050000Z
-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
-END:VTIMEZONE
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "Missing required",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTIMEZONE
-LAST-MODIFIED:20050809T050000Z
-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
-END:VTIMEZONE
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTIMEZONE
-LAST-MODIFIED:20050809T050000Z
-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
-END:VTIMEZONE
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VTIMEZONE] Missing or too many required property: TZID",
- )),
- ),
- (
- "Missing components",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTIMEZONE
-TZID:America/New_York
-LAST-MODIFIED:20050809T050000Z
-END:VTIMEZONE
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTIMEZONE
-TZID:America/New_York
-LAST-MODIFIED:20050809T050000Z
-END:VTIMEZONE
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VTIMEZONE] At least one component must be present: STANDARD or DAYLIGHT",
- )),
- ),
- )
-
- for title, test_old, test_new, test_fixed, test_unfixed in data:
- cal = PyCalendar.parseText(test_old)
- fixed, unfixed = cal.validate(doFix=True)
- 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,))
-
-
- def test_vtodo(self):
- data = (
- (
- "No problem",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "Missing required",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VTODO] Missing or too many required property: DTSTAMP",
- )),
- ),
- (
- "Too many",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3253
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-SUMMARY:New Year's Eve
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3253
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-SUMMARY:New Year's Eve
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VTODO] Missing or too many required property: UID",
- "[VTODO] Too many properties present: SUMMARY",
- )),
- ),
- (
- "PROP value",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-DTSTAMP:20020101T000000
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-DTSTAMP:20020101T000000
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VTODO] Property value incorrect: DTSTAMP",
- )),
- ),
- (
- "DURATION without DTSTART",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DURATION:P1D
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DURATION:P1D
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VTODO] Property must be present: DTSTART with DURATION",
- )),
- ),
- (
- "Combo fix",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-DUE;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set((
- "[VTODO] Properties must not both be present: DUE, DURATION",
- )),
- set(),
- ),
- )
-
- for title, test_old, test_new, test_fixed, test_unfixed in data:
- cal = PyCalendar.parseText(test_old)
- fixed, unfixed = cal.validate(doFix=True)
- 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,))
-
-
- def test_vavailability(self):
- data = (
- (
- "No problem",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VAVAILABILITY
-UID:20111005T133225Z-00001 at example.com
-DTSTAMP:20111005T133225Z
-ORGANIZER:mailto:bernard at example.com
-BEGIN:AVAILABLE
-UID:20111005T133225Z-00001-A at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DTEND;TZID=America/Montreal:20111002T170000
-DTSTAMP:20111005T133225Z
-RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
-SUMMARY:Monday to Friday from 9:00 to 17:00
-END:AVAILABLE
-END:VAVAILABILITY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VAVAILABILITY
-UID:20111005T133225Z-00001 at example.com
-DTSTAMP:20111005T133225Z
-ORGANIZER:mailto:bernard at example.com
-BEGIN:AVAILABLE
-UID:20111005T133225Z-00001-A at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DTEND;TZID=America/Montreal:20111002T170000
-DTSTAMP:20111005T133225Z
-RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
-SUMMARY:Monday to Friday from 9:00 to 17:00
-END:AVAILABLE
-END:VAVAILABILITY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "Missing required",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VAVAILABILITY
-UID:20111005T133225Z-00001 at example.com
-ORGANIZER:mailto:bernard at example.com
-BEGIN:AVAILABLE
-UID:20111005T133225Z-00001-A at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DTEND;TZID=America/Montreal:20111002T170000
-DTSTAMP:20111005T133225Z
-RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
-SUMMARY:Monday to Friday from 9:00 to 17:00
-END:AVAILABLE
-END:VAVAILABILITY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VAVAILABILITY
-UID:20111005T133225Z-00001 at example.com
-ORGANIZER:mailto:bernard at example.com
-BEGIN:AVAILABLE
-UID:20111005T133225Z-00001-A at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DTEND;TZID=America/Montreal:20111002T170000
-DTSTAMP:20111005T133225Z
-RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
-SUMMARY:Monday to Friday from 9:00 to 17:00
-END:AVAILABLE
-END:VAVAILABILITY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VAVAILABILITY] Missing or too many required property: DTSTAMP",
- )),
- ),
- (
- "Too many",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VAVAILABILITY
-UID:20111005T133225Z-00001 at example.com
-UID:20111005T133225Z-00001 at example.com
-DTSTAMP:20111005T133225Z
-ORGANIZER:mailto:bernard at example.com
-ORGANIZER:mailto:bernard at example.com
-BEGIN:AVAILABLE
-UID:20111005T133225Z-00001-A at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DTEND;TZID=America/Montreal:20111002T170000
-DTSTAMP:20111005T133225Z
-RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
-SUMMARY:Monday to Friday from 9:00 to 17:00
-END:AVAILABLE
-END:VAVAILABILITY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VAVAILABILITY
-UID:20111005T133225Z-00001 at example.com
-UID:20111005T133225Z-00001 at example.com
-DTSTAMP:20111005T133225Z
-ORGANIZER:mailto:bernard at example.com
-ORGANIZER:mailto:bernard at example.com
-BEGIN:AVAILABLE
-UID:20111005T133225Z-00001-A at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DTEND;TZID=America/Montreal:20111002T170000
-DTSTAMP:20111005T133225Z
-RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
-SUMMARY:Monday to Friday from 9:00 to 17:00
-END:AVAILABLE
-END:VAVAILABILITY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VAVAILABILITY] Missing or too many required property: UID",
- "[VAVAILABILITY] Too many properties present: ORGANIZER",
- )),
- ),
- (
- "Combo fix",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VAVAILABILITY
-UID:20111005T133225Z-00001 at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DURATION:P1D
-DTEND;TZID=America/Montreal:20111002T170000
-DTSTAMP:20111005T133225Z
-ORGANIZER:mailto:bernard at example.com
-BEGIN:AVAILABLE
-UID:20111005T133225Z-00001-A at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DTEND;TZID=America/Montreal:20111002T170000
-DTSTAMP:20111005T133225Z
-RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
-SUMMARY:Monday to Friday from 9:00 to 17:00
-END:AVAILABLE
-END:VAVAILABILITY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VAVAILABILITY
-UID:20111005T133225Z-00001 at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DURATION:P1D
-DTSTAMP:20111005T133225Z
-ORGANIZER:mailto:bernard at example.com
-BEGIN:AVAILABLE
-UID:20111005T133225Z-00001-A at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DTEND;TZID=America/Montreal:20111002T170000
-DTSTAMP:20111005T133225Z
-RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
-SUMMARY:Monday to Friday from 9:00 to 17:00
-END:AVAILABLE
-END:VAVAILABILITY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set((
- "[VAVAILABILITY] Properties must not both be present: DTEND, DURATION",
- )),
- set(),
- ),
- )
-
- for title, test_old, test_new, test_fixed, test_unfixed in data:
- cal = PyCalendar.parseText(test_old)
- fixed, unfixed = cal.validate(doFix=True)
- 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,))
-
-
- def test_available(self):
- data = (
- (
- "No problem",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VAVAILABILITY
-UID:20111005T133225Z-00001 at example.com
-DTSTAMP:20111005T133225Z
-ORGANIZER:mailto:bernard at example.com
-BEGIN:AVAILABLE
-UID:20111005T133225Z-00001-A at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DTEND;TZID=America/Montreal:20111002T170000
-DTSTAMP:20111005T133225Z
-RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
-SUMMARY:Monday to Friday from 9:00 to 17:00
-END:AVAILABLE
-END:VAVAILABILITY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VAVAILABILITY
-UID:20111005T133225Z-00001 at example.com
-DTSTAMP:20111005T133225Z
-ORGANIZER:mailto:bernard at example.com
-BEGIN:AVAILABLE
-UID:20111005T133225Z-00001-A at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DTEND;TZID=America/Montreal:20111002T170000
-DTSTAMP:20111005T133225Z
-RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
-SUMMARY:Monday to Friday from 9:00 to 17:00
-END:AVAILABLE
-END:VAVAILABILITY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "Missing required",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VAVAILABILITY
-UID:20111005T133225Z-00001 at example.com
-DTSTAMP:20111005T133225Z
-ORGANIZER:mailto:bernard at example.com
-BEGIN:AVAILABLE
-UID:20111005T133225Z-00001-A at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DTEND;TZID=America/Montreal:20111002T170000
-RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
-SUMMARY:Monday to Friday from 9:00 to 17:00
-END:AVAILABLE
-END:VAVAILABILITY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VAVAILABILITY
-UID:20111005T133225Z-00001 at example.com
-DTSTAMP:20111005T133225Z
-ORGANIZER:mailto:bernard at example.com
-BEGIN:AVAILABLE
-UID:20111005T133225Z-00001-A at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DTEND;TZID=America/Montreal:20111002T170000
-RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
-SUMMARY:Monday to Friday from 9:00 to 17:00
-END:AVAILABLE
-END:VAVAILABILITY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[AVAILABLE] Missing or too many required property: DTSTAMP",
- )),
- ),
- (
- "Too many",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VAVAILABILITY
-UID:20111005T133225Z-00001 at example.com
-DTSTAMP:20111005T133225Z
-ORGANIZER:mailto:bernard at example.com
-BEGIN:AVAILABLE
-UID:20111005T133225Z-00001-A at example.com
-UID:20111005T133225Z-00001-A at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DTEND;TZID=America/Montreal:20111002T170000
-DTSTAMP:20111005T133225Z
-RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
-SUMMARY:Monday to Friday from 9:00 to 17:00
-SUMMARY:Monday to Friday from 9 am to 5 pm
-END:AVAILABLE
-END:VAVAILABILITY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VAVAILABILITY
-UID:20111005T133225Z-00001 at example.com
-DTSTAMP:20111005T133225Z
-ORGANIZER:mailto:bernard at example.com
-BEGIN:AVAILABLE
-UID:20111005T133225Z-00001-A at example.com
-UID:20111005T133225Z-00001-A at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DTEND;TZID=America/Montreal:20111002T170000
-DTSTAMP:20111005T133225Z
-RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
-SUMMARY:Monday to Friday from 9:00 to 17:00
-SUMMARY:Monday to Friday from 9 am to 5 pm
-END:AVAILABLE
-END:VAVAILABILITY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[AVAILABLE] Missing or too many required property: UID",
- "[AVAILABLE] Too many properties present: SUMMARY",
- )),
- ),
- (
- "Combo fix",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VAVAILABILITY
-UID:20111005T133225Z-00001 at example.com
-DTSTAMP:20111005T133225Z
-ORGANIZER:mailto:bernard at example.com
-BEGIN:AVAILABLE
-UID:20111005T133225Z-00001-A at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DURATION:P1D
-DTEND;TZID=America/Montreal:20111002T170000
-DTSTAMP:20111005T133225Z
-RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
-SUMMARY:Monday to Friday from 9:00 to 17:00
-END:AVAILABLE
-END:VAVAILABILITY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VAVAILABILITY
-UID:20111005T133225Z-00001 at example.com
-DTSTAMP:20111005T133225Z
-ORGANIZER:mailto:bernard at example.com
-BEGIN:AVAILABLE
-UID:20111005T133225Z-00001-A at example.com
-DTSTART;TZID=America/Montreal:20111002T090000
-DURATION:P1D
-DTSTAMP:20111005T133225Z
-RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
-SUMMARY:Monday to Friday from 9:00 to 17:00
-END:AVAILABLE
-END:VAVAILABILITY
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set((
- "[AVAILABLE] Properties must not both be present: DTEND, DURATION",
- )),
- set(),
- ),
- )
-
- for title, test_old, test_new, test_fixed, test_unfixed in data:
- cal = PyCalendar.parseText(test_old)
- fixed, unfixed = cal.validate(doFix=True)
- 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,))
-
-
- def test_valarm(self):
- data = (
- (
- "No problem",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-TRIGGER:-PT15M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-TRIGGER:-PT15M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "Missing required",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VALARM] Missing or too many required property: TRIGGER",
- )),
- ),
- (
- "Missing required with fix",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:DISPLAY
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set((
- "[VALARM] Missing required property: DESCRIPTION",
- )),
- set((
- "[VALARM] Missing or too many required property: TRIGGER",
- )),
- ),
- (
- "Too many",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:AUDIO
-ATTACH:http://example.com/audio/boink
-ATTACH:http://example.com/audio/quack
-TRIGGER:-PT15M
-TRIGGER:-PT30M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:AUDIO
-ATTACH:http://example.com/audio/boink
-ATTACH:http://example.com/audio/quack
-TRIGGER:-PT15M
-TRIGGER:-PT30M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VALARM] Missing or too many required property: TRIGGER",
- "[VALARM] Too many properties present: ATTACH",
- )),
- ),
- (
- "Too few",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:EMAIL
-DESCRIPTION:Reminder
-SUMMARY:Reminder
-TRIGGER:-PT15M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:EMAIL
-DESCRIPTION:Reminder
-SUMMARY:Reminder
-TRIGGER:-PT15M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VALARM] Missing required property: ATTENDEE",
- )),
- ),
- (
- "PROP value",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-DURATION:-P1D
-REPEAT:-1
-TRIGGER:-PT15M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-DURATION:-P1D
-REPEAT:-1
-TRIGGER:-PT15M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VALARM] Property value incorrect: REPEAT",
- )),
- ),
- (
- "DUARTION and REPEAT together",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-METHOD:CANCEL
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-REPEAT:2
-TRIGGER:-PT15M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-METHOD:CANCEL
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-REPEAT:2
-TRIGGER:-PT15M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VALARM] Properties must be present together: DURATION, REPEAT",
- )),
- ),
- (
- "Combo fix",
- """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
-DURATION:P1D
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-END:VALARM
-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
-DURATION:P1D
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set((
- "[VEVENT] Properties must not both be present: DTEND, DURATION",
- )),
- set((
- "[VALARM] Missing or too many required property: TRIGGER",
- )),
- ),
- (
- "ACKNOWLEDGED OK",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACKNOWLEDGED:20020101T000000Z
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-TRIGGER:-PT15M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACKNOWLEDGED:20020101T000000Z
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-TRIGGER:-PT15M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "ACKNOWLEDGED Bad value",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACKNOWLEDGED:20020101T000000
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-TRIGGER:-PT15M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACKNOWLEDGED:20020101T000000
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-TRIGGER:-PT15M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VALARM] Property value incorrect: ACKNOWLEDGED",
- )),
- ),
- (
- "ACKNOWLEDGED too many",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACKNOWLEDGED:20020101T000000Z
-ACKNOWLEDGED:20020102T000000Z
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-TRIGGER:-PT15M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACKNOWLEDGED:20020101T000000Z
-ACKNOWLEDGED:20020102T000000Z
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-TRIGGER:-PT15M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VALARM] Too many properties present: ACKNOWLEDGED",
- )),
- ),
- (
- "No problem ACTION=URI",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:URI
-TRIGGER:-PT15M
-URL:http://test.example.com
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:URI
-TRIGGER:-PT15M
-URL:http://test.example.com
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "ACTION=URI missing URL",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:URI
-TRIGGER:-PT15M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:URI
-TRIGGER:-PT15M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VALARM] Missing or too many required property: URL",
- )),
- ),
- (
- "No problem ACTION=NONE",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:NONE
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:NONE
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "No problem ACTION=NONE with TRIGGER",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:NONE
-TRIGGER;VALUE=DATE-TIME:19760401T005545Z
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-BEGIN:VALARM
-ACTION:NONE
-TRIGGER;VALUE=DATE-TIME:19760401T005545Z
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- )
-
- for title, test_old, test_new, test_fixed, test_unfixed in data:
- cal = PyCalendar.parseText(test_old)
- fixed, unfixed = cal.validate(doFix=True)
- 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,))
-
-
- def test_xcomponents(self):
- data = (
- (
- "No problem #1",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-BEGIN:X-COMPONENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-SUMMARY:New Year's Day
-END:X-COMPONENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-BEGIN:X-COMPONENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-SUMMARY:New Year's Day
-END:X-COMPONENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "No problem #2",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-BEGIN:X-COMPONENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTEND;VALUE=DATE:20020102
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-SUMMARY:New Year's Day
-END:X-COMPONENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-BEGIN:X-COMPONENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTEND;VALUE=DATE:20020102
-DTSTART;VALUE=DATE:20020101
-DURATION:P1D
-SUMMARY:New Year's Day
-END:X-COMPONENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "Prop value",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-BEGIN:X-COMPONENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000
-DTSTART;VALUE=DATE:20020101
-SUMMARY:New Year's Day
-END:X-COMPONENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-END:VEVENT
-BEGIN:X-COMPONENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000
-DTSTART;VALUE=DATE:20020101
-SUMMARY:New Year's Day
-END:X-COMPONENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[X-COMPONENT] Property value incorrect: DTSTAMP",
- )),
- ),
- )
-
- for title, test_old, test_new, test_fixed, test_unfixed in data:
- cal = PyCalendar.parseText(test_old)
- fixed, unfixed = cal.validate(doFix=True)
- 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,))
-
-
- def test_STATUS_fix(self):
- """
- Test calendarserver issue where multiple STATUS properties can be present in components.
- Want to make sure we report those as an error or fix them.
- """
- data = (
- (
- "1.1 Two STATUS in VEVENT - fix",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-STATUS:CONFIRMED
-STATUS:CANCELLED
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-STATUS:CANCELLED
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- True,
- set((
- "[VEVENT] Too many properties: STATUS",
- )),
- set(),
- ),
- (
- "1.2 Two STATUS in VEVENT - no fix",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-STATUS:CONFIRMED
-STATUS:CANCELLED
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-STATUS:CONFIRMED
-STATUS:CANCELLED
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- False,
- set(),
- set((
- "[VEVENT] Too many properties: STATUS",
- )),
- ),
- (
- "1.3 Two STATUS in VEVENT, none CANCELLED - fix",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-STATUS:CONFIRMED
-STATUS:TENTATIVE
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-STATUS:CONFIRMED
-STATUS:TENTATIVE
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- True,
- set(),
- set((
- "[VEVENT] Too many properties: STATUS",
- )),
- ),
- (
- "1.4 Two STATUS in VEVENT, none CANCELLED - no fix",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-STATUS:CONFIRMED
-STATUS:TENTATIVE
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VEVENT
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-STATUS:CONFIRMED
-STATUS:TENTATIVE
-SUMMARY:New Year's Day
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- False,
- set(),
- set((
- "[VEVENT] Too many properties: STATUS",
- )),
- ),
- (
- "2.1 Two STATUS in VTODO - fix",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-STATUS:NEEDS-ACTION
-STATUS:CANCELLED
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-STATUS:CANCELLED
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- True,
- set((
- "[VTODO] Too many properties: STATUS",
- )),
- set(),
- ),
- (
- "2.2 Two STATUS in VTODO - no fix",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-STATUS:NEEDS-ACTION
-STATUS:CANCELLED
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-STATUS:NEEDS-ACTION
-STATUS:CANCELLED
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- False,
- set(),
- set((
- "[VTODO] Too many properties: STATUS",
- )),
- ),
- (
- "2.3 Two STATUS in VTODO, none CANCELLED - fix",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-STATUS:NEEDS-ACTION
-STATUS:COMPLETED
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-STATUS:NEEDS-ACTION
-STATUS:COMPLETED
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- True,
- set(),
- set((
- "[VTODO] Too many properties: STATUS",
- )),
- ),
- (
- "2.4 Two STATUS in VTODO, none CANCELLED - no fix",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-STATUS:NEEDS-ACTION
-STATUS:COMPLETED
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VTODO
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DUE;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-STATUS:NEEDS-ACTION
-STATUS:COMPLETED
-SUMMARY:New Year's Day
-END:VTODO
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- False,
- set(),
- set((
- "[VTODO] Too many properties: STATUS",
- )),
- ),
- (
- "3.1 Two STATUS in VJOURNAL - fix",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VJOURNAL
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTSTAMP:20020101T000000Z
-STATUS:DRAFT
-STATUS:CANCELLED
-END:VJOURNAL
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VJOURNAL
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTSTAMP:20020101T000000Z
-STATUS:CANCELLED
-END:VJOURNAL
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- True,
- set((
- "[VJOURNAL] Too many properties: STATUS",
- )),
- set(),
- ),
- (
- "3.2 Two STATUS in VJOURNAL - no fix",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VJOURNAL
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTSTAMP:20020101T000000Z
-STATUS:DRAFT
-STATUS:CANCELLED
-END:VJOURNAL
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VJOURNAL
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTSTAMP:20020101T000000Z
-STATUS:DRAFT
-STATUS:CANCELLED
-END:VJOURNAL
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- False,
- set(),
- set((
- "[VJOURNAL] Too many properties: STATUS",
- )),
- ),
- (
- "3.3 Two STATUS in VJOURNAL, none CANCELLED - fix",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VJOURNAL
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTSTAMP:20020101T000000Z
-STATUS:DRAFT
-STATUS:FINAL
-END:VJOURNAL
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VJOURNAL
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTSTAMP:20020101T000000Z
-STATUS:DRAFT
-STATUS:FINAL
-END:VJOURNAL
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- True,
- set(),
- set((
- "[VJOURNAL] Too many properties: STATUS",
- )),
- ),
- (
- "3.4 Two STATUS in VJOURNAL, none CANCELLED - no fix",
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VJOURNAL
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTSTAMP:20020101T000000Z
-STATUS:DRAFT
-STATUS:FINAL
-END:VJOURNAL
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-BEGIN:VJOURNAL
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-DTSTART;VALUE=DATE:20020101
-DTSTAMP:20020101T000000Z
-STATUS:DRAFT
-STATUS:FINAL
-END:VJOURNAL
-END:VCALENDAR
-""".replace("\n", "\r\n"),
- False,
- set(),
- set((
- "[VJOURNAL] Too many properties: STATUS",
- )),
- ),
- )
-
- 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,))
Copied: PyCalendar/trunk/src/pycalendar/icalendar/tests/test_validation.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/icalendar/tests/test_validation.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/icalendar/tests/test_validation.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/icalendar/tests/test_validation.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,3189 @@
+##
+# 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.
+##
+
+from pycalendar.calendar import PyCalendar
+from pycalendar.exceptions import PyCalendarValidationError
+import unittest
+
+class TestValidation(unittest.TestCase):
+
+ def test_basic(self):
+
+ data = (
+ (
+ "No problems",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "No PRODID",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VCALENDAR] Missing or too many required property: PRODID",
+ )),
+ ),
+ )
+
+ 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",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "Unfixable only",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VCALENDAR] Missing or too many required property: PRODID",
+ )),
+ ),
+ (
+ "Fixable only",
+ """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
+DURATION:P1D
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VEVENT] Properties must not both be present: DTEND, DURATION",
+ )),
+ ),
+ (
+ "Fixable and unfixable",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VCALENDAR] Missing or too many required property: PRODID",
+ "[VEVENT] Properties must not both be present: DTEND, DURATION",
+ )),
+ ),
+ )
+
+ for title, test_old, test_new, test_fixed, test_unfixed in data:
+ cal = PyCalendar.parseText(test_old)
+ fixed, unfixed = cal.validate(doFix=False, doRaise=False)
+ 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,))
+
+
+ def test_mode_fix_no_raise(self):
+
+ data = (
+ (
+ "OK",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "Unfixable only",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VCALENDAR] Missing or too many required property: PRODID",
+ )),
+ ),
+ (
+ "Fixable only",
+ """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
+DURATION:P1D
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set((
+ "[VEVENT] Properties must not both be present: DTEND, DURATION",
+ )),
+ set(),
+ ),
+ (
+ "Fixable and unfixable",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set((
+ "[VEVENT] Properties must not both be present: DTEND, DURATION",
+ )),
+ set((
+ "[VCALENDAR] Missing or too many required property: PRODID",
+ )),
+ ),
+ )
+
+ for title, test_old, test_new, test_fixed, test_unfixed in data:
+ cal = PyCalendar.parseText(test_old)
+ fixed, unfixed = cal.validate(doFix=True, doRaise=False)
+ 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,))
+
+
+ def test_mode_no_fix_raise(self):
+
+ data = (
+ (
+ "OK",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ False,
+ ),
+ (
+ "Unfixable only",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VCALENDAR] Missing or too many required property: PRODID",
+ )),
+ True,
+ ),
+ (
+ "Fixable only",
+ """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
+DURATION:P1D
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VEVENT] Properties must not both be present: DTEND, DURATION",
+ )),
+ True,
+ ),
+ (
+ "Fixable and unfixable",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VCALENDAR] Missing or too many required property: PRODID",
+ "[VEVENT] Properties must not both be present: DTEND, DURATION",
+ )),
+ True,
+ ),
+ )
+
+ for title, test_old, test_new, test_fixed, test_unfixed, test_raises in data:
+ cal = PyCalendar.parseText(test_old)
+ if test_raises:
+ self.assertRaises(PyCalendarValidationError, cal.validate, doFix=False, doRaise=True)
+ else:
+ try:
+ fixed, unfixed = cal.validate(doFix=False, doRaise=False)
+ except:
+ self.fail(msg="Failed test: %s" % (title,))
+ 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,))
+
+
+ def test_mode_fix_raise(self):
+
+ data = (
+ (
+ "OK",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ False,
+ ),
+ (
+ "Unfixable only",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VCALENDAR] Missing or too many required property: PRODID",
+ )),
+ True,
+ ),
+ (
+ "Fixable only",
+ """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
+DURATION:P1D
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set((
+ "[VEVENT] Properties must not both be present: DTEND, DURATION",
+ )),
+ set(),
+ False,
+ ),
+ (
+ "Fixable and unfixable",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set((
+ "[VEVENT] Properties must not both be present: DTEND, DURATION",
+ )),
+ set((
+ "[VCALENDAR] Missing or too many required property: PRODID",
+ )),
+ True,
+ ),
+ )
+
+ for title, test_old, test_new, test_fixed, test_unfixed, test_raises in data:
+ cal = PyCalendar.parseText(test_old)
+ if test_raises:
+ self.assertRaises(PyCalendarValidationError, cal.validate, doFix=False, doRaise=True)
+ else:
+ try:
+ fixed, unfixed = cal.validate(doFix=True, doRaise=False)
+ except:
+ self.fail(msg="Failed test: %s" % (title,))
+ 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,))
+
+
+ def test_vevent(self):
+ data = (
+ (
+ "No problem",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "Missing required",
+ """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
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VEVENT] Missing or too many required property: DTSTAMP",
+ )),
+ ),
+ (
+ "Too many",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3253
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+SUMMARY:New Year's Eve
+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
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3253
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+SUMMARY:New Year's Eve
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VEVENT] Missing or too many required property: UID",
+ "[VEVENT] Too many properties present: SUMMARY",
+ )),
+ ),
+ (
+ "PROP value",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VEVENT] Property value incorrect: DTSTAMP",
+ )),
+ ),
+ (
+ "No DTSTART without METHOD",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:CANCEL
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20020101T000000Z
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:CANCEL
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20020101T000000Z
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "Combo fix",
+ """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
+DURATION:P1D
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set((
+ "[VEVENT] Properties must not both be present: DTEND, DURATION",
+ )),
+ set(),
+ ),
+ (
+ "Mismatch UNTIL as DATE-TIME",
+ """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
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231T120000;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set((
+ "[VEVENT] Value types must match: DTSTART, UNTIL",
+ )),
+ set(),
+ ),
+ (
+ "Mismatch UNTIL as DATE",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART:20020101T121212Z
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART:20020101T121212Z
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231T121212Z;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set((
+ "[VEVENT] Value types must match: DTSTART, UNTIL",
+ )),
+ set(),
+ ),
+ )
+
+ for title, test_old, test_new, test_fixed, test_unfixed in data:
+ cal = PyCalendar.parseText(test_old)
+ fixed, unfixed = cal.validate(doFix=True)
+ 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,))
+
+
+ def test_vfreebusy(self):
+ data = (
+ (
+ "No problem",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VFREEBUSY
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+END:VFREEBUSY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VFREEBUSY
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+END:VFREEBUSY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "Missing required",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VFREEBUSY
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+END:VFREEBUSY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VFREEBUSY
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+END:VFREEBUSY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VFREEBUSY] Missing or too many required property: DTSTAMP",
+ )),
+ ),
+ )
+
+ for title, test_old, test_new, test_fixed, test_unfixed in data:
+ cal = PyCalendar.parseText(test_old)
+ fixed, unfixed = cal.validate(doFix=True)
+ 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,))
+
+
+ def test_vjournal(self):
+ data = (
+ (
+ "No problem",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VJOURNAL
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTSTAMP:20020101T000000Z
+END:VJOURNAL
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VJOURNAL
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTSTAMP:20020101T000000Z
+END:VJOURNAL
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "Missing required",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VJOURNAL
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+END:VJOURNAL
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VJOURNAL
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+END:VJOURNAL
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VJOURNAL] Missing or too many required property: DTSTAMP",
+ )),
+ ),
+ )
+
+ for title, test_old, test_new, test_fixed, test_unfixed in data:
+ cal = PyCalendar.parseText(test_old)
+ fixed, unfixed = cal.validate(doFix=True)
+ 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,))
+
+
+ def test_vtimezone(self):
+ data = (
+ (
+ "No problem",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+LAST-MODIFIED:20050809T050000Z
+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
+END:VTIMEZONE
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+LAST-MODIFIED:20050809T050000Z
+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
+END:VTIMEZONE
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "Missing required",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTIMEZONE
+LAST-MODIFIED:20050809T050000Z
+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
+END:VTIMEZONE
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTIMEZONE
+LAST-MODIFIED:20050809T050000Z
+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
+END:VTIMEZONE
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VTIMEZONE] Missing or too many required property: TZID",
+ )),
+ ),
+ (
+ "Missing components",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+LAST-MODIFIED:20050809T050000Z
+END:VTIMEZONE
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+LAST-MODIFIED:20050809T050000Z
+END:VTIMEZONE
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VTIMEZONE] At least one component must be present: STANDARD or DAYLIGHT",
+ )),
+ ),
+ )
+
+ for title, test_old, test_new, test_fixed, test_unfixed in data:
+ cal = PyCalendar.parseText(test_old)
+ fixed, unfixed = cal.validate(doFix=True)
+ 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,))
+
+
+ def test_vtodo(self):
+ data = (
+ (
+ "No problem",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "Missing required",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VTODO] Missing or too many required property: DTSTAMP",
+ )),
+ ),
+ (
+ "Too many",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3253
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+SUMMARY:New Year's Eve
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3253
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+SUMMARY:New Year's Eve
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VTODO] Missing or too many required property: UID",
+ "[VTODO] Too many properties present: SUMMARY",
+ )),
+ ),
+ (
+ "PROP value",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+DTSTAMP:20020101T000000
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+DTSTAMP:20020101T000000
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VTODO] Property value incorrect: DTSTAMP",
+ )),
+ ),
+ (
+ "DURATION without DTSTART",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VTODO] Property must be present: DTSTART with DURATION",
+ )),
+ ),
+ (
+ "Combo fix",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+DUE;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set((
+ "[VTODO] Properties must not both be present: DUE, DURATION",
+ )),
+ set(),
+ ),
+ )
+
+ for title, test_old, test_new, test_fixed, test_unfixed in data:
+ cal = PyCalendar.parseText(test_old)
+ fixed, unfixed = cal.validate(doFix=True)
+ 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,))
+
+
+ def test_vavailability(self):
+ data = (
+ (
+ "No problem",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VAVAILABILITY
+UID:20111005T133225Z-00001 at example.com
+DTSTAMP:20111005T133225Z
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20111005T133225Z-00001-A at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DTEND;TZID=America/Montreal:20111002T170000
+DTSTAMP:20111005T133225Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+SUMMARY:Monday to Friday from 9:00 to 17:00
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VAVAILABILITY
+UID:20111005T133225Z-00001 at example.com
+DTSTAMP:20111005T133225Z
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20111005T133225Z-00001-A at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DTEND;TZID=America/Montreal:20111002T170000
+DTSTAMP:20111005T133225Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+SUMMARY:Monday to Friday from 9:00 to 17:00
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "Missing required",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VAVAILABILITY
+UID:20111005T133225Z-00001 at example.com
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20111005T133225Z-00001-A at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DTEND;TZID=America/Montreal:20111002T170000
+DTSTAMP:20111005T133225Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+SUMMARY:Monday to Friday from 9:00 to 17:00
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VAVAILABILITY
+UID:20111005T133225Z-00001 at example.com
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20111005T133225Z-00001-A at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DTEND;TZID=America/Montreal:20111002T170000
+DTSTAMP:20111005T133225Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+SUMMARY:Monday to Friday from 9:00 to 17:00
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VAVAILABILITY] Missing or too many required property: DTSTAMP",
+ )),
+ ),
+ (
+ "Too many",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VAVAILABILITY
+UID:20111005T133225Z-00001 at example.com
+UID:20111005T133225Z-00001 at example.com
+DTSTAMP:20111005T133225Z
+ORGANIZER:mailto:bernard at example.com
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20111005T133225Z-00001-A at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DTEND;TZID=America/Montreal:20111002T170000
+DTSTAMP:20111005T133225Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+SUMMARY:Monday to Friday from 9:00 to 17:00
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VAVAILABILITY
+UID:20111005T133225Z-00001 at example.com
+UID:20111005T133225Z-00001 at example.com
+DTSTAMP:20111005T133225Z
+ORGANIZER:mailto:bernard at example.com
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20111005T133225Z-00001-A at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DTEND;TZID=America/Montreal:20111002T170000
+DTSTAMP:20111005T133225Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+SUMMARY:Monday to Friday from 9:00 to 17:00
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VAVAILABILITY] Missing or too many required property: UID",
+ "[VAVAILABILITY] Too many properties present: ORGANIZER",
+ )),
+ ),
+ (
+ "Combo fix",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VAVAILABILITY
+UID:20111005T133225Z-00001 at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DURATION:P1D
+DTEND;TZID=America/Montreal:20111002T170000
+DTSTAMP:20111005T133225Z
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20111005T133225Z-00001-A at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DTEND;TZID=America/Montreal:20111002T170000
+DTSTAMP:20111005T133225Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+SUMMARY:Monday to Friday from 9:00 to 17:00
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VAVAILABILITY
+UID:20111005T133225Z-00001 at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DURATION:P1D
+DTSTAMP:20111005T133225Z
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20111005T133225Z-00001-A at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DTEND;TZID=America/Montreal:20111002T170000
+DTSTAMP:20111005T133225Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+SUMMARY:Monday to Friday from 9:00 to 17:00
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set((
+ "[VAVAILABILITY] Properties must not both be present: DTEND, DURATION",
+ )),
+ set(),
+ ),
+ )
+
+ for title, test_old, test_new, test_fixed, test_unfixed in data:
+ cal = PyCalendar.parseText(test_old)
+ fixed, unfixed = cal.validate(doFix=True)
+ 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,))
+
+
+ def test_available(self):
+ data = (
+ (
+ "No problem",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VAVAILABILITY
+UID:20111005T133225Z-00001 at example.com
+DTSTAMP:20111005T133225Z
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20111005T133225Z-00001-A at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DTEND;TZID=America/Montreal:20111002T170000
+DTSTAMP:20111005T133225Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+SUMMARY:Monday to Friday from 9:00 to 17:00
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VAVAILABILITY
+UID:20111005T133225Z-00001 at example.com
+DTSTAMP:20111005T133225Z
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20111005T133225Z-00001-A at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DTEND;TZID=America/Montreal:20111002T170000
+DTSTAMP:20111005T133225Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+SUMMARY:Monday to Friday from 9:00 to 17:00
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "Missing required",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VAVAILABILITY
+UID:20111005T133225Z-00001 at example.com
+DTSTAMP:20111005T133225Z
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20111005T133225Z-00001-A at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DTEND;TZID=America/Montreal:20111002T170000
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+SUMMARY:Monday to Friday from 9:00 to 17:00
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VAVAILABILITY
+UID:20111005T133225Z-00001 at example.com
+DTSTAMP:20111005T133225Z
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20111005T133225Z-00001-A at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DTEND;TZID=America/Montreal:20111002T170000
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+SUMMARY:Monday to Friday from 9:00 to 17:00
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[AVAILABLE] Missing or too many required property: DTSTAMP",
+ )),
+ ),
+ (
+ "Too many",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VAVAILABILITY
+UID:20111005T133225Z-00001 at example.com
+DTSTAMP:20111005T133225Z
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20111005T133225Z-00001-A at example.com
+UID:20111005T133225Z-00001-A at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DTEND;TZID=America/Montreal:20111002T170000
+DTSTAMP:20111005T133225Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+SUMMARY:Monday to Friday from 9:00 to 17:00
+SUMMARY:Monday to Friday from 9 am to 5 pm
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VAVAILABILITY
+UID:20111005T133225Z-00001 at example.com
+DTSTAMP:20111005T133225Z
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20111005T133225Z-00001-A at example.com
+UID:20111005T133225Z-00001-A at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DTEND;TZID=America/Montreal:20111002T170000
+DTSTAMP:20111005T133225Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+SUMMARY:Monday to Friday from 9:00 to 17:00
+SUMMARY:Monday to Friday from 9 am to 5 pm
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[AVAILABLE] Missing or too many required property: UID",
+ "[AVAILABLE] Too many properties present: SUMMARY",
+ )),
+ ),
+ (
+ "Combo fix",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VAVAILABILITY
+UID:20111005T133225Z-00001 at example.com
+DTSTAMP:20111005T133225Z
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20111005T133225Z-00001-A at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DURATION:P1D
+DTEND;TZID=America/Montreal:20111002T170000
+DTSTAMP:20111005T133225Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+SUMMARY:Monday to Friday from 9:00 to 17:00
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VAVAILABILITY
+UID:20111005T133225Z-00001 at example.com
+DTSTAMP:20111005T133225Z
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20111005T133225Z-00001-A at example.com
+DTSTART;TZID=America/Montreal:20111002T090000
+DURATION:P1D
+DTSTAMP:20111005T133225Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+SUMMARY:Monday to Friday from 9:00 to 17:00
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set((
+ "[AVAILABLE] Properties must not both be present: DTEND, DURATION",
+ )),
+ set(),
+ ),
+ )
+
+ for title, test_old, test_new, test_fixed, test_unfixed in data:
+ cal = PyCalendar.parseText(test_old)
+ fixed, unfixed = cal.validate(doFix=True)
+ 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,))
+
+
+ def test_valarm(self):
+ data = (
+ (
+ "No problem",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+TRIGGER:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+TRIGGER:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "Missing required",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VALARM] Missing or too many required property: TRIGGER",
+ )),
+ ),
+ (
+ "Missing required with fix",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:DISPLAY
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set((
+ "[VALARM] Missing required property: DESCRIPTION",
+ )),
+ set((
+ "[VALARM] Missing or too many required property: TRIGGER",
+ )),
+ ),
+ (
+ "Too many",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:AUDIO
+ATTACH:http://example.com/audio/boink
+ATTACH:http://example.com/audio/quack
+TRIGGER:-PT15M
+TRIGGER:-PT30M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:AUDIO
+ATTACH:http://example.com/audio/boink
+ATTACH:http://example.com/audio/quack
+TRIGGER:-PT15M
+TRIGGER:-PT30M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VALARM] Missing or too many required property: TRIGGER",
+ "[VALARM] Too many properties present: ATTACH",
+ )),
+ ),
+ (
+ "Too few",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:EMAIL
+DESCRIPTION:Reminder
+SUMMARY:Reminder
+TRIGGER:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:EMAIL
+DESCRIPTION:Reminder
+SUMMARY:Reminder
+TRIGGER:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VALARM] Missing required property: ATTENDEE",
+ )),
+ ),
+ (
+ "PROP value",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+DURATION:-P1D
+REPEAT:-1
+TRIGGER:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+DURATION:-P1D
+REPEAT:-1
+TRIGGER:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VALARM] Property value incorrect: REPEAT",
+ )),
+ ),
+ (
+ "DUARTION and REPEAT together",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:CANCEL
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+REPEAT:2
+TRIGGER:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:CANCEL
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+REPEAT:2
+TRIGGER:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VALARM] Properties must be present together: DURATION, REPEAT",
+ )),
+ ),
+ (
+ "Combo fix",
+ """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
+DURATION:P1D
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+END:VALARM
+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
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set((
+ "[VEVENT] Properties must not both be present: DTEND, DURATION",
+ )),
+ set((
+ "[VALARM] Missing or too many required property: TRIGGER",
+ )),
+ ),
+ (
+ "ACKNOWLEDGED OK",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACKNOWLEDGED:20020101T000000Z
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+TRIGGER:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACKNOWLEDGED:20020101T000000Z
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+TRIGGER:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "ACKNOWLEDGED Bad value",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACKNOWLEDGED:20020101T000000
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+TRIGGER:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACKNOWLEDGED:20020101T000000
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+TRIGGER:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VALARM] Property value incorrect: ACKNOWLEDGED",
+ )),
+ ),
+ (
+ "ACKNOWLEDGED too many",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACKNOWLEDGED:20020101T000000Z
+ACKNOWLEDGED:20020102T000000Z
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+TRIGGER:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACKNOWLEDGED:20020101T000000Z
+ACKNOWLEDGED:20020102T000000Z
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+TRIGGER:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VALARM] Too many properties present: ACKNOWLEDGED",
+ )),
+ ),
+ (
+ "No problem ACTION=URI",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:URI
+TRIGGER:-PT15M
+URL:http://test.example.com
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:URI
+TRIGGER:-PT15M
+URL:http://test.example.com
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "ACTION=URI missing URL",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:URI
+TRIGGER:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:URI
+TRIGGER:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VALARM] Missing or too many required property: URL",
+ )),
+ ),
+ (
+ "No problem ACTION=NONE",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:NONE
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:NONE
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "No problem ACTION=NONE with TRIGGER",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:NONE
+TRIGGER;VALUE=DATE-TIME:19760401T005545Z
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:NONE
+TRIGGER;VALUE=DATE-TIME:19760401T005545Z
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ )
+
+ for title, test_old, test_new, test_fixed, test_unfixed in data:
+ cal = PyCalendar.parseText(test_old)
+ fixed, unfixed = cal.validate(doFix=True)
+ 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,))
+
+
+ def test_xcomponents(self):
+ data = (
+ (
+ "No problem #1",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:X-COMPONENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+SUMMARY:New Year's Day
+END:X-COMPONENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:X-COMPONENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+SUMMARY:New Year's Day
+END:X-COMPONENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "No problem #2",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:X-COMPONENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTEND;VALUE=DATE:20020102
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+SUMMARY:New Year's Day
+END:X-COMPONENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:X-COMPONENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTEND;VALUE=DATE:20020102
+DTSTART;VALUE=DATE:20020101
+DURATION:P1D
+SUMMARY:New Year's Day
+END:X-COMPONENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "Prop value",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:X-COMPONENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000
+DTSTART;VALUE=DATE:20020101
+SUMMARY:New Year's Day
+END:X-COMPONENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:X-COMPONENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000
+DTSTART;VALUE=DATE:20020101
+SUMMARY:New Year's Day
+END:X-COMPONENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[X-COMPONENT] Property value incorrect: DTSTAMP",
+ )),
+ ),
+ )
+
+ for title, test_old, test_new, test_fixed, test_unfixed in data:
+ cal = PyCalendar.parseText(test_old)
+ fixed, unfixed = cal.validate(doFix=True)
+ 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,))
+
+
+ def test_STATUS_fix(self):
+ """
+ Test calendarserver issue where multiple STATUS properties can be present in components.
+ Want to make sure we report those as an error or fix them.
+ """
+ data = (
+ (
+ "1.1 Two STATUS in VEVENT - fix",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+STATUS:CONFIRMED
+STATUS:CANCELLED
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+STATUS:CANCELLED
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ True,
+ set((
+ "[VEVENT] Too many properties: STATUS",
+ )),
+ set(),
+ ),
+ (
+ "1.2 Two STATUS in VEVENT - no fix",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+STATUS:CONFIRMED
+STATUS:CANCELLED
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+STATUS:CONFIRMED
+STATUS:CANCELLED
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ False,
+ set(),
+ set((
+ "[VEVENT] Too many properties: STATUS",
+ )),
+ ),
+ (
+ "1.3 Two STATUS in VEVENT, none CANCELLED - fix",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+STATUS:CONFIRMED
+STATUS:TENTATIVE
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+STATUS:CONFIRMED
+STATUS:TENTATIVE
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ True,
+ set(),
+ set((
+ "[VEVENT] Too many properties: STATUS",
+ )),
+ ),
+ (
+ "1.4 Two STATUS in VEVENT, none CANCELLED - no fix",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+STATUS:CONFIRMED
+STATUS:TENTATIVE
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+STATUS:CONFIRMED
+STATUS:TENTATIVE
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ False,
+ set(),
+ set((
+ "[VEVENT] Too many properties: STATUS",
+ )),
+ ),
+ (
+ "2.1 Two STATUS in VTODO - fix",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+STATUS:NEEDS-ACTION
+STATUS:CANCELLED
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+STATUS:CANCELLED
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ True,
+ set((
+ "[VTODO] Too many properties: STATUS",
+ )),
+ set(),
+ ),
+ (
+ "2.2 Two STATUS in VTODO - no fix",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+STATUS:NEEDS-ACTION
+STATUS:CANCELLED
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+STATUS:NEEDS-ACTION
+STATUS:CANCELLED
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ False,
+ set(),
+ set((
+ "[VTODO] Too many properties: STATUS",
+ )),
+ ),
+ (
+ "2.3 Two STATUS in VTODO, none CANCELLED - fix",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+STATUS:NEEDS-ACTION
+STATUS:COMPLETED
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+STATUS:NEEDS-ACTION
+STATUS:COMPLETED
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ True,
+ set(),
+ set((
+ "[VTODO] Too many properties: STATUS",
+ )),
+ ),
+ (
+ "2.4 Two STATUS in VTODO, none CANCELLED - no fix",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+STATUS:NEEDS-ACTION
+STATUS:COMPLETED
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTODO
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DUE;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+STATUS:NEEDS-ACTION
+STATUS:COMPLETED
+SUMMARY:New Year's Day
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ False,
+ set(),
+ set((
+ "[VTODO] Too many properties: STATUS",
+ )),
+ ),
+ (
+ "3.1 Two STATUS in VJOURNAL - fix",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VJOURNAL
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTSTAMP:20020101T000000Z
+STATUS:DRAFT
+STATUS:CANCELLED
+END:VJOURNAL
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VJOURNAL
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTSTAMP:20020101T000000Z
+STATUS:CANCELLED
+END:VJOURNAL
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ True,
+ set((
+ "[VJOURNAL] Too many properties: STATUS",
+ )),
+ set(),
+ ),
+ (
+ "3.2 Two STATUS in VJOURNAL - no fix",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VJOURNAL
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTSTAMP:20020101T000000Z
+STATUS:DRAFT
+STATUS:CANCELLED
+END:VJOURNAL
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VJOURNAL
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTSTAMP:20020101T000000Z
+STATUS:DRAFT
+STATUS:CANCELLED
+END:VJOURNAL
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ False,
+ set(),
+ set((
+ "[VJOURNAL] Too many properties: STATUS",
+ )),
+ ),
+ (
+ "3.3 Two STATUS in VJOURNAL, none CANCELLED - fix",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VJOURNAL
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTSTAMP:20020101T000000Z
+STATUS:DRAFT
+STATUS:FINAL
+END:VJOURNAL
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VJOURNAL
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTSTAMP:20020101T000000Z
+STATUS:DRAFT
+STATUS:FINAL
+END:VJOURNAL
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ True,
+ set(),
+ set((
+ "[VJOURNAL] Too many properties: STATUS",
+ )),
+ ),
+ (
+ "3.4 Two STATUS in VJOURNAL, none CANCELLED - no fix",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VJOURNAL
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTSTAMP:20020101T000000Z
+STATUS:DRAFT
+STATUS:FINAL
+END:VJOURNAL
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VJOURNAL
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTSTAMP:20020101T000000Z
+STATUS:DRAFT
+STATUS:FINAL
+END:VJOURNAL
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ False,
+ set(),
+ set((
+ "[VJOURNAL] Too many properties: STATUS",
+ )),
+ ),
+ )
+
+ 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,))
Deleted: PyCalendar/trunk/src/pycalendar/icalendar/validation.py
===================================================================
--- PyCalendar/branches/server/src/pycalendar/icalendar/validation.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/icalendar/validation.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,37 +0,0 @@
-##
-# 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.
-##
-
-from pycalendar import definitions
-from pycalendar.validation import partial, PropertyValueChecks
-
-ICALENDAR_VALUE_CHECKS = {
- definitions.cICalProperty_CALSCALE: partial(PropertyValueChecks.stringValue, "GREGORIAN"),
- definitions.cICalProperty_VERSION: partial(PropertyValueChecks.stringValue, "2.0"),
-
- definitions.cICalProperty_PERCENT_COMPLETE: partial(PropertyValueChecks.numericRange, 0, 100),
- definitions.cICalProperty_PRIORITY: partial(PropertyValueChecks.numericRange, 0, 9),
-
- definitions.cICalProperty_COMPLETED: PropertyValueChecks.alwaysUTC,
-
- definitions.cICalProperty_REPEAT: PropertyValueChecks.positiveIntegerOrZero,
-
- definitions.cICalProperty_CREATED: PropertyValueChecks.alwaysUTC,
- definitions.cICalProperty_DTSTAMP: PropertyValueChecks.alwaysUTC,
- definitions.cICalProperty_LAST_MODIFIED: PropertyValueChecks.alwaysUTC,
- definitions.cICalProperty_SEQUENCE: PropertyValueChecks.positiveIntegerOrZero,
-
- definitions.cICalProperty_ACKNOWLEDGED: PropertyValueChecks.alwaysUTC,
-}
Copied: PyCalendar/trunk/src/pycalendar/icalendar/validation.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/icalendar/validation.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/icalendar/validation.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/icalendar/validation.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,37 @@
+##
+# 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.
+##
+
+from pycalendar import definitions
+from pycalendar.validation import partial, PropertyValueChecks
+
+ICALENDAR_VALUE_CHECKS = {
+ definitions.cICalProperty_CALSCALE: partial(PropertyValueChecks.stringValue, "GREGORIAN"),
+ definitions.cICalProperty_VERSION: partial(PropertyValueChecks.stringValue, "2.0"),
+
+ definitions.cICalProperty_PERCENT_COMPLETE: partial(PropertyValueChecks.numericRange, 0, 100),
+ definitions.cICalProperty_PRIORITY: partial(PropertyValueChecks.numericRange, 0, 9),
+
+ definitions.cICalProperty_COMPLETED: PropertyValueChecks.alwaysUTC,
+
+ definitions.cICalProperty_REPEAT: PropertyValueChecks.positiveIntegerOrZero,
+
+ definitions.cICalProperty_CREATED: PropertyValueChecks.alwaysUTC,
+ definitions.cICalProperty_DTSTAMP: PropertyValueChecks.alwaysUTC,
+ definitions.cICalProperty_LAST_MODIFIED: PropertyValueChecks.alwaysUTC,
+ definitions.cICalProperty_SEQUENCE: PropertyValueChecks.positiveIntegerOrZero,
+
+ definitions.cICalProperty_ACKNOWLEDGED: PropertyValueChecks.alwaysUTC,
+}
Modified: PyCalendar/trunk/src/pycalendar/integervalue.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/integervalue.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/integervalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -16,35 +16,45 @@
# iCalendar UTC Offset value
-from value import PyCalendarValue
+from pycalendar import xmldefs
+from pycalendar.value import PyCalendarValue
-class PyCalendarIntegerValue( PyCalendarValue ):
+class PyCalendarIntegerValue(PyCalendarValue):
- def __init__(self, value = None, copyit = None):
- if value:
- self.mValue = value
- elif copyit:
- self.mValue = copyit.mValue
- else:
- self.mValue = 0
+ 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)
+PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_INTEGER, PyCalendarIntegerValue, xmldefs.value_integer)
Modified: PyCalendar/trunk/src/pycalendar/itipdefinitions.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/itipdefinitions.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/itipdefinitions.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -39,3 +39,4 @@
cICalMIMEComponent_VTODO = "vtodo"
cICalMIMEComponent_VJOURNAL = "vjournal"
cICalMIMEComponent_VFREEBUSY = "vfreebusy"
+cICalMIMEComponent_VAVAILABILITY = "vavailability"
Modified: PyCalendar/trunk/src/pycalendar/locale.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/locale.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/locale.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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
Modified: PyCalendar/trunk/src/pycalendar/manager.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/manager.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/manager.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,22 +14,16 @@
# limitations under the License.
##
-from calendar import PyCalendar
-from property import PyCalendarProperty
-from timezone import PyCalendarTimezone
+from pycalendar.timezone import PyCalendarTimezone
class PyCalendarManager(object):
sICalendarManager = None
- @staticmethod
- def loadStatics():
- PyCalendar.loadStatics()
- PyCalendarProperty.loadStatics()
-
def __init__(self):
- self.mDefaultTimezone = PyCalendarTimezone()
+ PyCalendarTimezone.sDefaultTimezone = PyCalendarTimezone()
+
def initManager(self):
# TODO: - read in timezones from vtimezones.ics file
@@ -37,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":
@@ -46,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()
Modified: PyCalendar/trunk/src/pycalendar/multivalue.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/multivalue.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/multivalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,54 +14,83 @@
# limitations under the License.
##
-from value import PyCalendarValue
+from pycalendar.value import PyCalendarValue
-class PyCalendarMultiValue( PyCalendarValue ):
+class PyCalendarMultiValue(PyCalendarValue):
- def __init__( self, type = None, copyit = None ):
+ def __init__(self, type):
- if type:
- self.mType = type
- self.mValues = []
- elif copyit:
- self.mType = copyit.mType
- self.mValues = [i.copy() for i in copyit.mValues]
+ self.mType = type
+ self.mValues = []
- def getType( self ):
+
+ 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):
return self.mType
+
def getRealType(self):
return PyCalendarValue.VALUETYPE_MULTIVALUE
- def getValues( self ):
+
+ def getValue(self):
return self.mValues
- def addValue( self, value ):
- self.mValues.append( value )
- def parse( self, data ):
+ def getValues(self):
+ return self.mValues
+
+
+ def addValue(self, value):
+ self.mValues.append(value)
+
+
+ def setValue(self, value):
+ newValues = []
+ for v in value:
+ pyCalendarValue = PyCalendarValue.createFromType(self.mType)
+ pyCalendarValue.setValue(v)
+ newValues.append(pyCalendarValue)
+ self.mValues = newValues
+
+
+ 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
-PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_MULTIVALUE, PyCalendarMultiValue)
-
\ No newline at end of file
+
+ def writeXML(self, node, namespace):
+ for iter in self.mValues:
+ iter.writeXML(node, namespace)
+
+PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_MULTIVALUE, PyCalendarMultiValue, None)
Copied: PyCalendar/trunk/src/pycalendar/n.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/n.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/n.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/n.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,124 @@
+##
+# 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.
+##
+
+# vCard ADR value
+
+from pycalendar import utils
+from pycalendar.valueutils import ValueMixin
+
+class N(ValueMixin):
+ """
+ mValue is a tuple of seven str or tuples of str
+ """
+
+ (
+ LAST,
+ FIRST,
+ MIDDLE,
+ PREFIX,
+ SUFFIX,
+ MAXITEMS
+ ) = range(6)
+
+ 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):
+ 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)
+
+ results = []
+ for i in (N.PREFIX, N.FIRST, N.MIDDLE, N.LAST, N.SUFFIX):
+ result = _stringOrList(self.mValue[i])
+ if result:
+ results.append(result)
+
+ 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
Copied: PyCalendar/trunk/src/pycalendar/nvalue.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/nvalue.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/nvalue.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/nvalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,51 @@
+##
+# 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.
+##
+
+# vCard ADR value
+
+from pycalendar.n import N
+from pycalendar.value import PyCalendarValue
+
+class NValue(PyCalendarValue):
+
+ 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
+
+PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_N, NValue, None)
Copied: PyCalendar/trunk/src/pycalendar/orgvalue.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/orgvalue.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/orgvalue.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/orgvalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,54 @@
+##
+# 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.
+##
+
+# vCard ORG value
+
+from pycalendar import utils
+from pycalendar.value import PyCalendarValue
+
+class OrgValue(PyCalendarValue):
+ """
+ mValue is a str or tuple of str
+ """
+
+ 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
+
+PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_ORG, OrgValue, None)
Modified: PyCalendar/trunk/src/pycalendar/outputfilter.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/outputfilter.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/outputfilter.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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
Copied: PyCalendar/trunk/src/pycalendar/parser.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/parser.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/parser.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/parser.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,56 @@
+##
+# 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.
+##
+
+class ParserContext(object):
+ """
+ Ultimately want to have these states as per-object so we can pass a context down
+ through the entire parse call chain so that we can use different error handling for
+ different situations. For now though it is a module static.
+ """
+
+ (
+ 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
+ INVALID_COLON_ESCAPE_SEQUENCE = PARSER_FIX
+
+ # 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 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
+ 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
Modified: PyCalendar/trunk/src/pycalendar/period.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/period.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/period.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,102 +14,150 @@
# limitations under the License.
##
-import cStringIO as StringIO
+from pycalendar import xmldefs
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.duration import PyCalendarDuration
+from pycalendar.valueutils import ValueMixin
+import xml.etree.cElementTree as XML
-from datetime import PyCalendarDateTime
-from duration import PyCalendarDuration
+class PyCalendarPeriod(ValueMixin):
-class PyCalendarPeriod(object):
+ def __init__(self, start=None, end=None, duration=None):
- def __init__( self, start = None, end = None, duration = None, copyit = None ):
-
- if (start is not None) and (end is not None):
- self.mStart = start
+ self.mStart = start if start is not None else PyCalendarDateTime()
+
+ if end is not None:
self.mEnd = end
self.mDuration = self.mEnd - self.mStart
self.mUseDuration = False
- elif (start is not None) and (duration is not None):
- self.mStart = start
+ elif duration is not None:
self.mDuration = duration
self.mEnd = self.mStart + self.mDuration
self.mUseDuration = True
- elif (copyit is not None):
- self.mStart = PyCalendarDateTime(copyit=copyit.mStart)
- self.mEnd = PyCalendarDateTime(copyit=copyit.mEnd)
- self.mDuration = PyCalendarDuration(copyit=copyit.mDuration)
- self.mUseDuration = copyit.mUseDuration
else:
- self.mStart = PyCalendarDateTime()
- self.mEnd = PyCalendarDateTime()
+ self.mEnd = self.mStart.duplicate()
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):
- os = StringIO.StringIO()
- self.generate(os)
- return os.getvalue()
+ return "PyCalendarPeriod %s" % (self.getText(),)
- def __eq__( self, comp ):
+
+ def __str__(self):
+ return self.getText()
+
+
+ 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
+
+
+ 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.add( self.mDuration )
+ 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 getStart( self ):
+
+ def writeXML(self, node, namespace):
+ start = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.period_start))
+ start.text = self.mStart.getXMLText()
+
+ if self.mUseDuration:
+ duration = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.period_duration))
+ duration.text = self.mDuration.getText()
+ else:
+ end = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.period_end))
+ end.text = self.mEnd.getXMLText()
+
+
+ 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 isDateWithinPeriod( self, dt ):
+
+ def getUseDuration(self):
+ return self.mUseDuration
+
+
+ def setUseDuration(self, use):
+ self.mUseDuration = use
+
+
+ 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 describeDuration( self ):
+
+ def adjustToUTC(self):
+ self.mStart.adjustToUTC()
+ self.mEnd.adjustToUTC()
+
+
+ def describeDuration(self):
return ""
Modified: PyCalendar/trunk/src/pycalendar/periodvalue.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/periodvalue.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/periodvalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,32 +14,42 @@
# limitations under the License.
##
-from period import PyCalendarPeriod
-from value import PyCalendarValue
+from pycalendar import xmldefs
+from pycalendar.period import PyCalendarPeriod
+from pycalendar.value import PyCalendarValue
-class PyCalendarPeriodValue( PyCalendarValue ):
+class PyCalendarPeriodValue(PyCalendarValue):
- def __init__( self, value = None, copyit = None ):
- if value:
- self.mValue = value
- elif copyit:
- self.mValue = PyCalendarPeriod( copyit=copyit.mValue )
- else:
- self.mValue = PyCalendarPeriod()
+ def __init__(self, value=None):
+ self.mValue = value if value is not None else PyCalendarPeriod()
- def getType( self ):
+
+ def duplicate(self):
+ return PyCalendarPeriodValue(self.mValue.duplicate())
+
+
+ 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 getValue( self ):
+
+ 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_PERIOD, PyCalendarPeriodValue)
+PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_PERIOD, PyCalendarPeriodValue, xmldefs.value_period)
Modified: PyCalendar/trunk/src/pycalendar/plaintextvalue.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/plaintextvalue.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/plaintextvalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -16,31 +16,40 @@
# iCalendar UTC Offset value
-from value import PyCalendarValue
+from pycalendar.value import PyCalendarValue
-class PyCalendarPlainTextValue( PyCalendarValue ):
+class PyCalendarPlainTextValue(PyCalendarValue):
- def __init__(self, copyit=None):
-
- if copyit:
- self.mValue = copyit.mValue
- else:
- self.mValue = ''
+ def __init__(self, value=''):
+ self.mValue = value
- def parse( self, data ):
+
+ def duplicate(self):
+ return self.__class__(self.mValue)
+
+
+ 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
Modified: PyCalendar/trunk/src/pycalendar/property.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/property.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/property.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,119 +14,296 @@
# limitations under the License.
##
+from pycalendar import definitions, xmldefs
+from pycalendar import stringutils
+from pycalendar.attribute import PyCalendarAttribute
+from pycalendar.binaryvalue import PyCalendarBinaryValue
+from pycalendar.caladdressvalue import PyCalendarCalAddressValue
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.datetimevalue import PyCalendarDateTimeValue
+from pycalendar.duration import PyCalendarDuration
+from pycalendar.durationvalue import PyCalendarDurationValue
+from pycalendar.exceptions import PyCalendarInvalidProperty
+from pycalendar.integervalue import PyCalendarIntegerValue
+from pycalendar.multivalue import PyCalendarMultiValue
+from pycalendar.period import PyCalendarPeriod
+from pycalendar.periodvalue import PyCalendarPeriodValue
+from pycalendar.plaintextvalue import PyCalendarPlainTextValue
+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
-from attribute import PyCalendarAttribute
-from caladdressvalue import PyCalendarCalAddressValue
-from datetime import PyCalendarDateTime
-from datetimevalue import PyCalendarDateTimeValue
-from duration import PyCalendarDuration
-from durationvalue import PyCalendarDurationValue
-from integervalue import PyCalendarIntegerValue
-from multivalue import PyCalendarMultiValue
-from period import PyCalendarPeriod
-from periodvalue import PyCalendarPeriodValue
-from plaintextvalue import PyCalendarPlainTextValue
-from recurrence import PyCalendarRecurrence
-from recurrencevalue import PyCalendarRecurrenceValue
-from urivalue import PyCalendarURIValue
-from utcoffsetvalue import PyCalendarUTCOffsetValue
-from value import PyCalendarValue
-import definitions
-import stringutils
-
class PyCalendarProperty(object):
-# protected string mName
-#
-# protected MultiMap mAttributes
-#
-# protected ICalendarValue mValue
-#
+ sDefaultValueTypeMap = {
- sDefaultValueTypeMap = None
- sValueTypeMap = None
- sTypeValueMap = None
- sMultiValues = None
+ # 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,
+ definitions.cICalProperty_CLASS : PyCalendarValue.VALUETYPE_TEXT,
+ definitions.cICalProperty_COMMENT : PyCalendarValue.VALUETYPE_TEXT,
+ definitions.cICalProperty_DESCRIPTION : PyCalendarValue.VALUETYPE_TEXT,
+ definitions.cICalProperty_GEO : PyCalendarValue.VALUETYPE_GEO,
+ definitions.cICalProperty_LOCATION : PyCalendarValue.VALUETYPE_TEXT,
+ definitions.cICalProperty_PERCENT_COMPLETE : PyCalendarValue.VALUETYPE_INTEGER,
+ definitions.cICalProperty_PRIORITY : PyCalendarValue.VALUETYPE_INTEGER,
+ definitions.cICalProperty_RESOURCES : PyCalendarValue.VALUETYPE_TEXT,
+ definitions.cICalProperty_STATUS : PyCalendarValue.VALUETYPE_TEXT,
+ definitions.cICalProperty_SUMMARY : PyCalendarValue.VALUETYPE_TEXT,
+
+ # 5545 Section 3.8.2
+ definitions.cICalProperty_COMPLETED : PyCalendarValue.VALUETYPE_DATETIME,
+ definitions.cICalProperty_DTEND : PyCalendarValue.VALUETYPE_DATETIME,
+ definitions.cICalProperty_DUE : PyCalendarValue.VALUETYPE_DATETIME,
+ definitions.cICalProperty_DTSTART : PyCalendarValue.VALUETYPE_DATETIME,
+ definitions.cICalProperty_DURATION : PyCalendarValue.VALUETYPE_DURATION,
+ definitions.cICalProperty_FREEBUSY : PyCalendarValue.VALUETYPE_PERIOD,
+ definitions.cICalProperty_TRANSP : PyCalendarValue.VALUETYPE_TEXT,
+
+ # 5545 Section 3.8.3
+ definitions.cICalProperty_TZID : PyCalendarValue.VALUETYPE_TEXT,
+ definitions.cICalProperty_TZNAME : PyCalendarValue.VALUETYPE_TEXT,
+ definitions.cICalProperty_TZOFFSETFROM : PyCalendarValue.VALUETYPE_UTC_OFFSET,
+ definitions.cICalProperty_TZOFFSETTO : PyCalendarValue.VALUETYPE_UTC_OFFSET,
+ definitions.cICalProperty_TZURL : PyCalendarValue.VALUETYPE_URI,
+
+ # 5545 Section 3.8.4
+ definitions.cICalProperty_ATTENDEE : PyCalendarValue.VALUETYPE_CALADDRESS,
+ definitions.cICalProperty_CONTACT : PyCalendarValue.VALUETYPE_TEXT,
+ definitions.cICalProperty_ORGANIZER : PyCalendarValue.VALUETYPE_CALADDRESS,
+ definitions.cICalProperty_RECURRENCE_ID : PyCalendarValue.VALUETYPE_DATETIME,
+ definitions.cICalProperty_RELATED_TO : PyCalendarValue.VALUETYPE_TEXT,
+ definitions.cICalProperty_URL : PyCalendarValue.VALUETYPE_URI,
+ definitions.cICalProperty_UID : PyCalendarValue.VALUETYPE_TEXT,
+
+ # 5545 Section 3.8.5
+ definitions.cICalProperty_EXDATE : PyCalendarValue.VALUETYPE_DATETIME,
+ definitions.cICalProperty_EXRULE : PyCalendarValue.VALUETYPE_RECUR, # 2445 only
+ definitions.cICalProperty_RDATE : PyCalendarValue.VALUETYPE_DATETIME,
+ definitions.cICalProperty_RRULE : PyCalendarValue.VALUETYPE_RECUR,
+
+ # 5545 Section 3.8.6
+ definitions.cICalProperty_ACTION : PyCalendarValue.VALUETYPE_TEXT,
+ definitions.cICalProperty_REPEAT : PyCalendarValue.VALUETYPE_INTEGER,
+ definitions.cICalProperty_TRIGGER : PyCalendarValue.VALUETYPE_DURATION,
+
+ # 5545 Section 3.8.7
+ definitions.cICalProperty_CREATED : PyCalendarValue.VALUETYPE_DATETIME,
+ definitions.cICalProperty_DTSTAMP : PyCalendarValue.VALUETYPE_DATETIME,
+ definitions.cICalProperty_LAST_MODIFIED : PyCalendarValue.VALUETYPE_DATETIME,
+ definitions.cICalProperty_SEQUENCE : PyCalendarValue.VALUETYPE_INTEGER,
+
+ # 5545 Section 3.8.8
+ definitions.cICalProperty_REQUEST_STATUS : PyCalendarValue.VALUETYPE_REQUEST_STATUS,
+
+ # Extensions: draft-daboo-valarm-extensions-03
+ definitions.cICalProperty_ACKNOWLEDGED : PyCalendarValue.VALUETYPE_DATETIME,
+
+ # Apple Extensions
+ 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,
+ definitions.cICalProperty_ALARM_X_LASTTRIGGER : PyCalendarValue.VALUETYPE_DATETIME,
+ definitions.cICalProperty_ALARM_X_ALARMSTATUS : PyCalendarValue.VALUETYPE_TEXT,
+ }
+
+ sValueTypeMap = {
+
+ # 5545 Section 3.3
+ definitions.cICalValue_BINARY : PyCalendarValue.VALUETYPE_BINARY,
+ definitions.cICalValue_BOOLEAN : PyCalendarValue.VALUETYPE_BOOLEAN,
+ definitions.cICalValue_CAL_ADDRESS : PyCalendarValue.VALUETYPE_CALADDRESS,
+ definitions.cICalValue_DATE : PyCalendarValue.VALUETYPE_DATE,
+ definitions.cICalValue_DATE_TIME : PyCalendarValue.VALUETYPE_DATETIME,
+ definitions.cICalValue_DURATION : PyCalendarValue.VALUETYPE_DURATION,
+ definitions.cICalValue_FLOAT : PyCalendarValue.VALUETYPE_FLOAT,
+ definitions.cICalValue_INTEGER : PyCalendarValue.VALUETYPE_INTEGER,
+ definitions.cICalValue_PERIOD : PyCalendarValue.VALUETYPE_PERIOD,
+ definitions.cICalValue_RECUR : PyCalendarValue.VALUETYPE_RECUR,
+ definitions.cICalValue_TEXT : PyCalendarValue.VALUETYPE_TEXT,
+ definitions.cICalValue_TIME : PyCalendarValue.VALUETYPE_TIME,
+ definitions.cICalValue_URI : PyCalendarValue.VALUETYPE_URI,
+ definitions.cICalValue_UTC_OFFSET : PyCalendarValue.VALUETYPE_UTC_OFFSET,
+ }
+
+ sTypeValueMap = {
+
+ # 5545 Section 3.3
+ PyCalendarValue.VALUETYPE_BINARY : definitions.cICalValue_BINARY,
+ PyCalendarValue.VALUETYPE_BOOLEAN : definitions.cICalValue_BOOLEAN,
+ PyCalendarValue.VALUETYPE_CALADDRESS : definitions.cICalValue_CAL_ADDRESS,
+ PyCalendarValue.VALUETYPE_DATE : definitions.cICalValue_DATE,
+ PyCalendarValue.VALUETYPE_DATETIME : definitions.cICalValue_DATE_TIME,
+ PyCalendarValue.VALUETYPE_DURATION : definitions.cICalValue_DURATION,
+ PyCalendarValue.VALUETYPE_FLOAT : definitions.cICalValue_FLOAT,
+ PyCalendarValue.VALUETYPE_GEO : definitions.cICalValue_FLOAT,
+ PyCalendarValue.VALUETYPE_INTEGER : definitions.cICalValue_INTEGER,
+ PyCalendarValue.VALUETYPE_PERIOD : definitions.cICalValue_PERIOD,
+ PyCalendarValue.VALUETYPE_RECUR : definitions.cICalValue_RECUR,
+ PyCalendarValue.VALUETYPE_TEXT : definitions.cICalValue_TEXT,
+ PyCalendarValue.VALUETYPE_REQUEST_STATUS : definitions.cICalValue_TEXT,
+ PyCalendarValue.VALUETYPE_TIME : definitions.cICalValue_TIME,
+ PyCalendarValue.VALUETYPE_URI : definitions.cICalValue_URI,
+ PyCalendarValue.VALUETYPE_UTC_OFFSET : definitions.cICalValue_UTC_OFFSET,
+ }
+
+ sMultiValues = set((
+ definitions.cICalProperty_CATEGORIES,
+ definitions.cICalProperty_RESOURCES,
+ definitions.cICalProperty_FREEBUSY,
+ definitions.cICalProperty_EXDATE,
+ definitions.cICalProperty_RDATE,
+ ))
+
@staticmethod
- def loadStatics():
- PyCalendarProperty._init_map()
+ def registerDefaultValue(propname, valuetype):
+ if propname not in PyCalendarProperty.sDefaultValueTypeMap:
+ PyCalendarProperty.sDefaultValueTypeMap[propname] = valuetype
- def __init__(self, arg1 = None, arg2 = None, arg3 = None):
+
+ def __init__(self, name=None, value=None, valuetype=None):
self._init_PyCalendarProperty()
+ self.mName = name if name is not None else ""
- arg1str = isinstance(arg1, str)
- arg2str = isinstance(arg2, str)
- if arg1str:
- if isinstance(arg2, int) and (arg3 is None):
- self.mName = arg1
- self.init_attr_value_int(arg2)
- elif arg2str and (arg3 is None):
- self.mName = arg1
- self._init_attr_value_text(arg2, PyCalendarValue.VALUETYPE_TEXT)
- elif arg2str and isinstance(arg3, int):
- self.mName = arg1
- self._init_attr_value_text(arg2, arg3)
- elif isinstance(arg2, PyCalendarDateTime) and (arg3 is None):
- self.mName = arg1
- self._init_attr_value_datetime(arg2)
- elif isinstance(arg2, list) and (arg3 is None):
- self.mName = arg1
- self._init_attr_value_datetimelist(arg2)
- elif isinstance(arg2, PyCalendarDuration) and (arg3 is None):
- self.mName = arg1
- self._init_attr_value_duration(arg2)
- elif isinstance(arg2, PyCalendarPeriod) and (arg3 is None):
- self.mName = arg1
- self._init_attr_value_period(arg2)
- elif isinstance(arg2, PyCalendarRecurrence) and (arg3 is None):
- self.mName = arg1
- self._init_attr_value_recur(arg2)
- elif isinstance(arg2, PyCalendarUTCOffsetValue) and (arg3 is None):
- self.mName = arg1
- self._init_attr_value_utcoffset(arg2)
- elif isinstance(arg1, PyCalendarProperty) and (arg2 is None) and (arg3 is None):
- self._copy_PyCalendarProperty(arg1)
+ # 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_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)
+ else:
+ period_list = False
+ if len(value) != 0:
+ period_list = isinstance(value[0], PyCalendarPeriod)
+ if period_list:
+ self._init_attr_value_periodlist(value)
+ else:
+ self._init_attr_value_datetimelist(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():
+ other.mAttributes[attrname] = [i.duplicate() for i in attrs]
+ other.mValue = self.mValue.duplicate()
+
+ return other
+
+
+ def __hash__(self):
+ return hash((
+ self.mName,
+ tuple([tuple(self.mAttributes[attrname]) for attrname in sorted(self.mAttributes.keys())]),
+ self.mValue,
+ ))
+
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+
+ def __eq__(self, other):
+ 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):
- os = StringIO.StringIO()
- self.generate(os)
- return os.getvalue()
+ return "PyCalendarProperty: %s" % (self.getText(),)
+
def __str__(self):
- os = StringIO.StringIO()
- self.generate(os)
- return os.getvalue()
+ 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 = attributes
+ self.mAttributes = dict([(k.upper(), v) for k, v in attributes.iteritems()])
+
def hasAttribute(self, attr):
- return self.mAttributes.has_key(attr)
+ return attr.upper() in self.mAttributes
+
def getAttributeValue(self, attr):
- return self.mAttributes[attr][0].getFirstValue()
+ return self.mAttributes[attr.upper()][0].getFirstValue()
+
def addAttribute(self, attr):
- self.mAttributes.setdefault(attr.getName(), []).append(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):
- del self.mAttributes[attr]
+ 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):
+ return self.mValue
+ else:
+ return None
+
+
def getCalAddressValue(self):
if isinstance(self.mValue, PyCalendarCalAddressValue):
@@ -134,6 +311,7 @@
else:
return None
+
def getDateTimeValue(self):
if isinstance(self.mValue, PyCalendarDateTimeValue):
@@ -141,6 +319,7 @@
else:
return None
+
def getDurationValue(self):
if isinstance(self.mValue, PyCalendarDurationValue):
@@ -148,6 +327,7 @@
else:
return None
+
def getIntegerValue(self):
if isinstance(self.mValue, PyCalendarIntegerValue):
@@ -155,6 +335,7 @@
else:
return None
+
def getMultiValue(self):
if isinstance(self.mValue, PyCalendarMultiValue):
@@ -162,6 +343,7 @@
else:
return None
+
def getPeriodValue(self):
if isinstance(self.mValue, PyCalendarPeriodValue):
@@ -169,6 +351,7 @@
else:
return None
+
def getRecurrenceValue(self):
if isinstance(self.mValue, PyCalendarRecurrenceValue):
@@ -176,6 +359,7 @@
else:
return None
+
def getTextValue(self):
if isinstance(self.mValue, PyCalendarPlainTextValue):
@@ -183,6 +367,7 @@
else:
return None
+
def getURIValue(self):
if isinstance(self.mValue, PyCalendarURIValue):
@@ -190,6 +375,7 @@
else:
return None
+
def getUTCOffsetValue(self):
if isinstance(self.mValue, PyCalendarUTCOffsetValue):
@@ -197,54 +383,71 @@
else:
return None
+
def parse(self, data):
# Look for attribute or value delimiter
prop_name, txt = stringutils.strduptokenstr(data, ";:")
if not prop_name:
- return False
+ raise PyCalendarInvalidProperty("Invalid property", data)
# We have the name
self.mName = prop_name
-
+
# TODO: Try to use static string for the name
# Now loop getting data
- while txt:
- if txt[0] == ';':
- # Parse attribute
+ try:
+ while txt:
+ if txt[0] == ';':
+ # Parse attribute
- # Move past delimiter
- txt = txt[1:]
+ # Move past delimiter
+ txt = txt[1:]
- # Get quoted string or token
- attribute_name, txt = stringutils.strduptokenstr(txt, "=")
- if attribute_name is None:
- return False
- txt = txt[1:]
- attribute_value, txt = stringutils.strduptokenstr(txt, ":;,")
- if attribute_value is None:
- return False
+ # Get quoted string or token
+ attribute_name, txt = stringutils.strduptokenstr(txt, "=")
+ if attribute_name is None:
+ raise PyCalendarInvalidProperty("Invalid property", data)
+ txt = txt[1:]
+ 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)
- self.mAttributes.setdefault(attribute_name, []).append(attrvalue)
+ # 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] == ',':
+ # 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(decodeParameterValue(attribute_value2))
+ elif txt[0] == ':':
txt = txt[1:]
- attribute_value2, txt = stringutils.strduptokenstr(txt, ":;,")
- if attribute_value2 is None:
- return False
- attrvalue.addValue(attribute_value2)
- elif txt[0] == ':':
- txt = txt[1:]
- self.createValue(txt)
- txt = None
+ self.createValue(txt)
+ txt = None
+ else:
+ # We should never get here but if we do we need to terminate the loop
+ raise PyCalendarInvalidProperty("Invalid property", data)
+ except IndexError:
+ raise PyCalendarInvalidProperty("Invalid property", data)
+
# We must have a value of some kind
- return self.mValue is not None
+ if self.mValue is None:
+ raise PyCalendarInvalidProperty("Invalid property", data)
+ return True
+
+ def getText(self):
+ os = StringIO.StringIO()
+ self.generate(os)
+ return os.getvalue()
+
+
def generate(self, os):
# Write it out always with value
@@ -252,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)
+ 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):
@@ -268,8 +472,8 @@
sout.write(self.mName)
# Write all attributes
- for attrs in self.mAttributes.values():
- for attr in attrs:
+ for key in sorted(self.mAttributes.keys()):
+ for attr in self.mAttributes[key]:
sout.write(";")
attr.generate(sout)
@@ -301,169 +505,77 @@
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("\n ")
+ os.write("\r\n ")
written += offset - start
start = offset
-
- os.write("\n")
-
- def _init_PyCalendarProperty(self):
- self.mName = ""
- self.mAttributes = {}
- self.mValue = None
- def _copy_PyCalendarProperty(self, copyit):
- self.mName = copyit.mName
- self.mAttributes = {}
- for attr in copyit.mAttributes.values():
- self.mAttributes.setdefault(attr.getName(), []).append(PyCalendarAttribute(copyit=attr))
- self.mValue = copyit.mValue.copy()
+ os.write("\r\n")
- @staticmethod
- def _init_map():
- # Only if empty
- if PyCalendarProperty.sDefaultValueTypeMap is None:
- PyCalendarProperty.sDefaultValueTypeMap = {}
- # 2445 ?4.8.1
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_ATTACH] = PyCalendarValue.VALUETYPE_BINARY
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_CATEGORIES] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_CLASS] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_COMMENT] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_DESCRIPTION] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_GEO] = PyCalendarValue.VALUETYPE_GEO
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_LOCATION] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_PERCENT_COMPLETE] = PyCalendarValue.VALUETYPE_INTEGER
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_PRIORITY] = PyCalendarValue.VALUETYPE_INTEGER
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_RESOURCES] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_STATUS] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_SUMMARY] = PyCalendarValue.VALUETYPE_TEXT
+ def writeXML(self, node, namespace):
- # 2445 ?4.8.2
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_COMPLETED] = PyCalendarValue.VALUETYPE_DATETIME
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_DTEND] = PyCalendarValue.VALUETYPE_DATETIME
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_DUE] = PyCalendarValue.VALUETYPE_DATETIME
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_DTSTART] = PyCalendarValue.VALUETYPE_DATETIME
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_DURATION] = PyCalendarValue.VALUETYPE_DURATION
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_FREEBUSY] = PyCalendarValue.VALUETYPE_PERIOD
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_TRANSP] = PyCalendarValue.VALUETYPE_TEXT
+ # Write it out always with value
+ self.generateValueXML(node, namespace, False)
- # 2445 ?4.8.3
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_TZID] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_TZNAME] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_TZOFFSETFROM] = PyCalendarValue.VALUETYPE_UTC_OFFSET
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_TZOFFSETTO] = PyCalendarValue.VALUETYPE_UTC_OFFSET
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_TZURL] = PyCalendarValue.VALUETYPE_URI
- # 2445 ?4.8.4
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_ATTENDEE] = PyCalendarValue.VALUETYPE_CALADDRESS
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_CONTACT] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_ORGANIZER] = PyCalendarValue.VALUETYPE_CALADDRESS
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_RECURRENCE_ID] = PyCalendarValue.VALUETYPE_DATETIME
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_RELATED_TO] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_URL] = PyCalendarValue.VALUETYPE_URI
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_UID] = PyCalendarValue.VALUETYPE_TEXT
+ def generateFilteredXML(self, node, namespace, filter):
- # 2445 ?4.8.5
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_EXDATE] = PyCalendarValue.VALUETYPE_DATETIME
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_EXRULE] = PyCalendarValue.VALUETYPE_RECUR
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_RDATE] = PyCalendarValue.VALUETYPE_DATETIME
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_RRULE] = PyCalendarValue.VALUETYPE_RECUR
+ # 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)
- # 2445 ?4.8.6
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_ACTION] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_REPEAT] = PyCalendarValue.VALUETYPE_INTEGER
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_TRIGGER] = PyCalendarValue.VALUETYPE_DURATION
- # 2445 ?4.8.7
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_CREATED] = PyCalendarValue.VALUETYPE_DATETIME
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_DTSTAMP] = PyCalendarValue.VALUETYPE_DATETIME
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_LAST_MODIFIED] = PyCalendarValue.VALUETYPE_DATETIME
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_SEQUENCE] = PyCalendarValue.VALUETYPE_INTEGER
+ # Write out the actual property, possibly skipping the value
+ def generateValueXML(self, node, namespace, novalue):
- # Apple Extensions
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_XWRCALNAME] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_XWRCALDESC] = PyCalendarValue.VALUETYPE_TEXT
+ prop = XML.SubElement(node, xmldefs.makeTag(namespace, self.getName()))
- # Mulberry extensions
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_X_PRIVATE_RURL] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_X_PRIVATE_ETAG] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_ACTION_X_SPEAKTEXT] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_ALARM_X_LASTTRIGGER] = PyCalendarValue.VALUETYPE_DATETIME
- PyCalendarProperty.sDefaultValueTypeMap[definitions.cICalProperty_ALARM_X_ALARMSTATUS] = PyCalendarValue.VALUETYPE_TEXT
+ # Write all attributes
+ if len(self.mAttributes):
+ params = XML.SubElement(prop, xmldefs.makeTag(namespace, xmldefs.parameters))
+ for key in sorted(self.mAttributes.keys()):
+ for attr in self.mAttributes[key]:
+ if attr.getName() != "VALUE":
+ attr.writeXML(params, namespace)
- # Only if empty
- if PyCalendarProperty.sValueTypeMap is None:
- PyCalendarProperty.sValueTypeMap = {}
+ # Write value
+ if self.mValue and not novalue:
+ self.mValue.writeXML(prop, namespace)
- # 2445 ?4.3
- PyCalendarProperty.sValueTypeMap[definitions.cICalValue_BINARY] = PyCalendarValue.VALUETYPE_BINARY
- PyCalendarProperty.sValueTypeMap[definitions.cICalValue_BOOLEAN] = PyCalendarValue.VALUETYPE_BOOLEAN
- PyCalendarProperty.sValueTypeMap[definitions.cICalValue_CAL_ADDRESS] = PyCalendarValue.VALUETYPE_CALADDRESS
- PyCalendarProperty.sValueTypeMap[definitions.cICalValue_DATE] = PyCalendarValue.VALUETYPE_DATE
- PyCalendarProperty.sValueTypeMap[definitions.cICalValue_DATE_TIME] = PyCalendarValue.VALUETYPE_DATETIME
- PyCalendarProperty.sValueTypeMap[definitions.cICalValue_DURATION] = PyCalendarValue.VALUETYPE_DURATION
- PyCalendarProperty.sValueTypeMap[definitions.cICalValue_FLOAT] = PyCalendarValue.VALUETYPE_FLOAT
- PyCalendarProperty.sValueTypeMap[definitions.cICalValue_INTEGER] = PyCalendarValue.VALUETYPE_INTEGER
- PyCalendarProperty.sValueTypeMap[definitions.cICalValue_PERIOD] = PyCalendarValue.VALUETYPE_PERIOD
- PyCalendarProperty.sValueTypeMap[definitions.cICalValue_RECUR] = PyCalendarValue.VALUETYPE_RECUR
- PyCalendarProperty.sValueTypeMap[definitions.cICalValue_TEXT] = PyCalendarValue.VALUETYPE_TEXT
- PyCalendarProperty.sValueTypeMap[definitions.cICalValue_TIME] = PyCalendarValue.VALUETYPE_TIME
- PyCalendarProperty.sValueTypeMap[definitions.cICalValue_URI] = PyCalendarValue.VALUETYPE_URI
- PyCalendarProperty.sValueTypeMap[definitions.cICalValue_UTC_OFFSET] = PyCalendarValue.VALUETYPE_UTC_OFFSET
- if PyCalendarProperty.sTypeValueMap is None:
- PyCalendarProperty.sTypeValueMap = {}
+ def _init_PyCalendarProperty(self):
+ self.mName = ""
+ self.mAttributes = {}
+ self.mValue = None
- # 2445 ?4.3
- PyCalendarProperty.sTypeValueMap[PyCalendarValue.VALUETYPE_BINARY] = definitions.cICalValue_BINARY
- PyCalendarProperty.sTypeValueMap[PyCalendarValue.VALUETYPE_BOOLEAN] = definitions.cICalValue_BOOLEAN
- PyCalendarProperty.sTypeValueMap[PyCalendarValue.VALUETYPE_CALADDRESS] = definitions.cICalValue_CAL_ADDRESS
- PyCalendarProperty.sTypeValueMap[PyCalendarValue.VALUETYPE_DATE] = definitions.cICalValue_DATE
- PyCalendarProperty.sTypeValueMap[PyCalendarValue.VALUETYPE_DATETIME] = definitions.cICalValue_DATE_TIME
- PyCalendarProperty.sTypeValueMap[PyCalendarValue.VALUETYPE_DURATION] = definitions.cICalValue_DURATION
- PyCalendarProperty.sTypeValueMap[PyCalendarValue.VALUETYPE_FLOAT] = definitions.cICalValue_FLOAT
- PyCalendarProperty.sTypeValueMap[PyCalendarValue.VALUETYPE_GEO] = definitions.cICalValue_FLOAT
- PyCalendarProperty.sTypeValueMap[PyCalendarValue.VALUETYPE_INTEGER] = definitions.cICalValue_INTEGER
- PyCalendarProperty.sTypeValueMap[PyCalendarValue.VALUETYPE_PERIOD] = definitions.cICalValue_PERIOD
- PyCalendarProperty.sTypeValueMap[PyCalendarValue.VALUETYPE_RECUR] = definitions.cICalValue_RECUR
- PyCalendarProperty.sTypeValueMap[PyCalendarValue.VALUETYPE_TEXT] = definitions.cICalValue_TEXT
- PyCalendarProperty.sTypeValueMap[PyCalendarValue.VALUETYPE_TIME] = definitions.cICalValue_TIME
- PyCalendarProperty.sTypeValueMap[PyCalendarValue.VALUETYPE_URI] = definitions.cICalValue_URI
- PyCalendarProperty.sTypeValueMap[PyCalendarValue.VALUETYPE_UTC_OFFSET] = definitions.cICalValue_UTC_OFFSET
- if PyCalendarProperty.sMultiValues is None:
- PyCalendarProperty.sMultiValues = set()
-
- PyCalendarProperty.sMultiValues.add(definitions.cICalProperty_CATEGORIES)
- PyCalendarProperty.sMultiValues.add(definitions.cICalProperty_RESOURCES)
- PyCalendarProperty.sMultiValues.add(definitions.cICalProperty_FREEBUSY)
- PyCalendarProperty.sMultiValues.add(definitions.cICalProperty_EXDATE)
- PyCalendarProperty.sMultiValues.add(definitions.cICalProperty_RDATE)
-
def createValue(self, data):
# Tidy first
self.mValue = None
# Get value type from property name
- type = PyCalendarProperty.sDefaultValueTypeMap.get(self.mName, 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
- if self.mName in PyCalendarProperty.sMultiValues:
+ if self.mName.upper() in PyCalendarProperty.sMultiValues:
self.mValue = PyCalendarMultiValue(type)
else:
# Create the type
self.mValue = PyCalendarValue.createFromType(type)
# Now parse the data
- self.mValue.parse(data)
+ try:
+ self.mValue.parse(data)
+ except ValueError:
+ raise PyCalendarInvalidProperty("Invalid property value", data)
# Special post-create for some types
if type in (PyCalendarValue.VALUETYPE_TIME, PyCalendarValue.VALUETYPE_DATETIME):
@@ -471,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):
@@ -479,23 +591,60 @@
if isinstance(item, PyCalendarDateTimeValue):
item.getValue().setTimezoneID(tzid)
+
+ def setValue(self, value):
+ # Tidy first
+ self.mValue = None
+
+ # Get value type from property name
+ type = PyCalendarProperty.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarValue.VALUETYPE_TEXT)
+
+ # Check whether custom value is set
+ if definitions.cICalAttribute_VALUE in self.mAttributes:
+ type = PyCalendarProperty.sValueTypeMap.get(self.getAttributeValue(definitions.cICalAttribute_VALUE), type)
+
+ # Check for multivalued
+ if self.mName.upper() in PyCalendarProperty.sMultiValues:
+ self.mValue = PyCalendarMultiValue(type)
+ else:
+ # Create the type
+ self.mValue = PyCalendarValue.createFromType(type)
+
+ self.mValue.setValue(value)
+
+ # Special post-create for some types
+ if type in (PyCalendarValue.VALUETYPE_TIME, PyCalendarValue.VALUETYPE_DATETIME):
+ # Look for TZID attribute
+ 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):
+ for item in self.mValue.getValues():
+ 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
- found = self.sDefaultValueTypeMap.get(self.mName, None)
- if found is not None:
- default_type = found
- if default_type != self.mValue.getType():
- found2 = self.sTypeValueMap.get(self.mValue.getType(), None)
- if found2 is not None:
- self.mAttributes.setdefault(definitions.cICalAttribute_VALUE, []).append(PyCalendarAttribute(name=definitions.cICalAttribute_VALUE, value=found2))
+ # 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())
+ 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):
# Value
@@ -508,12 +657,21 @@
def _init_attr_value_text(self, txt, value_type):
# Value
self.mValue = PyCalendarValue.createFromType(value_type)
- if isinstance(self.mValue, PyCalendarPlainTextValue):
+ 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)
+
+ # Attributes
+ self.setupValueAttribute()
+
+
def _init_attr_value_datetime(self, dt):
# Value
self.mValue = PyCalendarDateTimeValue(value=dt)
@@ -523,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()
@@ -545,12 +704,23 @@
# Look for timezone
if ((len(dtl) > 0)
and not dtl[0].isDateOnly()
- and not dtl[0].floating()):
- if self.mAttributes.has_key(definitions.cICalAttribute_TZID):
+ and dtl[0].local()):
+ 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)
+ for period in periodlist:
+ self.mValue.addValue(PyCalendarPeriodValue(period))
+
+ # Attributes
+ self.setupValueAttribute()
+
+
def _init_attr_value_duration(self, du):
# Value
self.mValue = PyCalendarDurationValue(value=du)
@@ -574,6 +744,7 @@
# Attributes
self.setupValueAttribute()
+
def _init_attr_value_utcoffset(self, utcoffset):
# Value
self.mValue = PyCalendarUTCOffsetValue()
@@ -581,19 +752,3 @@
# Attributes
self.setupValueAttribute()
-
-PyCalendarProperty.loadStatics()
-
-if __name__ == '__main__':
- prop = PyCalendarProperty()
- prop.parse("DTSTART;TZID=\"US/Eastern\":20060226T120000")
- io = StringIO.StringIO()
- prop.generate(io)
- print io.getvalue()
-
- prop = PyCalendarProperty(definitions.cICalProperty_DTSTAMP,
- PyCalendarDateTime.getNowUTC())
- prop.generate(io)
- print io.getvalue()
-
-
\ No newline at end of file
Modified: PyCalendar/trunk/src/pycalendar/recurrence.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/recurrence.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/recurrence.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,13 +14,14 @@
# limitations under the License.
##
+from pycalendar import definitions
+from pycalendar import xmldefs
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.period import PyCalendarPeriod
+from pycalendar.valueutils import ValueMixin
import cStringIO as StringIO
+import xml.etree.cElementTree as XML
-from datetime import PyCalendarDateTime
-from period import PyCalendarPeriod
-import definitions
-import stringutils
-
def WeekDayNumCompare_compare(w1, w2):
if w1[0] < w2[0]:
@@ -33,58 +34,116 @@
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(object):
- cFreqMap = [
- definitions.cICalValue_RECUR_SECONDLY,
- definitions.cICalValue_RECUR_MINUTELY,
- definitions.cICalValue_RECUR_HOURLY,
- definitions.cICalValue_RECUR_DAILY,
- definitions.cICalValue_RECUR_WEEKLY,
- definitions.cICalValue_RECUR_MONTHLY,
- definitions.cICalValue_RECUR_YEARLY,
- 0]
-
- cRecurMap = [
- definitions.cICalValue_RECUR_UNTIL,
- definitions.cICalValue_RECUR_COUNT,
- definitions.cICalValue_RECUR_INTERVAL,
- definitions.cICalValue_RECUR_BYSECOND,
- definitions.cICalValue_RECUR_BYMINUTE,
- definitions.cICalValue_RECUR_BYHOUR,
- definitions.cICalValue_RECUR_BYDAY,
- definitions.cICalValue_RECUR_BYMONTHDAY,
- definitions.cICalValue_RECUR_BYYEARDAY,
- definitions.cICalValue_RECUR_BYWEEKNO,
- definitions.cICalValue_RECUR_BYMONTH,
- definitions.cICalValue_RECUR_BYSETPOS,
- definitions.cICalValue_RECUR_WKST,
- 0]
-
- cWeekdayMap = [
- definitions.cICalValue_RECUR_WEEKDAY_SU,
- definitions.cICalValue_RECUR_WEEKDAY_MO,
- definitions.cICalValue_RECUR_WEEKDAY_TU,
- definitions.cICalValue_RECUR_WEEKDAY_WE,
- definitions.cICalValue_RECUR_WEEKDAY_TH,
- definitions.cICalValue_RECUR_WEEKDAY_FR,
- definitions.cICalValue_RECUR_WEEKDAY_SA,
- 0]
-
+
+class PyCalendarRecurrence(ValueMixin):
+
+ cFreqMap = {
+ definitions.cICalValue_RECUR_SECONDLY : definitions.eRecurrence_SECONDLY,
+ definitions.cICalValue_RECUR_MINUTELY : definitions.eRecurrence_MINUTELY,
+ definitions.cICalValue_RECUR_HOURLY : definitions.eRecurrence_HOURLY,
+ definitions.cICalValue_RECUR_DAILY : definitions.eRecurrence_DAILY,
+ definitions.cICalValue_RECUR_WEEKLY : definitions.eRecurrence_WEEKLY,
+ definitions.cICalValue_RECUR_MONTHLY : definitions.eRecurrence_MONTHLY,
+ definitions.cICalValue_RECUR_YEARLY : definitions.eRecurrence_YEARLY,
+ }
+
+ cFreqToXMLMap = {
+ definitions.eRecurrence_SECONDLY: xmldefs.recur_freq_secondly,
+ definitions.eRecurrence_MINUTELY: xmldefs.recur_freq_minutely,
+ definitions.eRecurrence_HOURLY: xmldefs.recur_freq_hourly,
+ definitions.eRecurrence_DAILY: xmldefs.recur_freq_daily,
+ definitions.eRecurrence_WEEKLY: xmldefs.recur_freq_weekly,
+ 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,
+ definitions.cICalValue_RECUR_COUNT : definitions.eRecurrence_COUNT,
+ definitions.cICalValue_RECUR_INTERVAL : definitions.eRecurrence_INTERVAL,
+ definitions.cICalValue_RECUR_BYSECOND : definitions.eRecurrence_BYSECOND,
+ definitions.cICalValue_RECUR_BYMINUTE : definitions.eRecurrence_BYMINUTE,
+ definitions.cICalValue_RECUR_BYHOUR : definitions.eRecurrence_BYHOUR,
+ definitions.cICalValue_RECUR_BYDAY : definitions.eRecurrence_BYDAY,
+ definitions.cICalValue_RECUR_BYMONTHDAY : definitions.eRecurrence_BYMONTHDAY,
+ definitions.cICalValue_RECUR_BYYEARDAY : definitions.eRecurrence_BYYEARDAY,
+ definitions.cICalValue_RECUR_BYWEEKNO : definitions.eRecurrence_BYWEEKNO,
+ definitions.cICalValue_RECUR_BYMONTH : definitions.eRecurrence_BYMONTH,
+ 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,
+ definitions.cICalValue_RECUR_WEEKDAY_TU : definitions.eRecurrence_WEEKDAY_TU,
+ definitions.cICalValue_RECUR_WEEKDAY_WE : definitions.eRecurrence_WEEKDAY_WE,
+ definitions.cICalValue_RECUR_WEEKDAY_TH : definitions.eRecurrence_WEEKDAY_TH,
+ 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, arg = None):
-
- if isinstance(arg, PyCalendarRecurrence):
- self.copy_PyCalendarRecurrence(arg)
- else:
- self.init_PyCalendarRecurrence()
+ def __init__(self):
+ self.init_PyCalendarRecurrence()
+
+ def duplicate(self):
+ other = PyCalendarRecurrence()
+
+ other.mFreq = self.mFreq
+
+ other.mUseCount = self.mUseCount
+ other.mCount = self.mCount
+ other.mUseUntil = self.mUseUntil
+ if other.mUseUntil:
+ other.mUntil = self.mUntil.duplicate()
+
+ other.mInterval = self.mInterval
+ if self.mBySeconds is not None:
+ other.mBySeconds = self.mBySeconds[:]
+ if self.mByMinutes is not None:
+ other.mByMinutes = self.mByMinutes[:]
+ if self.mByHours is not None:
+ other.mByHours = self.mByHours[:]
+ if self.mByDay is not None:
+ other.mByDay = self.mByDay[:]
+ if self.mByMonthDay is not None:
+ other.mByMonthDay = self.mByMonthDay[:]
+ if self.mByYearDay is not None:
+ other.mByYearDay = self.mByYearDay[:]
+ if self.mByWeekNo is not None:
+ other.mByWeekNo = self.mByWeekNo[:]
+ if self.mByMonth is not None:
+ other.mByMonth = self.mByMonth[:]
+ if self.mBySetPos is not None:
+ other.mBySetPos = self.mBySetPos[:]
+ other.mWeekstart = self.mWeekstart
+
+ other.mCached = self.mCached
+ if self.mCacheStart:
+ other.mCacheStart = self.mCacheStart.duplicate()
+ if self.mCacheUpto:
+ other.mCacheUpto = self.mCacheUpto.duplicate()
+ other.mFullyCached = self.mFullyCached
+ if self.mRecurrences:
+ other.mRecurrences = self.mRecurrences[:]
+
+ return other
+
+
def init_PyCalendarRecurrence(self):
self.mFreq = definitions.eRecurrence_YEARLY
@@ -92,18 +151,18 @@
self.mCount = 0
self.mUseUntil = False
- self.mUntil = 0
+ self.mUntil = None
self.mInterval = 1
- self.mBySeconds = 0
- self.mByMinutes = 0
- self.mByHours = 0
- self.mByDay = 0
- self.mByMonthDay = 0
- self.mByYearDay = 0
- self.mByWeekNo = 0
- self.mByMonth = 0
- self.mBySetPos = 0
+ self.mBySeconds = None
+ self.mByMinutes = None
+ self.mByHours = None
+ self.mByDay = None
+ self.mByMonthDay = None
+ self.mByYearDay = None
+ self.mByWeekNo = None
+ self.mByMonth = None
+ self.mBySetPos = None
self.mWeekstart = definitions.eRecurrence_WEEKDAY_MO
self.mCached = False
@@ -112,55 +171,41 @@
self.mFullyCached = False
self.mRecurrences = None
- def copy_PyCalendarRecurrence(self, copy):
- self.init_PyCalendarRecurrence()
- self.mFreq = copy.mFreq
+ def __hash__(self):
+ return hash((
+ self.mFreq,
+ self.mUseCount,
+ self.mCount,
+ self.mUseUntil,
+ self.mUntil,
+ self.mInterval,
+ tuple(self.mBySeconds) if self.mBySeconds else None,
+ tuple(self.mByMinutes) if self.mByMinutes else None,
+ tuple(self.mByHours) if self.mByHours else None,
+ tuple(self.mByDay) if self.mByDay else None,
+ tuple(self.mByMonthDay) if self.mByMonthDay else None,
+ tuple(self.mByYearDay) if self.mByYearDay else None,
+ tuple(self.mByWeekNo) if self.mByWeekNo else None,
+ tuple(self.mByMonth) if self.mByMonth else None,
+ tuple(self.mBySetPos) if self.mBySetPos else None,
+ self.mWeekstart,
+ ))
- self.mUseCount = copy.mUseCount
- self.mCount = copy.mCount
- self.mUseUntil = copy.mUseUntil
- if self.mUseUntil:
- self.mUntil = PyCalendarDateTime(copyit=copy.mUntil)
- self.mInterval = copy.mInterval
- if copy.mBySeconds != 0:
- self.mBySeconds = copy.mBySeconds[:]
- if copy.mByMinutes != 0:
- self.mByMinutes = copy.mByMinutes[:]
- if copy.mByHours != 0:
- self.mByHours = copy.mByHours[:]
- if copy.mByDay != 0:
- self.mByDay = copy.mByDay[:]
- if copy.mByMonthDay != 0:
- self.mByMonthDay = copy.mByMonthDay[:]
- if copy.mByYearDay != 0:
- self.mByYearDay = copy.mByYearDay[:]
- if copy.mByWeekNo != 0:
- self.mByWeekNo = copy.mByWeekNo[:]
- if copy.mByMonth != 0:
- self.mByMonth = copy.mByMonth[:]
- if copy.mBySetPos != 0:
- self.mBySetPos = copy.mBySetPos[:]
- self.mWeekstart = copy.mWeekstart
+ def __ne__(self, other):
+ return not self.__eq__(other)
- self.mCached = copy.mCached
- if copy.mCacheStart:
- self.mCacheStart = PyCalendarDateTime(copyit=copy.mCacheStart)
- else:
- self.mCacheStart = None
- if copy.mCacheUpto:
- self.mCacheUpto = PyCalendarDateTime(copyit=copy.mCacheUpto)
- else:
- self.mCacheUpto = None
- self.mFullyCached = copy.mFullyCached
- if copy.mRecurrences:
- self.mRecurrences = copy.mRecurrences[:]
- else:
- self.mRecurrences = None
+ def __eq__(self, other):
+ if not isinstance(other, PyCalendarRecurrence):
+ return False
+ return self.equals(other)
+
+
def equals(self, comp):
- return (self.mFreq == comp.mFreq) and (self.mCount == comp.mCount) \
+ return (self.mFreq == comp.mFreq) \
+ and (self.mUseCount == comp.mUseCount) and (self.mCount == comp.mCount) \
and (self.mUseUntil == comp.mUseUntil) and (self.mUntil == comp.mUntil) \
and (self.mInterval == comp.mInterval) \
and self.equalsNum(self.mBySeconds, comp.mBySeconds) \
@@ -174,8 +219,13 @@
and self.equalsNum(self.mBySetPos, comp.mBySetPos) \
and (self.mWeekstart == comp.mWeekstart)
+
def equalsNum(self, items1, items2):
# Check sizes first
+ if items1 is None:
+ items1 = []
+ if items2 is None:
+ items2 = []
if len(items1) != len(items2):
return False
elif len(items1) == 0:
@@ -192,8 +242,13 @@
return False
return True
+
def equalsDayNum(self, items1, items2):
# Check sizes first
+ if items1 is None:
+ items1 = []
+ if items2 is None:
+ items2 = []
if len(items1) != len(items2):
return False
elif len(items1) == 0:
@@ -210,57 +265,83 @@
return False
return True
+
def getFreq(self):
return self.mFreq
+
def setFreq(self, freq):
self.mFreq = freq
+
def getUseUntil(self):
return self.mUseUntil
+
def setUseUntil(self, use_until):
self.mUseUntil = use_until
+
def getUntil(self):
return self.mUntil
+
def setUntil(self, until):
self.mUntil = until
+
def getUseCount(self):
return self.mUseCount
+
def setUseCount(self, use_count):
self.mUseCount = use_count
+
def getCount(self):
return self.mCount
+
def setCount(self, count):
self.mCount = count
+
def getInterval(self):
return self.mInterval
+
def setInterval(self, interval):
self.mInterval = interval
+
def getByMonth(self):
return self.mByMonth
+
def setByMonth(self, by):
self.mByMonth = by[:]
+
def getByMonthDay(self):
return self.mByMonthDay
+
def setByMonthDay(self, by):
self.mByMonthDay = by[:]
+
+ def getByYearDay(self):
+ return self.mByYearDay
+
+
+ def setByYearDay(self, by):
+ self.mByYearDay = by[:]
+
+
def getByDay(self):
return self.mByDay
+
def setByDay(self, by):
self.mByDay = by[:]
@@ -268,9 +349,11 @@
def getBySetPos(self):
return self.mBySetPos
+
def setBySetPos(self, by):
self.mBySetPos = by
+
def parse(self, data):
self.init_PyCalendarRecurrence()
@@ -278,133 +361,146 @@
tokens = data.split(";")
tokens.reverse()
- # Look for FREQ= with delimiter
-
if len(tokens) == 0:
- return
- token = tokens.pop()
+ raise ValueError("PyCalendarRecurrence: Invalid recurrence rule value")
- # Make sure it is the token we expect
- if not token.startswith(definitions.cICalValue_RECUR_FREQ):
- return
- q = token[definitions.cICalValue_RECUR_FREQ_LEN:]
-
- # Get the FREQ value
- index = stringutils.strindexfind(q, PyCalendarRecurrence.cFreqMap, PyCalendarRecurrence.cUnknownIndex)
- if index == PyCalendarRecurrence.cUnknownIndex:
- return
- self.mFreq = index
-
-
while len(tokens) != 0:
# Get next token
token = tokens.pop()
+ try:
+ tname, tvalue = token.split("=")
+ except ValueError:
+ raise ValueError("PyCalendarRecurrence: Invalid token '%s'" % (token,))
# Determine token type
- index = stringutils.strnindexfind(token, PyCalendarRecurrence.cRecurMap, PyCalendarRecurrence.cUnknownIndex)
+ index = PyCalendarRecurrence.cRecurMap.get(tname, PyCalendarRecurrence.cUnknownIndex)
if index == PyCalendarRecurrence.cUnknownIndex:
- return
+ raise ValueError("PyCalendarRecurrence: Invalid token '%s'" % (tname,))
# Parse remainder based on index
- q = token[token.find('=') + 1:]
+ if index == definitions.eRecurrence_FREQ:
+ # Get the FREQ value
+ index = PyCalendarRecurrence.cFreqMap.get(tvalue, PyCalendarRecurrence.cUnknownIndex)
+ if index == PyCalendarRecurrence.cUnknownIndex:
+ raise ValueError("PyCalendarRecurrence: Invalid FREQ value")
+ self.mFreq = index
- if index == 0: # UNTIL
+ elif index == definitions.eRecurrence_UNTIL:
if self.mUseCount:
- return
+ raise ValueError("PyCalendarRecurrence: Can't have both UNTIL and COUNT")
self.mUseUntil = True
- if self.mUntil == 0:
+ if self.mUntil is None:
self.mUntil = PyCalendarDateTime()
- self.mUntil.parse(q)
+ try:
+ self.mUntil.parse(tvalue)
+ except ValueError:
+ raise ValueError("PyCalendarRecurrence: Invalid UNTIL value")
- elif index == 1: # COUNT
+ elif index == definitions.eRecurrence_COUNT:
if self.mUseUntil:
- return
+ raise ValueError("PyCalendarRecurrence: Can't have both UNTIL and COUNT")
self.mUseCount = True
- self.mCount = int(q)
+ try:
+ self.mCount = int(tvalue)
+ except ValueError:
+ raise ValueError("PyCalendarRecurrence: Invalid COUNT value")
# Must not be less than one
if self.mCount < 1:
- self.mCount = 1
+ raise ValueError("PyCalendarRecurrence: Invalid COUNT value")
- elif index == 2: # INTERVAL
- self.mInterval = int(q)
+ elif index == definitions.eRecurrence_INTERVAL:
+ try:
+ self.mInterval = int(tvalue)
+ except ValueError:
+ raise ValueError("PyCalendarRecurrence: Invalid INTERVAL value")
# Must NOT be less than one
if self.mInterval < 1:
- self.mInterval = 1
-
- elif index == 3: # BYSECOND
- if self.mBySeconds != 0:
- return
+ raise ValueError("PyCalendarRecurrence: Invalid INTERVAL value")
+
+ elif index == definitions.eRecurrence_BYSECOND:
+ if self.mBySeconds is not None:
+ raise ValueError("PyCalendarRecurrence: Only one BYSECOND allowed")
self.mBySeconds = []
- self.parseList(q, self.mBySeconds)
+ self.parseList(tvalue, self.mBySeconds, 0, 60, errmsg="PyCalendarRecurrence: Invalid BYSECOND value")
- elif index == 4: # BYMINUTE
- if self.mByMinutes != 0:
- return
+ elif index == definitions.eRecurrence_BYMINUTE:
+ if self.mByMinutes is not None:
+ raise ValueError("PyCalendarRecurrence: Only one BYMINUTE allowed")
self.mByMinutes = []
- self.parseList(q, self.mByMinutes)
+ self.parseList(tvalue, self.mByMinutes, 0, 59, errmsg="PyCalendarRecurrence: Invalid BYMINUTE value")
- elif index == 5: # BYHOUR
- if self.mByHours != 0:
- return
+ elif index == definitions.eRecurrence_BYHOUR:
+ if self.mByHours is not None:
+ raise ValueError("PyCalendarRecurrence: Only one BYHOUR allowed")
self.mByHours = []
- self.parseList(q, self.mByHours)
+ self.parseList(tvalue, self.mByHours, 0, 23, errmsg="PyCalendarRecurrence: Invalid BYHOUR value")
- elif index == 6: # BYDAY
- if self.mByDay != 0:
- return
+ elif index == definitions.eRecurrence_BYDAY:
+ if self.mByDay is not None:
+ raise ValueError("PyCalendarRecurrence: Only one BYDAY allowed")
self.mByDay = []
- self.parseListDW(q, self.mByDay)
+ self.parseListDW(tvalue, self.mByDay, errmsg="PyCalendarRecurrence: Invalid BYDAY value")
- elif index == 7: # BYMONTHDAY
- if self.mByMonthDay != 0:
- return
+ elif index == definitions.eRecurrence_BYMONTHDAY:
+ if self.mByMonthDay is not None:
+ raise ValueError("PyCalendarRecurrence: Only one BYMONTHDAY allowed")
self.mByMonthDay = []
- self.parseList(q, self.mByMonthDay)
+ self.parseList(tvalue, self.mByMonthDay, 1, 31, True, errmsg="PyCalendarRecurrence: Invalid BYMONTHDAY value")
- elif index == 8: # BYYEARDAY
- if self.mByYearDay != 0:
- return
+ elif index == definitions.eRecurrence_BYYEARDAY:
+ if self.mByYearDay is not None:
+ raise ValueError("PyCalendarRecurrence: Only one BYYEARDAY allowed")
self.mByYearDay = []
- self.parseList(q, self.mByYearDay)
+ self.parseList(tvalue, self.mByYearDay, 1, 366, True, errmsg="PyCalendarRecurrence: Invalid BYYEARDAY value")
- elif index == 9: # BYWEEKNO
- if self.mByWeekNo != 0:
- return
+ elif index == definitions.eRecurrence_BYWEEKNO:
+ if self.mByWeekNo is not None:
+ raise ValueError("PyCalendarRecurrence: Only one BYWEEKNO allowed")
self.mByWeekNo = []
- self.parseList(q, self.mByWeekNo)
+ self.parseList(tvalue, self.mByWeekNo, 1, 53, True, errmsg="PyCalendarRecurrence: Invalid BYWEEKNO value")
- elif index == 10: # BYMONTH
- if self.mByMonth != 0:
- return
+ elif index == definitions.eRecurrence_BYMONTH:
+ if self.mByMonth is not None:
+ raise ValueError("PyCalendarRecurrence: Only one BYMONTH allowed")
self.mByMonth = []
- self.parseList(q, self.mByMonth)
+ self.parseList(tvalue, self.mByMonth, 1, 12, errmsg="PyCalendarRecurrence: Invalid BYMONTH value")
- elif index == 11: # BYSETPOS
- if self.mBySetPos != 0:
- return
+ elif index == definitions.eRecurrence_BYSETPOS:
+ if self.mBySetPos is not None:
+ raise ValueError("PyCalendarRecurrence: Only one BYSETPOS allowed")
self.mBySetPos = []
- self.parseList(q, self.mBySetPos)
+ self.parseList(tvalue, self.mBySetPos, allowNegative=True, errmsg="PyCalendarRecurrence: Invalid BYSETPOS value")
- elif index == 12: # WKST
- index = stringutils.strindexfind(q, PyCalendarRecurrence.cWeekdayMap, PyCalendarRecurrence.cUnknownIndex)
+ elif index == definitions.eRecurrence_WKST:
+ index = PyCalendarRecurrence.cWeekdayMap.get(tvalue, PyCalendarRecurrence.cUnknownIndex)
if (index == PyCalendarRecurrence.cUnknownIndex):
- return
- self.mWeekstart = index
+ raise ValueError("PyCalendarRecurrence: Invalid WKST value")
+ self.mWeekstart = index
- def parseList(self, txt, list):
-
+
+ def parseList(self, txt, list, min=None, max=None, allowNegative=False, errmsg=""):
+
if "," in txt:
tokens = txt.split(",")
else:
tokens = (txt,)
for token in tokens:
- list.append(int(token))
+ value = int(token)
+ if not allowNegative and value < 0:
+ raise ValueError(errmsg)
+ avalue = abs(value)
+ if min is not None and avalue < min:
+ raise ValueError(errmsg)
+ if max is not None and avalue > max:
+ raise ValueError(errmsg)
+ list.append(value)
- def parseListDW(self, txt, list):
+ def parseListDW(self, txt, list, errmsg=""):
+
if "," in txt:
tokens = txt.split(",")
else:
@@ -417,67 +513,76 @@
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)
+ if anum > 53:
+ raise ValueError(errmsg)
+
# Get day
- index = stringutils.strnindexfind(token, PyCalendarRecurrence.cWeekdayMap, PyCalendarRecurrence.cUnknownIndex)
+ index = PyCalendarRecurrence.cWeekdayMap.get(token, PyCalendarRecurrence.cUnknownIndex)
if (index == PyCalendarRecurrence.cUnknownIndex):
- return
+ raise ValueError(errmsg)
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:
os.write(";")
os.write(definitions.cICalValue_RECUR_COUNT)
+ os.write("=")
os.write(str(self.mCount))
- elif (self.mUseUntil):
+ elif self.mUseUntil:
os.write(";")
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)
self.generateList(os, definitions.cICalValue_RECUR_BYHOUR, self.mByHours)
- if (self.mByDay != 0) and (len(self.mByDay) != 0):
+ if (self.mByDay is not None) and (len(self.mByDay) != 0):
os.write(";")
os.write(definitions.cICalValue_RECUR_BYDAY)
+ os.write("=")
comma = False
for iter in self.mByDay:
if comma:
@@ -493,19 +598,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)
@@ -518,8 +623,9 @@
if self.mWeekstart != definitions.eRecurrence_WEEKDAY_MO:
os.write(";")
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:
@@ -543,28 +649,81 @@
except:
pass
- def generateList(self, os, title, list):
- if (list != 0) and (len(list) != 0):
+ def generateList(self, os, title, items):
+
+ if (items is not None) and (len(items) != 0):
os.write(";")
os.write(title)
+ os.write("=")
comma = False
- for e in list:
+ for e in items:
if comma:
os.write(",")
comma = True
os.write(str(e))
+
+ def writeXML(self, node, namespace):
+
+ recur = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.value_recur))
+
+ freq = XML.SubElement(recur, xmldefs.makeTag(namespace, xmldefs.recur_freq))
+ freq.text = self.cFreqToXMLMap[self.mFreq]
+
+ if self.mUseCount:
+ count = XML.SubElement(recur, xmldefs.makeTag(namespace, xmldefs.recur_count))
+ count.text = str(self.mCount)
+ elif self.mUseUntil:
+ until = XML.SubElement(recur, xmldefs.makeTag(namespace, xmldefs.recur_until))
+ self.mUntil.writeXML(until, namespace)
+
+ if self.mInterval > 1:
+ interval = XML.SubElement(recur, xmldefs.makeTag(namespace, xmldefs.recur_interval))
+ interval.text = str(self.mInterval)
+
+ 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))
+ data = ""
+ if iter[0] != 0:
+ data = str(iter[0])
+ data += self.cWeekdayRecurMap.get(iter[1], "")
+ byday.text = data
+
+ self.writeXMLList(recur, namespace, xmldefs.recur_bymonthday, self.mByMonthDay)
+ self.writeXMLList(recur, namespace, xmldefs.recur_byyearday, self.mByYearDay)
+ self.writeXMLList(recur, namespace, xmldefs.recur_byweekno, self.mByWeekNo)
+ self.writeXMLList(recur, namespace, xmldefs.recur_bymonth, self.mByMonth)
+ self.writeXMLList(recur, namespace, xmldefs.recur_bysetpos, self.mBySetPos)
+
+ # MO is the default so we do not need it
+ if self.mWeekstart != definitions.eRecurrence_WEEKDAY_MO:
+ 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 != 0) and (len(self.mBySeconds) != 0) \
- or (self.mByMinutes != 0) and (len(self.mByMinutes) != 0) \
- or (self.mByHours != 0) and (len(self.mByHours) != 0) \
- or (self.mByDay != 0) and (len(self.mByDay) != 0) \
- or (self.mByMonthDay != 0) and (len(self.mByMonthDay) != 0) \
- or (self.mByYearDay != 0) and (len(self.mByYearDay) != 0) \
- or (self.mByWeekNo != 0) and (len(self.mByWeekNo) != 0) \
- or (self.mByMonth != 0) and (len(self.mByMonth) != 0) \
- or (self.mBySetPos != 0) and (len(self.mBySetPos) != 0)
+ return (self.mBySeconds is not None) and (len(self.mBySeconds) != 0) \
+ or (self.mByMinutes is not None) and (len(self.mByMinutes) != 0) \
+ or (self.mByHours is not None) and (len(self.mByHours) != 0) \
+ or (self.mByDay is not None) and (len(self.mByDay) != 0) \
+ or (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0) \
+ or (self.mByYearDay is not None) and (len(self.mByYearDay) != 0) \
+ or (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0) \
+ or (self.mByMonth is not None) and (len(self.mByMonth) != 0) \
+ or (self.mBySetPos is not None) and (len(self.mBySetPos) != 0)
def isSimpleRule(self):
@@ -581,21 +740,21 @@
# no others
# First checks the ones we do not handle at all
- if ((self.mBySeconds != 0) and (len(self.mBySeconds) != 0) \
- or (self.mByMinutes != 0) and (len(self.mByMinutes) != 0) \
- or (self.mByHours != 0) and (len(self.mByHours) != 0) \
- or (self.mByYearDay != 0) and (len(self.mByYearDay) != 0) \
- or (self.mByWeekNo != 0) and (len(self.mByWeekNo) != 0)):
+ if ((self.mBySeconds is not None) and (len(self.mBySeconds) != 0) \
+ or (self.mByMinutes is not None) and (len(self.mByMinutes) != 0) \
+ or (self.mByHours is not None) and (len(self.mByHours) != 0) \
+ or (self.mByYearDay is not None) and (len(self.mByYearDay) != 0) \
+ or (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0)):
return False
# Check BYMONTHDAY numbers (we can handle -7...-1, 1..31)
- if self.mByMonthDay != 0:
+ if self.mByMonthDay is not None:
for iter in self.mByMonthDay:
if (iter < -7) or (iter > 31) or (iter == 0):
return False
# Check BYDAY numbers
- if self.mByDay != 0:
+ if self.mByDay is not None:
number = 0
first = True
for iter in self.mByDay:
@@ -608,19 +767,17 @@
# 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]:
return False
# Check BYSETPOS numbers
- if self.mBySetPos != 0:
+ if self.mBySetPos is not None:
if len(self.mBySetPos) > 1:
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
@@ -634,7 +791,7 @@
result = sout.getvalue()
except:
result = ""
-
+
return result
@@ -649,12 +806,12 @@
self.mCached = False
self.mFullyCached = False
self.mRecurrences = []
-
- # Is the current cache complete or does it extaned past the requested
+
+ # Is the current cache complete or does it extend past the requested
# range end
if not self.mCached or not self.mFullyCached \
and (self.mCacheUpto is None or self.mCacheUpto < range.getEnd()):
- cache_range = PyCalendarPeriod(copyit=range)
+ cache_range = range.duplicate()
# If partially cached just cache from previous cache end up to new
# end
@@ -671,18 +828,23 @@
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:
if range.isDateWithinPeriod(iter):
items.append(iter)
-
+ else:
+ limited = True
+ return limited
+
+
def simpleExpand(self, start, range, items, float_offset):
- start_iter = PyCalendarDateTime(copyit=start)
+ start_iter = start.duplicate()
ctr = 0
if self.mUseUntil:
- float_until = PyCalendarDateTime(copyit=self.mUntil)
+ float_until = self.mUntil.duplicate()
if start.floating():
float_until.setTimezoneID(0)
float_until.offsetSeconds(float_offset)
@@ -693,7 +855,7 @@
return False
# Add current one to list
- items.append(PyCalendarDateTime(copyit=start_iter))
+ items.append(start_iter.duplicate())
# Get next item
start_iter.recur(self.mFreq, self.mInterval)
@@ -710,18 +872,19 @@
if start_iter > float_until:
return True
+
def complexExpand(self, start, range, items, float_offset):
- start_iter = PyCalendarDateTime(copyit=start)
+ start_iter = start.duplicate()
ctr = 0
if self.mUseUntil:
- float_until = PyCalendarDateTime(copyit=self.mUntil)
+ float_until = self.mUntil.duplicate()
if start.floating():
float_until.setTimezoneID(None)
float_until.offsetSeconds(float_offset)
# Always add the initial instance DTSTART
- items.append(PyCalendarDateTime(copyit=start))
+ items.append(start.duplicate())
if self.mUseCount:
# Bump counter and exit if over
ctr += 1
@@ -752,10 +915,11 @@
self.generateMonthlySet(start_iter, set_items)
elif self.mFreq == definitions.eRecurrence_YEARLY:
- self.generateYearlySet(start_iter, set_items)
+ self.generateYearlySet(start_iter, set_items)
# Always sort the set as BYxxx rules may not be sorted
- set_items.sort(cmp=PyCalendarDateTime.sort)
+ #set_items.sort(cmp=PyCalendarDateTime.sort)
+ set_items.sort(key=lambda x: x.getPosixTime())
# Process each one in the generated set
for iter in set_items:
@@ -800,6 +964,7 @@
# Get next item
start_iter.recur(self.mFreq, self.mInterval)
+
def clear(self):
self.mCached = False
self.mFullyCached = False
@@ -834,330 +999,321 @@
# 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
# Start with initial date-time
- items.append(PyCalendarDateTime(copyit=start))
+ items.append(start.duplicate())
- if (self.mByMonth != 0) and (len(self.mByMonth) != 0):
+ if (self.mByMonth is not None) and (len(self.mByMonth) != 0):
items[:] = self.byMonthExpand(items)
-
- if (self.mByWeekNo != 0) and (len(self.mByWeekNo) != 0):
+ if (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0):
items[:] = self.byWeekNoExpand(items)
-
- if (self.mByYearDay != 0) and (len(self.mByYearDay) != 0):
+ if (self.mByYearDay is not None) and (len(self.mByYearDay) != 0):
items[:] = self.byYearDayExpand(items)
-
- if (self.mByMonthDay != 0) and (len(self.mByMonthDay) != 0):
+ if (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0):
items[:] = self.byMonthDayExpand(items)
-
- if (self.mByDay != 0) and (len(self.mByDay) != 0):
+ if (self.mByDay is not None) and (len(self.mByDay) != 0):
# BYDAY is complicated:
# if BYDAY is included with BYYEARDAY or BYMONTHDAY then it
# contracts the recurrence set
# else it expands it, but the expansion depends on the frequency
# and other BYxxx periodicities
- if ((self.mByYearDay != 0) and (len(self.mByYearDay) != 0)) \
- or ((self.mByMonthDay != 0) and (len(self.mByMonthDay) != 0)):
+ if ((self.mByYearDay is not None) and (len(self.mByYearDay) != 0)) \
+ or ((self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0)):
items[:] = self.byDayLimit(items)
- elif (self.mByWeekNo != 0) and (len(self.mByWeekNo) != 0):
+ elif (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0):
items[:] = self.byDayExpandWeekly(items)
- elif (self.mByMonth != 0) and (len(self.mByMonth) != 0):
+ elif (self.mByMonth is not None) and (len(self.mByMonth) != 0):
items[:] = self.byDayExpandMonthly(items)
else:
items[:] = self.byDayExpandYearly(items)
- if (self.mByHours != 0) and (len(self.mByHours) != 0):
+ if (self.mByHours is not None) and (len(self.mByHours) != 0):
items[:] = self.byHourExpand(items)
-
- if (self.mByMinutes != 0) and (len(self.mByMinutes) != 0):
+
+ if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0):
items[:] = self.byMinuteExpand(items)
-
- if (self.mBySeconds != 0) and (len(self.mBySeconds) != 0):
+
+ if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0):
items[:] = self.bySecondExpand(items)
-
- if (self.mBySetPos != 0) and (len(self.mBySetPos) != 0):
+
+ 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
# Start with initial date-time
- items.append(PyCalendarDateTime(copyit=start))
+ items.append(start.duplicate())
- if (self.mByMonth != 0) and (len(self.mByMonth) != 0):
+ if (self.mByMonth is not None) and (len(self.mByMonth) != 0):
# BYMONTH limits the range of possible values
items[:] = self.byMonthLimit(items)
if (len(items) == 0):
return
-
+
# No BYWEEKNO
# No BYYEARDAY
- if (self.mByMonthDay != 0) and (len(self.mByMonthDay) != 0):
+ if (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0):
items[:] = self.byMonthDayExpand(items)
-
- if (self.mByDay != 0) and (len(self.mByDay) != 0):
+
+ if (self.mByDay is not None) and (len(self.mByDay) != 0):
# BYDAY is complicated:
# if BYDAY is included with BYYEARDAY or BYMONTHDAY then it
# contracts the recurrence set
# else it expands it, but the expansion depends on the frequency
# and other BYxxx periodicities
- if ((self.mByYearDay != 0) and (len(self.mByYearDay) != 0)) \
- or ((self.mByMonthDay != 0) and (len(self.mByMonthDay) != 0)):
+ if ((self.mByYearDay is not None) and (len(self.mByYearDay) != 0)) \
+ or ((self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0)):
items[:] = self.byDayLimit(items)
else:
items[:] = self.byDayExpandMonthly(items)
-
- if ((self.mByHours != 0) and (len(self.mByHours) != 0)):
+
+ if ((self.mByHours is not None) and (len(self.mByHours) != 0)):
items[:] = self.byHourExpand(items)
-
- if ((self.mByMinutes != 0) and (len(self.mByMinutes) != 0)):
+
+ if ((self.mByMinutes is not None) and (len(self.mByMinutes) != 0)):
items[:] = self.byMinuteExpand(items)
-
- if ((self.mBySeconds != 0) and (len(self.mBySeconds) != 0)):
+
+ if ((self.mBySeconds is not None) and (len(self.mBySeconds) != 0)):
items[:] = self.bySecondExpand(items)
-
- if ((self.mBySetPos != 0) and (len(self.mBySetPos) != 0)):
+
+ 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
- items.append(PyCalendarDateTime(copyit=start))
+ items.append(start.duplicate())
- if (self.mByMonth != 0) and (len(self.mByMonth) != 0):
+ if (self.mByMonth is not None) and (len(self.mByMonth) != 0):
# BYMONTH limits the range of possible values
items[:] = self.byMonthLimit(items)
if (len(items) == 0):
return
-
- if (self.mByWeekNo != 0) and (len(self.mByWeekNo) != 0):
+
+ 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 != 0) and (len(self.mByDay) != 0):
+ if (self.mByDay is not None) and (len(self.mByDay) != 0):
items[:] = self.byDayExpandWeekly(items)
-
- if (self.mByHours != 0) and (len(self.mByHours) != 0):
+
+ if (self.mByHours is not None) and (len(self.mByHours) != 0):
items[:] = self.byHourExpand(items)
-
- if (self.mByMinutes != 0) and (len(self.mByMinutes) != 0):
+
+ if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0):
items[:] = self.byMinuteExpand(items)
-
- if (self.mBySeconds != 0) and (len(self.mBySeconds) != 0):
+
+ if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0):
items[:] = self.bySecondExpand(items)
-
- if (self.mBySetPos != 0) and (len(self.mBySetPos) != 0):
+
+ 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
- items.append(PyCalendarDateTime(copyit=start))
+ items.append(start.duplicate())
- if (self.mByMonth != 0) and (len(self.mByMonth) != 0):
+ if (self.mByMonth is not None) and (len(self.mByMonth) != 0):
# BYMONTH limits the range of possible values
items[:] = self.byMonthLimit(items)
if (len(items) == 0):
return
-
- if (self.mByWeekNo != 0) and (len(self.mByWeekNo) != 0):
+ 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 != 0) and (len(self.mByMonthDay) != 0):
+ if (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0):
items[:] = self.byMonthDayLimit(items)
if (len(items) == 0):
return
-
- if (self.mByDay != 0) and (len(self.mByDay) != 0):
+ if (self.mByDay is not None) and (len(self.mByDay) != 0):
items[:] = self.byDayLimit(items)
if (len(items) == 0):
return
-
- if (self.mByHours != 0) and (len(self.mByHours) != 0):
+ if (self.mByHours is not None) and (len(self.mByHours) != 0):
items[:] = self.byHourExpand(items)
-
- if (self.mByMinutes != 0) and (len(self.mByMinutes) != 0):
+ if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0):
items[:] = self.byMinuteExpand(items)
-
- if (self.mBySeconds != 0) and (len(self.mBySeconds) != 0):
+ if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0):
items[:] = self.bySecondExpand(items)
-
- if (self.mBySetPos != 0) and (len(self.mBySetPos) != 0):
+ if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0):
items[:] = self.bySetPosLimit(items)
-
+
+
def generateHourlySet(self, start, items):
# Cannot have BYYEARDAY
# Start with initial date-time
- items.append(PyCalendarDateTime(copyit=start))
+ items.append(start.duplicate())
- if (self.mByMonth != 0) and (len(self.mByMonth) != 0):
+ if (self.mByMonth is not None) and (len(self.mByMonth) != 0):
# BYMONTH limits the range of possible values
items[:] = self.byMonthLimit(items)
if (len(items) == 0):
return
-
- if (self.mByWeekNo != 0) and (len(self.mByWeekNo) != 0):
+ 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 != 0) and (len(self.mByMonthDay) != 0):
+ if (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0):
items[:] = self.byMonthDayLimit(items)
if (len(items) == 0):
return
-
- if (self.mByDay != 0) and (len(self.mByDay) != 0):
+ if (self.mByDay is not None) and (len(self.mByDay) != 0):
items[:] = self.byDayLimit(items)
if (len(items) == 0):
return
-
- if (self.mByHours != 0) and (len(self.mByHours) != 0):
+ if (self.mByHours is not None) and (len(self.mByHours) != 0):
items[:] = self.byHourLimit(items)
if (len(items) == 0):
return
-
- if (self.mByMinutes != 0) and (len(self.mByMinutes) != 0):
+
+ if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0):
items[:] = self.byMinuteExpand(items)
-
- if (self.mBySeconds != 0) and (len(self.mBySeconds) != 0):
+ if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0):
items[:] = self.bySecondExpand(items)
-
- if (self.mBySetPos != 0) and (len(self.mBySetPos) != 0):
+ if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0):
items[:] = self.bySetPosLimit(items)
+
def generateMinutelySet(self, start, items):
# Cannot have BYYEARDAY
# Start with initial date-time
- items.append(PyCalendarDateTime(copyit=start))
+ items.append(start.duplicate())
- if (self.mByMonth != 0) and (len(self.mByMonth) != 0):
+ if (self.mByMonth is not None) and (len(self.mByMonth) != 0):
# BYMONTH limits the range of possible values
items[:] = self.byMonthLimit(items)
if (len(items) == 0):
return
-
- if (self.mByWeekNo != 0) and (len(self.mByWeekNo) != 0):
+
+ 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 != 0) and (len(self.mByMonthDay) != 0):
+ if (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0):
items[:] = self.byMonthDayLimit(items)
if (len(items) == 0):
return
-
- if (self.mByDay != 0) and (len(self.mByDay) != 0):
+
+ if (self.mByDay is not None) and (len(self.mByDay) != 0):
items[:] = self.byDayLimit(items)
if (len(items) == 0):
return
-
- if (self.mByHours != 0) and (len(self.mByHours) != 0):
+
+ if (self.mByHours is not None) and (len(self.mByHours) != 0):
items[:] = self.byHourLimit(items)
if (len(items) == 0):
return
-
- if (self.mByMinutes != 0) and (len(self.mByMinutes) != 0):
+
+ if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0):
items[:] = self.byMinuteLimit(items)
if (len(items) == 0):
return
-
- if (self.mBySeconds != 0) and (len(self.mBySeconds) != 0):
+
+ if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0):
items[:] = self.bySecondExpand(items)
-
- if (self.mBySetPos != 0) and (len(self.mBySetPos) != 0):
+
+ 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
- items.append(PyCalendarDateTime(copyit=start))
+ items.append(start.duplicate())
- if (self.mByMonth != 0) and (len(self.mByMonth) != 0):
+ if (self.mByMonth is not None) and (len(self.mByMonth) != 0):
# BYMONTH limits the range of possible values
items[:] = self.byMonthLimit(items)
if (len(items) == 0):
return
-
- if (self.mByWeekNo != 0) and (len(self.mByWeekNo) != 0):
+
+ 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 != 0) and (len(self.mByMonthDay) != 0):
+ if (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0):
items[:] = self.byMonthDayLimit(items)
if (len(items) == 0):
return
-
- if (self.mByDay != 0) and (len(self.mByDay) != 0):
+
+ if (self.mByDay is not None) and (len(self.mByDay) != 0):
items[:] = self.byDayLimit(items)
if (len(items) == 0):
return
-
- if (self.mByHours != 0) and (len(self.mByHours) != 0):
+
+ if (self.mByHours is not None) and (len(self.mByHours) != 0):
items[:] = self.byHourLimit(items)
if (len(items) == 0):
return
-
- if (self.mByMinutes != 0) and (len(self.mByMinutes) != 0):
+
+ if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0):
items[:] = self.byMinuteLimit(items)
if (len(items) == 0):
return
-
- if (self.mBySeconds != 0) and (len(self.mBySeconds) != 0):
+
+ if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0):
items[:] = self.bySecondLimit(items)
if (len(items) == 0):
return
-
- if (self.mBySetPos != 0) and (len(self.mBySetPos) != 0):
+
+ 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 = []
@@ -1165,12 +1321,13 @@
# Loop over each BYMONTH and generating a new date-time for it and
# insert into output
for iter2 in self.mByMonth:
- temp = PyCalendarDateTime(copyit=iter1)
+ temp = iter1.duplicate()
temp.setMonth(iter2)
output.append(temp)
return output
+
def byWeekNoExpand(self, dates):
# Loop over all input items
output = []
@@ -1178,12 +1335,13 @@
# Loop over each BYWEEKNO and generating a new date-time for it and
# insert into output
for iter2 in self.mByWeekNo:
- temp = PyCalendarDateTime(copyit=iter1)
+ temp = iter1.duplicate()
temp.setWeekNo(iter2)
output.append(temp)
-
+
return output
+
def byYearDayExpand(self, dates):
# Loop over all input items
output = []
@@ -1191,12 +1349,13 @@
# Loop over each BYYEARDAY and generating a new date-time for it
# and insert into output
for iter2 in self.mByYearDay:
- temp = PyCalendarDateTime(copyit=iter1)
+ temp = iter1.duplicate()
temp.setYearDay(iter2)
output.append(temp)
return output
+
def byMonthDayExpand(self, dates):
# Loop over all input items
output = []
@@ -1204,12 +1363,13 @@
# Loop over each BYMONTHDAY and generating a new date-time for it
# and insert into output
for iter2 in self.mByMonthDay:
- temp = PyCalendarDateTime(copyit=iter1)
+ temp = iter1.duplicate()
temp.setMonthDay(iter2)
output.append(temp)
return output
+
def byDayExpandYearly(self, dates):
# Loop over all input items
output = []
@@ -1219,19 +1379,20 @@
for iter2 in self.mByDay:
# Numeric value means specific instance
if iter2[0] != 0:
- temp = PyCalendarDateTime(copyit=iter1)
+ temp = iter1.duplicate()
temp.setDayOfWeekInYear(iter2[0], iter2[1])
output.append(temp)
else:
# Every matching day in the year
for i in range(1, 54):
- temp = PyCalendarDateTime(copyit=iter1)
+ temp = iter1.duplicate()
temp.setDayOfWeekInYear(i, iter2[1])
if temp.getYear() == (iter1).getYear():
output.append(temp)
return output
+
def byDayExpandMonthly(self, dates):
# Loop over all input items
output = []
@@ -1241,19 +1402,20 @@
for iter2 in self.mByDay:
# Numeric value means specific instance
if iter2[0] != 0:
- temp = PyCalendarDateTime(copyit=iter1)
+ temp = iter1.duplicate()
temp.setDayOfWeekInMonth(iter2[0], iter2[1])
output.append(temp)
else:
# Every matching day in the month
for i in range(1, 7):
- temp = PyCalendarDateTime(copyit=iter1)
+ temp = iter1.duplicate()
temp.setDayOfWeekInMonth(i, iter2[1])
if temp.getMonth() == iter1.getMonth():
output.append(temp)
return output
+
def byDayExpandWeekly(self, dates):
# Must take into account the WKST value
@@ -1265,7 +1427,7 @@
for iter2 in self.mByDay:
# Numeric values are meaningless so ignore them
if iter2[0] == 0:
- temp = PyCalendarDateTime(copyit=iter1)
+ temp = iter1.duplicate()
# Determine amount of offset to apply to temp to shift it
# to the start of the week (backwards)
@@ -1285,6 +1447,7 @@
return output
+
def byHourExpand(self, dates):
# Loop over all input items
output = []
@@ -1292,12 +1455,13 @@
# Loop over each BYHOUR and generating a new date-time for it and
# insert into output
for iter2 in self.mByHours:
- temp = PyCalendarDateTime(copyit=iter1)
+ temp = iter1.duplicate()
temp.setHours(iter2)
output.append(temp)
return output
+
def byMinuteExpand(self, dates):
# Loop over all input items
output = []
@@ -1305,12 +1469,13 @@
# Loop over each BYMINUTE and generating a new date-time for it and
# insert into output
for iter2 in self.mByMinutes:
- temp = PyCalendarDateTime(copyit=iter1)
+ temp = iter1.duplicate()
temp.setMinutes(iter2)
output.append(temp)
return output
+
def bySecondExpand(self, dates):
# Loop over all input items
output = []
@@ -1318,12 +1483,13 @@
# Loop over each BYSECOND and generating a new date-time for it and
# insert into output
for iter2 in self.mBySeconds:
- temp = PyCalendarDateTime(copyit=iter1)
+ temp = iter1.duplicate()
temp.setSeconds(iter2)
output.append(temp)
return output
+
def byMonthLimit(self, dates):
# Loop over all input items
output = []
@@ -1334,12 +1500,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 = []
@@ -1350,12 +1517,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 = []
@@ -1367,12 +1535,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 = []
@@ -1383,12 +1552,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 = []
@@ -1399,12 +1569,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 = []
@@ -1415,12 +1586,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 = []
@@ -1431,15 +1603,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(cmp=PyCalendarDateTime.sort)
+ 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
@@ -1454,5 +1628,5 @@
# Negative values are offset from the end
if -iter <= input_size:
output.append(dates[input_size + iter])
-
+
return output
Modified: PyCalendar/trunk/src/pycalendar/recurrenceset.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/recurrenceset.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/recurrenceset.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,32 +14,36 @@
# limitations under the License.
##
-from datetime import PyCalendarDateTime
-from utils import set_difference
+from pycalendar.utils import set_difference
class PyCalendarRecurrenceSet(object):
- def __init__(self, copyit=None):
- if copyit is None:
- self.mRrules = []
- self.mExrules = []
- self.mRdates = []
- self.mExdates = []
- self.mRperiods = []
- self.mExperiods = []
- else:
- self.mRrules = copyit.self.mRrules
- self.mExrules = copyit.self.mExrules
- self.mRdates = copyit.self.mRdates
- self.mExdates = copyit.self.mExdates
- self.mRperiods = copyit.self.mRperiods
- self.mExperiods = copyit.self.mExperiods
+ def __init__(self):
+ self.mRrules = []
+ self.mExrules = []
+ self.mRdates = []
+ self.mExdates = []
+ self.mRperiods = []
+ self.mExperiods = []
+
+ def duplicate(self):
+ other = PyCalendarRecurrenceSet()
+ other.mRrules = [i.duplicate() for i in self.mRrules]
+ other.mExrules = [i.duplicate() for i in self.mExrules]
+ other.mRdates = [i.duplicate() for i in self.mRdates]
+ other.mExdates = [i.duplicate() for i in self.mExdates]
+ other.mRperiods = [i.duplicate() for i in self.mRperiods]
+ 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):
@@ -64,6 +68,7 @@
# If we get here they match
return True
+
def equalsRules(self, rules1, rules2):
# Check sizes first
if len(rules1) != len(rules2):
@@ -92,6 +97,7 @@
return True
+
def equalsDates(self, dates1, dates2):
# Check sizes first
if len(dates1) != len(dates2):
@@ -103,11 +109,12 @@
dt1 = dates1[:]
dt2 = dates2[:]
- dt1.sort(cmp=PyCalendarDateTime.sort)
- dt2.sort(cmp=PyCalendarDateTime.sort)
+ 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):
@@ -124,65 +131,88 @@
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
+
# Now create list of items to include
include = []
# Always include the initial DTSTART if within the range
if range.isDateWithinPeriod(start):
include.append(start)
+ else:
+ limited = True
# RRULES
for iter in self.mRrules:
- iter.expand(start, range, include, float_offset=float_offset)
+ if iter.expand(start, range, include, float_offset=float_offset):
+ limited = True
# RDATES
for iter in self.mRdates:
if range.isDateWithinPeriod(iter):
include.append(iter)
+ else:
+ limited = True
for iter in self.mRperiods:
if range.isPeriodOverlap(iter):
include.append(iter.getStart())
+ else:
+ limited = True
# Make sure the list is unique
include = [x for x in set(include)]
- include.sort(cmp=PyCalendarDateTime.sort)
+ include.sort(key=lambda x: x.getPosixTime())
# Now create list of items to exclude
exclude = []
@@ -201,12 +231,14 @@
# Make sure the list is unique
exclude = [x for x in set(exclude)]
- exclude.sort(cmp=PyCalendarDateTime.sort)
+ 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:
@@ -216,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:
@@ -227,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
@@ -242,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)
@@ -254,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():
Modified: PyCalendar/trunk/src/pycalendar/recurrencevalue.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/recurrencevalue.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/recurrencevalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,32 +14,41 @@
# limitations under the License.
##
-from recurrence import PyCalendarRecurrence
-from value import PyCalendarValue
+from pycalendar import xmldefs
+from pycalendar.recurrence import PyCalendarRecurrence
+from pycalendar.value import PyCalendarValue
-class PyCalendarRecurrenceValue( PyCalendarValue ):
+class PyCalendarRecurrenceValue(PyCalendarValue):
- def __init__( self, value = None, copyit = None ):
- if value:
- self.mValue = value
- elif copyit:
- self.mValue = PyCalendarRecurrence( copyit.mValue )
- else:
- self.mValue = PyCalendarRecurrence()
+ def __init__(self, value=None):
+ self.mValue = value if value is not None else PyCalendarRecurrence()
- def getType( self ):
+
+ def duplicate(self):
+ return PyCalendarRecurrenceValue(self.mValue.duplicate())
+
+
+ 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 getValue( self ):
+
+ 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 ):
+
+ def setValue(self, value):
self.mValue = value
-PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_RECUR, PyCalendarRecurrenceValue)
+PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_RECUR, PyCalendarRecurrenceValue, xmldefs.value_recur)
Copied: PyCalendar/trunk/src/pycalendar/requeststatusvalue.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/requeststatusvalue.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/requeststatusvalue.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/requeststatusvalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,109 @@
+##
+# 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.
+##
+
+# iCalendar REQUEST-STATUS value
+
+from pycalendar import utils, xmldefs
+from pycalendar.parser import ParserContext
+from pycalendar.value import PyCalendarValue
+import xml.etree.cElementTree as XML
+
+class PyCalendarRequestStatusValue(PyCalendarValue):
+ """
+ The value is a list of strings (either 2 or 3 items)
+ """
+
+ 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)
+
+ 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]
+
+
+ # 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 writeXML(self, node, namespace):
+ code = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.req_status_code))
+ code.text = self.mValue[0]
+
+ description = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.req_status_description))
+ description.text = self.mValue[1]
+
+ if len(self.mValue) == 3 and self.mValue[2]:
+ 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):
+ self.mValue = value
+
+PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_REQUEST_STATUS, PyCalendarRequestStatusValue, None)
Modified: PyCalendar/trunk/src/pycalendar/stringutils.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/stringutils.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/stringutils.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,10 +14,10 @@
# limitations under the License.
##
-import md5
+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,12 +58,34 @@
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:
+ if s[offset] in "0123456789":
+ offset += 1
+ continue
+ elif offset == 0:
+ raise ValueError
+ else:
+ return int(s[startoffset:offset]), offset
+ else:
+ if offset == 0:
+ raise ValueError
+ else:
+ return int(s[startoffset:]), offset
+
+
+
def strindexfind(s, ss, default_index):
if s and ss:
i = 0
@@ -75,6 +97,8 @@
return default_index
+
+
def strnindexfind(s, ss, default_index):
if s and ss:
i = 0
@@ -86,6 +110,8 @@
return default_index
+
+
def compareStringsSafe(s1, s2):
if s1 is None and s2 is None:
return True
@@ -94,5 +120,7 @@
else:
return s1 == s2
+
+
def md5digest(txt):
return md5.new(txt).hexdigest()
Modified: PyCalendar/trunk/src/pycalendar/tests/__init__.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/__init__.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/tests/__init__.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,20 +1,15 @@
##
-# 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.
# See the License for the specific language governing permissions and
# limitations under the License.
##
-
-__all__ = [
- "calendar",
- "duration",
-]
Deleted: PyCalendar/trunk/src/pycalendar/tests/calendar.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/calendar.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/tests/calendar.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,85 +0,0 @@
-##
-# Copyright (c) 2007 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
-
-import StringIO
-import unittest
-
-class TestCalendar(unittest.TestCase):
-
- def testRoundtrip(self):
-
- data = (
-"""BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-VERSION:2.0
-BEGIN:VEVENT
-DTEND;VALUE=DATE:20020102
-DTSTAMP:20020101T000000Z
-DTSTART;VALUE=DATE:20020101
-RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
-SUMMARY:New Year's Day
-UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
-END:VEVENT
-END:VCALENDAR
-""",
-
-"""BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-VERSION:2.0
-X-WR-CALNAME:PayDay
-BEGIN:VTIMEZONE
-LAST-MODIFIED:20040110T032845Z
-TZID:US/Eastern
-BEGIN:DAYLIGHT
-DTSTART:20000404T020000
-RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:20001026T020000
-RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-END:VTIMEZONE
-BEGIN:VEVENT
-DTSTAMP:20050211T173501Z
-DTSTART;VALUE=DATE:20040227
-RRULE:FREQ=MONTHLY;BYDAY=-1MO,-1TU,-1WE,-1TH,-1FR;BYSETPOS=-1
-SUMMARY:PAY DAY
-UID:DC3D0301C7790B38631F1FBB at ninevah.local
-END:VEVENT
-END:VCALENDAR
-""",
-)
-
- def _doRoundtrip(caldata):
- cal = PyCalendar()
- cal.parse(StringIO.StringIO(caldata))
-
- s = StringIO.StringIO()
- cal.generate(s)
-
- self.assertEqual(caldata, s.getvalue())
-
- for item in data:
- _doRoundtrip(item)
Deleted: PyCalendar/trunk/src/pycalendar/tests/duration.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/duration.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/tests/duration.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,54 +0,0 @@
-##
-# Copyright (c) 2007 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 StringIO import StringIO
-
-import unittest
-
-from pycalendar.duration import PyCalendarDuration
-
-class TestDuration(unittest.TestCase):
-
- def testGenerate(self):
-
- def _doTest(d, result):
- os = StringIO()
- d.generate(os)
- self.assertEqual(os.getvalue(), result)
-
- test_data = (
- (0, "PT0S"),
- (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"),
- )
-
- for seconds, result in test_data:
- _doTest(PyCalendarDuration(duration=seconds), result)
Copied: PyCalendar/trunk/src/pycalendar/tests/test_adr.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_adr.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_adr.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_adr.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,50 @@
+##
+# 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.
+##
+
+from pycalendar.adr import Adr
+import unittest
+
+class TestAdrValue(unittest.TestCase):
+
+ def testInit(self):
+ data = (
+ (
+ ("pobox", "extended", "street", "locality", "region", "postalcode", "country"),
+ "pobox;extended;street;locality;region;postalcode;country",
+ ),
+ (
+ (("pobox",), ("extended",), ("street1", "street2",), "locality", "region", (), "country"),
+ "pobox;extended;street1,street2;locality;region;;country",
+ ),
+ )
+
+ for args, result in data:
+ a = Adr(*args)
+
+ self.assertEqual(
+ a.getValue(),
+ args,
+ )
+
+ self.assertEqual(
+ a.getText(),
+ result,
+ )
+
+ self.assertEqual(
+ a.duplicate().getText(),
+ result,
+ )
Copied: PyCalendar/trunk/src/pycalendar/tests/test_adrvalue.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_adrvalue.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_adrvalue.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_adrvalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,62 @@
+##
+# 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.
+##
+
+from pycalendar.adrvalue import AdrValue
+from pycalendar.vcard.property import Property
+import unittest
+
+class TestAdrValue(unittest.TestCase):
+
+ def testParseValue(self):
+
+ items = (
+ ("", ";;;;;;"),
+ (";", ";;;;;;"),
+ (";;;;;;", ";;;;;;"),
+ (";;123 Main Street;Any Town;CA;91921-1234", ";;123 Main Street;Any Town;CA;91921-1234;"),
+ (";;;;;;USA", ";;;;;;USA"),
+ ("POB1", "POB1;;;;;;"),
+ (";EXT", ";EXT;;;;;"),
+ (";;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;"),
+ (";;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:;;;;;;"),
+ ("ADR:;;;;;;", "ADR:;;;;;;"),
+ ("ADR:;;123 Main Street;Any Town;CA;91921-1234", "ADR:;;123 Main Street;Any Town;CA;91921-1234;"),
+ ("ADR:;;;;;;USA", "ADR:;;;;;;USA"),
+ ("ADR:POB1", "ADR:POB1;;;;;;"),
+ ("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)
+ test = prop.getText()
+ self.assertEqual(test, result + "\r\n", "Failed to parse and re-generate '%s'" % (item,))
Copied: PyCalendar/trunk/src/pycalendar/tests/test_calendar.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_calendar.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_calendar.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_calendar.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,854 @@
+##
+# 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.
+##
+
+from pycalendar.calendar import PyCalendar
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.exceptions import PyCalendarInvalidData
+from pycalendar.parser import ParserContext
+from pycalendar.period import PyCalendarPeriod
+from pycalendar.property import PyCalendarProperty
+import cStringIO as StringIO
+import difflib
+import unittest
+
+class TestCalendar(unittest.TestCase):
+
+ data = (
+"""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+"""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+X-WR-CALNAME:PayDay
+BEGIN:VTIMEZONE
+TZID:US/Eastern
+LAST-MODIFIED:20040110T032845Z
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:DC3D0301C7790B38631F1FBB at ninevah.local
+DTSTART;VALUE=DATE:20040227
+DTSTAMP:20050211T173501Z
+RRULE:FREQ=MONTHLY;BYDAY=-1MO,-1TU,-1WE,-1TH,-1FR;BYSETPOS=-1
+SUMMARY:PAY DAY
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Alarm for Organizer!
+TRIGGER;RELATED=START:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+"""BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+EXDATE:20081114T000000Z
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+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
+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;ENCODING=BASE64;VALUE=BINARY:dGVzdA==
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+"""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+BEGIN:VTIMEZONE
+TZID:America/Montreal
+LAST-MODIFIED:20040110T032845Z
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VAVAILABILITY
+UID:20061005T133225Z-00001-availability at example.com
+DTSTART;TZID=America/Montreal:20060101T000000
+DTEND;TZID=America/Montreal:20060108T000000
+DTSTAMP:20061005T133225Z
+ORGANIZER:mailto:bernard at example.com
+BEGIN:AVAILABLE
+UID:20061005T133225Z-00001-A-availability at example.com
+DTSTART;TZID=America/Montreal:20060102T090000
+DTEND;TZID=America/Montreal:20060102T120000
+DTSTAMP:20061005T133225Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR
+SUMMARY:Monday\\, Wednesday and Friday from 9:00 to 12:00
+END:AVAILABLE
+BEGIN:AVAILABLE
+UID:20061005T133225Z-00001-A-availability at example.com
+RECURRENCE-ID;TZID=America/Montreal:20060106T090000
+DTSTART;TZID=America/Montreal:20060106T120000
+DTEND;TZID=America/Montreal:20060106T170000
+DTSTAMP:20061005T133225Z
+SUMMARY:Friday override from 12:00 to 17:00
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+"""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+BEGIN:VTODO
+UID:event1 at ninevah.local
+CREATED:20060101T150000Z
+DTSTAMP:20051222T205953Z
+SUMMARY:event 1
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+"""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:X-COMPONENT
+UID:1234
+END:X-COMPONENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+"""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+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
+BEGIN:VEVENT
+UID:uid4
+DTSTART;TZID=US/Pacific:20100207T170000
+DTEND;TZID=US/Pacific:20100207T173000
+CREATED:20100203T013849Z
+DTSTAMP:20100203T013909Z
+SEQUENCE:3
+SUMMARY:New Event
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:AUDIO
+ATTACH:Basso
+TRIGGER:-PT20M
+X-WR-ALARMUID:1377CCC7-F85C-4610-8583-9513D4B364E1
+END:VALARM
+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 = (
+ (
+"""BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+EXDATE:20081114T000000Z
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+END:VEVENT
+X-TEST:Testing
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+"""BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+X-TEST:Testing
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+EXDATE:20081114T000000Z
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ ),
+)
+
+
+ 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,
+ "\n".join(difflib.unified_diff(str(test1).splitlines(), test2.splitlines()))
+ )
+
+ for item in self.data:
+ _doRoundtrip(item)
+
+ 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())
+
+ for item in self.data:
+ _doDuplicateRoundtrip(item)
+
+
+ def testEquality(self):
+
+
+ def _doEquality(caldata):
+ cal1 = PyCalendar()
+ cal1.parse(StringIO.StringIO(caldata))
+
+ cal2 = PyCalendar()
+ cal2.parse(StringIO.StringIO(caldata))
+
+ self.assertEqual(cal1, cal2, "%s\n\n%s" % (cal1, cal2,))
+
+
+ def _doNonEquality(caldata):
+ cal1 = PyCalendar()
+ cal1.parse(StringIO.StringIO(caldata))
+
+ cal2 = PyCalendar()
+ cal2.parse(StringIO.StringIO(caldata))
+ cal2.addProperty(PyCalendarProperty("X-FOO", "BAR"))
+
+ self.assertNotEqual(cal1, cal2)
+
+ for item in self.data:
+ _doEquality(item)
+ _doNonEquality(item)
+
+
+ def testParseComponent(self):
+
+ data1 = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ data2 = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+BEGIN:VTIMEZONE
+TZID:America/Montreal
+LAST-MODIFIED:20040110T032845Z
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ result = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTIMEZONE
+TZID:America/Montreal
+LAST-MODIFIED:20040110T032845Z
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ 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
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+""".replace("\n", "\r\n"),
+
+"""BEGIN:VCARD
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+VERSION:2.0
+END:VCARD
+""".replace("\n", "\r\n"),
+
+"""BOGUS
+BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+"""BOGUS
+
+BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+"""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+BOGUS
+""".replace("\n", "\r\n"),
+
+"""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+
+BOGUS
+""".replace("\n", "\r\n"),
+
+ )
+
+ for item in data:
+ self.assertRaises(PyCalendarInvalidData, PyCalendar.parseText, item)
+
+
+ def testParseBlank(self):
+
+ data = (
+"""
+BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+"""
+
+BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+"""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+
+
+""".replace("\n", "\r\n"),
+
+"""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+"""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+
+
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ )
+
+ save = ParserContext.BLANK_LINES_IN_DATA
+ for item in data:
+ ParserContext.BLANK_LINES_IN_DATA = ParserContext.PARSER_RAISE
+ self.assertRaises(PyCalendarInvalidData, PyCalendar.parseText, item)
+
+ ParserContext.BLANK_LINES_IN_DATA = ParserContext.PARSER_IGNORE
+ lines = item.split("\r\n")
+ result = "\r\n".join([line for line in lines if line]) + "\r\n"
+ self.assertEqual(str(PyCalendar.parseText(item)), result)
+
+ ParserContext.BLANK_LINES_IN_DATA = save
+
+
+ def testGetVEvents(self):
+
+ data = (
+ (
+ "Non-recurring match",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20110601
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (PyCalendarDateTime(2011, 6, 1),),
+ ),
+ (
+ "Non-recurring no-match",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20110501
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (),
+ ),
+ (
+ "Recurring match",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20110601
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=DAILY;COUNT=2
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (
+ PyCalendarDateTime(2011, 6, 1),
+ PyCalendarDateTime(2011, 6, 2),
+ ),
+ ),
+ (
+ "Recurring no match",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20110501
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=DAILY;COUNT=2
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (),
+ ),
+ (
+ "Recurring with override match",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART:20110601T120000
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=DAILY;COUNT=2
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20110602T120000
+DTSTART;VALUE=DATE:20110602T130000
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (
+ PyCalendarDateTime(2011, 6, 1, 12, 0, 0),
+ PyCalendarDateTime(2011, 6, 2, 13, 0, 0),
+ ),
+ ),
+ (
+ "Recurring with override no match",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART:20110501T120000
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=DAILY;COUNT=2
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20110502T120000
+DTSTART;VALUE=DATE:20110502T130000
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (),
+ ),
+ (
+ "Recurring partial match",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20110531
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=DAILY;COUNT=2
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (
+ PyCalendarDateTime(2011, 6, 1),
+ ),
+ ),
+ (
+ "Recurring with override partial match",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART:20110531T120000
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=DAILY;COUNT=2
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20110601T120000
+DTSTART;VALUE=DATE:20110601T130000
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (
+ PyCalendarDateTime(2011, 6, 1, 13, 0, 0),
+ ),
+ ),
+ )
+
+ for title, caldata, result in data:
+ calendar = PyCalendar.parseText(caldata)
+ instances = []
+ calendar.getVEvents(
+ PyCalendarPeriod(
+ start=PyCalendarDateTime(2011, 6, 1),
+ end=PyCalendarDateTime(2011, 7, 1),
+ ),
+ instances
+ )
+ instances = tuple([instance.getInstanceStart() for instance in instances])
+ self.assertEqual(instances, result, "Failed in %s: got %s, expected %s" % (title, instances, result))
Copied: PyCalendar/trunk/src/pycalendar/tests/test_componentrecur.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_componentrecur.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_componentrecur.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_componentrecur.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,66 @@
+# 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.
+##
+
+from pycalendar.calendar import PyCalendar
+import cStringIO as StringIO
+import unittest
+
+class TestCalendar(unittest.TestCase):
+
+ def testDuplicateWithRecurrenceChange(self):
+
+ data = (
+"""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+"""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;COUNT=400
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+)
+
+ cal1 = PyCalendar()
+ cal1.parse(StringIO.StringIO(data[0]))
+ cal2 = cal1.duplicate()
+ vevent = cal2.getComponents()[0]
+ rrules = vevent.getRecurrenceSet()
+ for rrule in rrules.getRules():
+ rrule.setUseCount(True)
+ rrule.setCount(400)
+ rrules.changed()
+
+ self.assertEqual(data[0], str(cal1))
+ self.assertEqual(data[1], str(cal2))
Copied: PyCalendar/trunk/src/pycalendar/tests/test_datetime.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_datetime.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_datetime.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_datetime.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,324 @@
+##
+# 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.
+##
+
+from pycalendar.calendar import PyCalendar
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.timezone import PyCalendarTimezone
+import unittest
+
+class TestDateTime(unittest.TestCase):
+
+ def testDuplicateASUTC(self):
+
+ items = (
+ (
+ PyCalendarDateTime(2011, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+ PyCalendarDateTime(2011, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+ ),
+ (
+ PyCalendarDateTime(2011, 1, 1, 0, 0, 0),
+ PyCalendarDateTime(2011, 1, 1, 0, 0, 0),
+ ),
+ (
+ PyCalendarDateTime(2011, 1, 1),
+ 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",
+ "20110103T121212Z",
+ "00010102",
+ "00010103T121212",
+ "00010103T121212Z",
+ )
+
+ data2 = (
+ ("20110102", "20110102"),
+ ("2011-01-02", "20110102"),
+ ("20110103T121212", "20110103T121212"),
+ ("2011-01-03T12:12:12", "20110103T121212"),
+ ("20110103T121212Z", "20110103T121212Z"),
+ ("2011-01-03T12:12:12Z", "20110103T121212Z"),
+ ("20110103T121212+0100", "20110103T121212+0100"),
+ ("2011-01-03T12:12:12-0500", "20110103T121212-0500"),
+ ("20110103T121212,123", "20110103T121212"),
+ ("2011-01-03T12:12:12,123", "20110103T121212"),
+ ("20110103T121212,123Z", "20110103T121212Z"),
+ ("2011-01-03T12:12:12,123Z", "20110103T121212Z"),
+ ("20110103T121212,123+0100", "20110103T121212+0100"),
+ ("2011-01-03T12:12:12,123-0500", "20110103T121212-0500"),
+ )
+
+ 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):
+
+ data1 = (
+ "2011",
+ "201101023",
+ "20110103t121212",
+ "20110103T1212",
+ "20110103T1212123",
+ "20110103T121212A",
+ "2011-01-03T121212Z",
+ "20110103T12:12:12Z",
+ "20110103T121212+0500",
+ " 10102",
+ " 10102T010101",
+ "2011 102",
+ "201101 3T121212",
+ "-1110102",
+ "2011-102",
+ "201101-3T121212",
+ )
+ data2 = (
+ "2011-01+02",
+ "20110103T12-12-12",
+ "2011-01-03T12:12:12,",
+ "2011-01-03T12:12:12,ABC",
+ "20110103T12:12:12-1",
+ )
+
+ for item in data1:
+ self.assertRaises(ValueError, PyCalendarDateTime.parseText, item, False)
+
+ for item in data2:
+ self.assertRaises(ValueError, PyCalendarDateTime.parseText, item, False)
+
+
+ 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)
Copied: PyCalendar/trunk/src/pycalendar/tests/test_duration.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_duration.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_duration.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_duration.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,101 @@
+##
+# 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.
+##
+
+from cStringIO import StringIO
+from pycalendar.duration import PyCalendarDuration
+from pycalendar.parser import ParserContext
+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"),
+ )
+
+ def testGenerate(self):
+
+ def _doTest(d, result):
+
+ if result[0] == "+":
+ result = result[1:]
+ os = StringIO()
+ d.generate(os)
+ self.assertEqual(os.getvalue(), result)
+
+ 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",
+ "P",
+ "PABC",
+ "P12",
+ "P12D1",
+ "P12DTAB",
+ "P12DT1HA",
+ "P12DT1MA",
+ "P12DT1SA",
+ )
+ 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)
Copied: PyCalendar/trunk/src/pycalendar/tests/test_i18n.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_i18n.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_i18n.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_i18n.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,74 @@
+# coding: utf-8
+##
+# 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.
+##
+
+from pycalendar.attribute import PyCalendarAttribute
+from pycalendar.calendar import PyCalendar
+import cStringIO as StringIO
+import unittest
+
+class TestCalendar(unittest.TestCase):
+
+ def testAddCN(self):
+
+ data = (
+"""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+ORGANIZER:user01 at example.com
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+ "まだ",
+
+"""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+ORGANIZER;CN=まだ:user01 at example.com
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+ )
+
+ cal1 = PyCalendar()
+ cal1.parse(StringIO.StringIO(data[0]))
+
+ vevent = cal1.getComponents("VEVENT")[0]
+ organizer = vevent.getProperties("ORGANIZER")[0]
+ organizer.addAttribute(PyCalendarAttribute("CN", data[1]))
+
+ cal2 = PyCalendar()
+ cal2.parse(StringIO.StringIO(data[2]))
+
+ self.assertEqual(str(cal1), str(cal2))
Copied: PyCalendar/trunk/src/pycalendar/tests/test_multivalue.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_multivalue.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_multivalue.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_multivalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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")
Copied: PyCalendar/trunk/src/pycalendar/tests/test_n.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_n.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_n.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_n.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,92 @@
+##
+# 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.
+##
+
+from pycalendar.n import N
+import unittest
+
+class TestAdrValue(unittest.TestCase):
+
+ def testInit(self):
+
+ data = (
+ (
+ ("last", "first", "middle", "prefix", "suffix"),
+ "last;first;middle;prefix;suffix",
+ "prefix first middle last suffix",
+ ),
+ (
+ ("last", ("first",), ("middle1", "middle2",), (), ("suffix",)),
+ "last;first;middle1,middle2;;suffix",
+ "first middle1 middle2 last suffix",
+ ),
+ )
+
+ for args, result, fullName in data:
+ n = N(*args)
+
+ self.assertEqual(
+ n.getValue(),
+ args,
+ )
+
+ self.assertEqual(
+ n.getText(),
+ result,
+ )
+
+ self.assertEqual(
+ n.getFullName(),
+ fullName,
+ )
+
+ self.assertEqual(
+ n.duplicate().getText(),
+ result,
+ )
+
+
+ def testInitWithKeywords(self):
+
+ data = (
+ (
+ {"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",)},
+ "last;first;middle1,middle2;;suffix",
+ "first middle1 middle2 last suffix",
+ ),
+ )
+
+ for kwargs, result, fullName in data:
+ n = N(**kwargs)
+
+ self.assertEqual(
+ n.getText(),
+ result,
+ )
+
+ self.assertEqual(
+ n.getFullName(),
+ fullName,
+ )
+
+ self.assertEqual(
+ n.duplicate().getText(),
+ result,
+ )
Copied: PyCalendar/trunk/src/pycalendar/tests/test_nvalue.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_nvalue.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_nvalue.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_nvalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,59 @@
+##
+# 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.
+##
+
+from pycalendar.nvalue import NValue
+from pycalendar.vcard.property import Property
+import unittest
+
+class TestNValue(unittest.TestCase):
+
+ def testParseValue(self):
+
+ items = (
+ ("", ";;;;"),
+ (";", ";;;;"),
+ (";;;;", ";;;;"),
+ ("Cyrus;Daboo;;Dr.", "Cyrus;Daboo;;Dr.;"),
+ (";;;;PhD.", ";;;;PhD."),
+ ("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:;;;;"),
+ ("N:;;;;", "N:;;;;"),
+ ("N:Cyrus;Daboo;;Dr.", "N:Cyrus;Daboo;;Dr.;"),
+ ("N:;;;;PhD.", "N:;;;;PhD."),
+ ("N:Cyrus", "N:Cyrus;;;;"),
+ ("N:;Daboo", "N:;Daboo;;;"),
+ ("N;VALUE=TEXT:Cyrus;Daboo;;Dr.", "N:Cyrus;Daboo;;Dr.;"),
+ )
+
+ 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,))
Copied: PyCalendar/trunk/src/pycalendar/tests/test_orgvalue.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_orgvalue.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_orgvalue.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_orgvalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,53 @@
+##
+# 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.
+##
+
+from pycalendar.orgvalue import OrgValue
+from pycalendar.vcard.property import Property
+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"),
+ ("ORG:Example\, Inc.", "ORG:Example\, Inc."),
+ ("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)
+ test = prop.getText()
+ self.assertEqual(test, result + "\r\n", "Failed to parse and re-generate '%s'" % (item,))
Copied: PyCalendar/trunk/src/pycalendar/tests/test_property.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_property.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_property.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_property.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,189 @@
+##
+# 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.
+##
+
+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==",
+ "attach;VALUE=BINARY:VGVzdA==",
+ "ORGANIZER:mailto:jdoe at example.com",
+ "DTSTART;TZID=US/Eastern:20060226T120000",
+ "DTSTART;VALUE=DATE:20060226",
+ "DTSTART:20060226T130000Z",
+ "X-FOO:BAR",
+ "DURATION:PT10M",
+ "duraTION:PT10M",
+ "SEQUENCE:1",
+ "RDATE:20060226T120000Z,20060227T120000Z",
+ "FREEBUSY:20060226T120000Z/20060227T120000Z",
+ "SUMMARY:Some \\ntext",
+ "RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=-1",
+ "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 at example.com",
+
+ # Parameter escaping
+ "ATTENDEE;CN=My ^'Test^' Name;ROLE=CHAIR:mailto:jdoe at example.com",
+ )
+
+
+ def testParseGenerate(self):
+
+ for data in TestProperty.test_data:
+ prop = PyCalendarProperty()
+ 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 = 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",
+ "DTSTART:20060226T120000A",
+ "X-FOO;:BAR",
+ "DURATION:A",
+ "SEQUENCE:b",
+ "RDATE:20060226T120000Z;20060227T120000Z",
+ "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""",
+ )
+ save = ParserContext.INVALID_ESCAPE_SEQUENCES
+ for data in test_bad_data:
+ ParserContext.INVALID_ESCAPE_SEQUENCES = ParserContext.PARSER_RAISE
+ 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()
+ prop.parse(item)
+ hashes.append(hash(prop))
+ hashes.sort()
+ for i in range(1, len(hashes)):
+ self.assertNotEqual(hashes[i - 1], hashes[i])
+
+
+ def testDefaultValueCreate(self):
+
+ test_data = (
+ ("ATTENDEE", "mailto:attendee at example.com", "ATTENDEE:mailto:attendee at example.com\r\n"),
+ ("attendee", "mailto:attendee at example.com", "attendee:mailto:attendee at example.com\r\n"),
+ ("ORGANIZER", "mailto:organizer at example.com", "ORGANIZER:mailto:organizer at example.com\r\n"),
+ ("ORGANizer", "mailto:organizer at example.com", "ORGANizer:mailto:organizer at example.com\r\n"),
+ ("URL", "http://example.com/tz1", "URL:http://example.com/tz1\r\n"),
+ ("TZURL", "http://example.com/tz2", "TZURL:http://example.com/tz2\r\n"),
+ )
+ 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")
Copied: PyCalendar/trunk/src/pycalendar/tests/test_recurrence.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_recurrence.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_recurrence.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_recurrence.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,113 @@
+##
+# 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.
+##
+
+from pycalendar.recurrence import PyCalendarRecurrence
+import unittest
+
+class TestRecurrence(unittest.TestCase):
+
+ items = (
+ "FREQ=DAILY",
+ "FREQ=YEARLY;COUNT=400",
+ "FREQ=MONTHLY;UNTIL=20110102",
+ "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",
+ "FREQ=YEARLY;UNTIL=20060402T070000Z;BYDAY=1SU;BYMONTH=4",
+ "FREQ=YEARLY;BYDAY=2SU;BYMONTH=3",
+ "FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10",
+ "FREQ=DAILY;INTERVAL=2",
+ "FREQ=DAILY;COUNT=5;INTERVAL=10",
+ "FREQ=DAILY;UNTIL=20000131T140000Z;BYMONTH=1",
+ "FREQ=WEEKLY;INTERVAL=2;WKST=SU",
+ "FREQ=WEEKLY;UNTIL=19971007T000000Z;BYDAY=TU,TH;WKST=SU",
+ "FREQ=WEEKLY;COUNT=10;BYDAY=TU,TH;WKST=SU",
+ "FREQ=WEEKLY;UNTIL=19971224T000000Z;INTERVAL=2;BYDAY=MO,WE,FR;WKST=SU",
+ "FREQ=WEEKLY;COUNT=8;INTERVAL=2;BYDAY=TU,TH;WKST=SU",
+ "FREQ=MONTHLY;BYMONTHDAY=-3",
+ "FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1",
+ "FREQ=MONTHLY;COUNT=10;INTERVAL=18;BYMONTHDAY=10,11,12,13,14,15",
+ "FREQ=YEARLY;COUNT=10;INTERVAL=3;BYYEARDAY=1,100,200",
+ "FREQ=YEARLY;BYDAY=MO;BYWEEKNO=20",
+ "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=",
+ "FREQ=MICROSECONDLY",
+ "FREQ=YEARLY;COUNT=ABC",
+ "FREQ=YEARLY;COUNT=123;UNTIL=20110102",
+ "FREQ=MONTHLY;UNTIL=20110102T",
+ "FREQ=MONTHLY;UNTIL=20110102t090000",
+ "FREQ=MONTHLY;UNTIL=20110102T100000z",
+ "FREQ=MONTHLY;UNTIL=20110102TAABBCCz",
+ "FREQ=MONTHLY;BYDAY=A",
+ "FREQ=MONTHLY;BYDAY=+1,3MO",
+ "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()
+ recur.parse(item)
+ hashes.append(hash(recur))
+ hashes.sort()
+ for i in range(1, len(hashes)):
+ self.assertNotEqual(hashes[i - 1], hashes[i])
Copied: PyCalendar/trunk/src/pycalendar/tests/test_requeststatus.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_requeststatus.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_requeststatus.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_requeststatus.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,75 @@
+##
+# 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.
+##
+
+from pycalendar.parser import ParserContext
+from pycalendar.property import PyCalendarProperty
+from pycalendar.requeststatusvalue import PyCalendarRequestStatusValue
+import unittest
+
+class TestRequestStatus(unittest.TestCase):
+
+ def testParseValue(self):
+
+ items = (
+ "2.0;Success",
+ "2.0;Success\;here",
+ "2.0;Success;Extra",
+ "2.0;Success\;here;Extra",
+ "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"
+
+ # 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",
+ "REQUEST-STATUS:2.0;Success;Extra",
+ "REQUEST-STATUS:2.0;Success\;here;Extra",
+ "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)
+ self.assertEqual(req.getText(), item + "\r\n", "Failed to parse and re-generate '%s'" % (item,))
Copied: PyCalendar/trunk/src/pycalendar/tests/test_timezone.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_timezone.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_timezone.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_timezone.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,235 @@
+##
+# 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.
+##
+
+from pycalendar.calendar import PyCalendar
+from pycalendar.datetime import PyCalendarDateTime
+import unittest
+
+class TestCalendar(unittest.TestCase):
+
+ def testOffsets(self):
+
+ data = (
+ ("""BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//calendarserver.org//Zonal//EN
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:America/New_York
+X-LIC-LOCATION:America/New_York
+BEGIN:STANDARD
+DTSTART:18831118T120358
+RDATE:18831118T120358
+TZNAME:EST
+TZOFFSETFROM:-045602
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19180331T020000
+RRULE:FREQ=YEARLY;UNTIL=19190330T070000Z;BYDAY=-1SU;BYMONTH=3
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19181027T020000
+RRULE:FREQ=YEARLY;UNTIL=19191026T060000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:STANDARD
+DTSTART:19200101T000000
+RDATE:19200101T000000
+RDATE:19420101T000000
+RDATE:19460101T000000
+RDATE:19670101T000000
+TZNAME:EST
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19200328T020000
+RDATE:19200328T020000
+RDATE:19740106T020000
+RDATE:19750223T020000
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19201031T020000
+RDATE:19201031T020000
+RDATE:19450930T020000
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19210424T020000
+RRULE:FREQ=YEARLY;UNTIL=19410427T070000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19210925T020000
+RRULE:FREQ=YEARLY;UNTIL=19410928T060000Z;BYDAY=-1SU;BYMONTH=9
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19420209T020000
+RDATE:19420209T020000
+TZNAME:EWT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:19450814T190000
+RDATE:19450814T190000
+TZNAME:EPT
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:19460428T020000
+RRULE:FREQ=YEARLY;UNTIL=19660424T070000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19460929T020000
+RRULE:FREQ=YEARLY;UNTIL=19540926T060000Z;BYDAY=-1SU;BYMONTH=9
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:STANDARD
+DTSTART:19551030T020000
+RRULE:FREQ=YEARLY;UNTIL=19661030T060000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19670430T020000
+RRULE:FREQ=YEARLY;UNTIL=19730429T070000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19671029T020000
+RRULE:FREQ=YEARLY;UNTIL=20061029T060000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19760425T020000
+RRULE:FREQ=YEARLY;UNTIL=19860427T070000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:19870405T020000
+RRULE:FREQ=YEARLY;UNTIL=20060402T070000Z;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+END:VCALENDAR
+""",
+ (
+ (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),
+ )
+ ),
+ ("""BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//calendarserver.org//Zonal//EN
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:Etc/GMT+8
+X-LIC-LOCATION:Etc/GMT+8
+BEGIN:STANDARD
+DTSTART:18000101T000000
+RDATE:18000101T000000
+TZNAME:GMT+8
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+END:VCALENDAR
+""",
+ (
+ (PyCalendarDateTime(1942, 2, 8), -8),
+ (PyCalendarDateTime(1942, 2, 10), -8),
+ (PyCalendarDateTime(2011, 1, 1), -8),
+ (PyCalendarDateTime(2011, 4, 1), -8),
+ )
+ ),
+ )
+
+ for tzdata, offsets in data:
+
+ cal = PyCalendar.parseText(tzdata.replace("\n", "\r\n"))
+ tz = cal.getComponents()[0]
+
+ for dt, offset in offsets:
+ tzoffset = tz.getTimezoneOffsetSeconds(dt)
+ 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)
+ 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:
+ tz.mCachedExpandAllMax = None
+ tzoffset = tz.getTimezoneOffsetSeconds(dt)
+ 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):
+ tz.mCachedExpandAllMax = None
+ tzoffset = tz.getTimezoneOffsetSeconds(dt)
+ self.assertEqual(tzoffset, offset * 60 * 60, "Failed to match offset for %s at %s without caching, reversed" % (tz.getID(), dt,))
Copied: PyCalendar/trunk/src/pycalendar/tests/test_urivalue.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_urivalue.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_urivalue.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_urivalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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,))
Copied: PyCalendar/trunk/src/pycalendar/tests/test_utils.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_utils.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_utils.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_utils.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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)
Copied: PyCalendar/trunk/src/pycalendar/tests/test_validation.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_validation.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_validation.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_validation.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,92 @@
+##
+# 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.
+##
+
+from pycalendar.property import PyCalendarProperty
+from pycalendar.validation import partial, PropertyValueChecks
+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,),
+ ("PERCENT-COMPLETE:100", 0, 100, True,),
+ ("PERCENT-COMPLETE:50", 0, 100, True,),
+ ("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)
Copied: PyCalendar/trunk/src/pycalendar/tests/test_xml.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/tests/test_xml.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/tests/test_xml.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_xml.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,104 @@
+##
+# 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.
+##
+
+from pycalendar.calendar import PyCalendar
+import cStringIO as StringIO
+import difflib
+import unittest
+
+class TestCalendar(unittest.TestCase):
+
+ data = (
+ (
+"""BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+EXDATE:20081114T000000Z
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+END:VEVENT
+X-TEST:Testing
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+"""<?xml version="1.0" encoding="utf-8"?>
+<ns0:icalendar xmlns:ns0="urn:ietf:params:xml:ns:icalendar-2.0">
+ <ns0:vcalendar>
+ <ns0:properties>
+ <ns0:version>
+ <ns0:text>2.0</ns0:text>
+ </ns0:version>
+ <ns0:prodid>
+ <ns0:text>-//mulberrymail.com//Mulberry v4.0//EN</ns0:text>
+ </ns0:prodid>
+ <ns0:x-test>
+ <ns0:unknown>Testing</ns0:unknown>
+ </ns0:x-test>
+ </ns0:properties>
+ <ns0:components>
+ <ns0:vevent>
+ <ns0:properties>
+ <ns0:uid>
+ <ns0:text>12345-67890-3</ns0:text>
+ </ns0:uid>
+ <ns0:dtstart>
+ <ns0:date-time>2007-11-14T00:00:00Z</ns0:date-time>
+ </ns0:dtstart>
+ <ns0:attendee>
+ <ns0:cal-address>mailto:user2 at example.com</ns0:cal-address>
+ </ns0:attendee>
+ <ns0:exdate>
+ <ns0:date-time>2008-11-14T00:00:00Z</ns0:date-time>
+ </ns0:exdate>
+ <ns0:organizer>
+ <ns0:cal-address>mailto:user1 at example.com</ns0:cal-address>
+ </ns0:organizer>
+ <ns0:rrule>
+ <ns0:recur>
+ <ns0:freq>YEARLY</ns0:freq>
+ </ns0:recur>
+ </ns0:rrule>
+ </ns0:properties>
+ </ns0:vevent>
+ </ns0:components>
+ </ns0:vcalendar>
+</ns0:icalendar>
+""",
+ ),
+)
+
+ def testGenerateXML(self):
+
+ def _doRoundtrip(caldata, resultdata=None):
+ test1 = resultdata if resultdata is not None else caldata
+
+ cal = PyCalendar()
+ cal.parse(StringIO.StringIO(caldata))
+
+ test2 = cal.getTextXML()
+
+ self.assertEqual(
+ test1,
+ test2,
+ "\n".join(difflib.unified_diff(str(test1).splitlines(), test2.splitlines()))
+ )
+
+ for item1, item2 in self.data:
+ _doRoundtrip(item1, item2)
Modified: PyCalendar/trunk/src/pycalendar/textvalue.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/textvalue.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/textvalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -16,26 +16,27 @@
# iCalendar UTC Offset value
-from plaintextvalue import PyCalendarPlainTextValue
-from value import PyCalendarValue
-import utils
+from pycalendar import utils, xmldefs
+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)
-
\ No newline at end of file
+PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_TEXT, PyCalendarTextValue, xmldefs.value_text)
Modified: PyCalendar/trunk/src/pycalendar/timezone.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/timezone.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/timezone.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,26 +14,42 @@
# limitations under the License.
##
-import stringutils
+from pycalendar import stringutils
+from pycalendar.timezonedb import PyCalendarTimezoneDatabase
class PyCalendarTimezone(object):
-
- def __init__(self, utc=None, tzid=None, copyit=None):
-
+ """
+ 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
- elif copyit is not None:
- self._copy_PyCalendarTimezone(copyit)
+ elif tzid is not None:
+ self.mUTC = tzid.lower() == 'utc'
+ self.mTimezone = None if tzid.lower() == 'utc' else tzid
else:
self.mUTC = True
self.mTimezone = None
-
- # Copy defauilt timezone if it exists
- from manager import PyCalendarManager
- if PyCalendarManager.sICalendarManager is not None:
- self._copy_PyCalendarTimezone(PyCalendarManager.sICalendarManager.getDefaultTimezone())
+ # Copy default timezone if it exists
+ 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():
@@ -43,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'
@@ -53,51 +70,60 @@
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 not self.mTimezone
+ return not self.mUTC and self.mTimezone is None
+
def hasTZID(self):
- return not self.mUTC and self.mTimezone
+ return not self.mUTC and self.mTimezone is not None
+
def timeZoneSecondsOffset(self, dt):
- from manager import PyCalendarManager
- from calendar import PyCalendar
if self.mUTC:
return 0
- elif not self.mTimezone:
- return PyCalendar.sICalendar.getTimezoneOffsetSeconds(PyCalendarManager.sICalendarManager.getDefaultTimezone().getTimezoneID(), dt)
+ elif self.mTimezone is None:
+ return PyCalendarTimezoneDatabase.getTimezoneOffsetSeconds(PyCalendarTimezone.sDefaultTimezone.getTimezoneID(), dt)
+ elif isinstance(self.mTimezone, int):
+ return self.mTimezone
+ else:
+ # Look up timezone and resolve date using default timezones
+ return PyCalendarTimezoneDatabase.getTimezoneOffsetSeconds(self.mTimezone, dt)
- # Look up timezone and resolve date using default timezones
- return PyCalendar.sICalendar.getTimezoneOffsetSeconds(self.mTimezone, dt)
def timeZoneDescriptor(self, dt):
- from manager import PyCalendarManager
- from calendar import PyCalendar
if self.mUTC:
return "(UTC)"
- elif not self.mTimezone:
- return PyCalendar.sICalendar.getTimezoneDescriptor(PyCalendarManager.sICalendarManager.getDefaultTimezone().getTimezoneID(), dt)
+ elif self.mTimezone is None:
+ return PyCalendarTimezoneDatabase.getTimezoneDescriptor(PyCalendarTimezone.sDefaultTimezone.getTimezoneID(), dt)
+ elif isinstance(self.mTimezone, int):
+ sign = "-" if self.mTimezone < 0 else "+"
+ hours = abs(self.mTimezone) / 3600
+ minutes = divmod(abs(self.mTimezone) / 60, 60)[1]
+ return "%s%02d%02d" % (sign, hours, minutes,)
+ else:
+ # Look up timezone and resolve date using default timezones
+ return PyCalendarTimezoneDatabase.getTimezoneDescriptor(self.mTimezone, dt)
- # Look up timezone and resolve date using default timezones
- return PyCalendar.sICalendar.getTimezoneDescriptor(self.mTimezone, dt)
-
- def _copy_PyCalendarTimezone(self, copy):
- self.mUTC = copy.mUTC
- self.mTimezone = copy.mTimezone
-
\ No newline at end of file
+PyCalendarTimezone.sDefaultTimezone = PyCalendarTimezone()
Copied: PyCalendar/trunk/src/pycalendar/timezonedb.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/timezonedb.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/timezonedb.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/timezonedb.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,160 @@
+##
+# 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.
+##
+
+from pycalendar.exceptions import PyCalendarNoTimezoneInDatabase, \
+ PyCalendarInvalidData
+import os
+
+class PyCalendarTimezoneDatabase(object):
+ """
+ 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():
+ 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
+ tzdb = PyCalendarTimezoneDatabase.getTimezoneDatabase()
+ tz = tzdb.calendar.getTimezone(tzid)
+ if tz is None:
+ try:
+ tzdb.cacheTimezone(tzid)
+ except PyCalendarNoTimezoneInDatabase:
+ pass
+ 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):
+ # Cache it first
+ tz = PyCalendarTimezoneDatabase.getTimezone(tzid)
+ if tz is not None:
+ return tz.getTimezoneOffsetSeconds(dt)
+ else:
+ return 0
+
+
+ @staticmethod
+ def getTimezoneDescriptor(tzid, dt):
+ # Cache it first
+ tz = PyCalendarTimezoneDatabase.getTimezone(tzid)
+ if tz is not None:
+ return tz.getTimezoneDescriptor(dt)
+ else:
+ return ""
+
+
+ def cacheTimezone(self, tzid):
+
+ if self.dbpath is None:
+ return
+
+ tzpath = os.path.join(self.dbpath, "%s.ics" % (tzid,))
+ tzpath = os.path.normpath(tzpath)
+ if tzpath.startswith(self.dbpath) and os.path.isfile(tzpath):
+ try:
+ self.calendar.parseComponent(open(tzpath))
+ except (IOError, PyCalendarInvalidData):
+ 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 tzdb.calendar:
+ return
+
+ # Merge each timezone from other calendar
+ for tz in tzs:
+ 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)
Copied: PyCalendar/trunk/src/pycalendar/unknownvalue.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/unknownvalue.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/unknownvalue.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/unknownvalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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)
Modified: PyCalendar/trunk/src/pycalendar/urivalue.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/urivalue.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/urivalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,16 +14,26 @@
# limitations under the License.
##
-# iCalendar UTC Offset value
+# iCalendar URI value
-from plaintextvalue import PyCalendarPlainTextValue
-from value import PyCalendarValue
+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)
-
\ 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
+
+PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_URI, PyCalendarURIValue, xmldefs.value_uri)
Modified: PyCalendar/trunk/src/pycalendar/utcoffsetvalue.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/utcoffsetvalue.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/utcoffsetvalue.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -16,77 +16,96 @@
# iCalendar UTC Offset value
-from value import PyCalendarValue
+from cStringIO import StringIO
+from pycalendar import xmldefs
+from pycalendar.value import PyCalendarValue
-class PyCalendarUTCOffsetValue( PyCalendarValue ):
+class PyCalendarUTCOffsetValue(PyCalendarValue):
- def __init__( self, copyit = None ):
-
- if copyit:
- self.mValue = copyit.mValue
- else:
- self.mValue = 0
+ def __init__(self, value=0):
+ self.mValue = value
- def getType( self ):
+
+ def duplicate(self):
+ return PyCalendarUTCOffsetValue(self.mValue)
+
+
+ def getType(self):
return PyCalendarValue.VALUETYPE_UTC_OFFSET
- def parse( self, data ):
+
+ def parse(self, data):
# Must be of specific lengths
- if ( len( data ) != 5 ) and ( len( data ) != 7 ):
+ datalen = len(data)
+ if datalen not in (5, 7):
self.mValue = 0
- return
+ raise ValueError
# Get sign
- plus = ( data[0] == '+' )
+ if data[0] not in ('+', '-'):
+ raise ValueError
+ plus = (data[0] == '+')
# Get hours
- hours = int( data[1:3] )
+ hours = int(data[1:3])
# Get minutes
- mins = int( data[3:5] )
+ mins = int(data[3:5])
# Get seconds if present
secs = 0
- if ( len( data ) == 7 ):
- secs = int( data[5:] )
+ if datalen == 7 :
+ secs = int(data[5:])
+ self.mValue = ((hours * 60) + mins) * 60 + secs
+ if not plus:
+ self.mValue = -self.mValue
- 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 getValue( self ):
+
+ def writeXML(self, node, namespace):
+
+ os = StringIO.StringIO()
+ self.generate(os)
+ text = os.getvalue()
+ text = text[:-2] + ":" + text[-2:]
+
+ value = self.getXMLNode(node, namespace)
+ value.text = text
+
+
+ def getValue(self):
return self.mValue
- def setValue( self, value ):
+
+ def setValue(self, value):
self.mValue = value
-PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_UTC_OFFSET, PyCalendarUTCOffsetValue)
-
+PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_UTC_OFFSET, PyCalendarUTCOffsetValue, xmldefs.value_utc_offset)
Modified: PyCalendar/trunk/src/pycalendar/utils.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/utils.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/utils.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,19 +14,20 @@
# limitations under the License.
##
+from pycalendar.parser import ParserContext
import cStringIO as StringIO
-#from PyCalendarDateTime import PyCalendarDateTime
+def readFoldedLine(ins, lines):
-def readFoldedLine( ins, lines ):
-
# If line2 already has data, transfer that into line1
- if lines[1]:
+ if lines[1] is not None:
lines[0] = lines[1]
else:
# Fill first line
try:
myline = ins.readline()
+ if len(myline) == 0:
+ raise ValueError
if myline[-1] == "\n":
if myline[-2] == "\r":
lines[0] = myline[:-2]
@@ -36,18 +37,19 @@
lines[0] = myline[:-1]
else:
lines[0] = myline
+ except IndexError:
+ lines[0] = ""
except:
- lines[0] = ""
+ lines[0] = None
return False
- if not lines[0]:
- return False
-
# Now loop looking ahead at the next line to see if it is folded
while True:
# Get next line
try:
myline = ins.readline()
+ if len(myline) == 0:
+ raise ValueError
if myline[-1] == "\n":
if myline[-2] == "\r":
lines[1] = myline[:-2]
@@ -57,8 +59,10 @@
lines[1] = myline[:-1]
else:
lines[1] = myline
+ except IndexError:
+ lines[1] = ""
except:
- lines[1] = ""
+ lines[1] = None
return True
if not lines[1]:
@@ -75,20 +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 writeTextValue( os, value ):
+
+
+
+def escapeTextValue(value):
+ os = StringIO.StringIO()
+ writeTextValue(os, value)
+ return os.getvalue()
+
+
+
+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("\\")
@@ -113,10 +128,12 @@
break
else:
os.write(value)
-
+
except:
pass
-
+
+
+
def decodeTextValue(value):
os = StringIO.StringIO()
@@ -139,15 +156,30 @@
os.write('\r')
elif c == 'n':
os.write('\n')
+ elif c == 'N':
+ os.write('\n')
elif c == '':
os.write('')
elif c == '\\':
os.write('\\')
elif c == ',':
os.write(',')
+ elif c == ';':
+ os.write(';')
+ elif c == ':':
+ # ":" escape normally invalid
+ if ParserContext.INVALID_COLON_ESCAPE_SEQUENCE == ParserContext.PARSER_RAISE:
+ raise ValueError
+ elif ParserContext.INVALID_COLON_ESCAPE_SEQUENCE == ParserContext.PARSER_FIX:
+ os.write(':')
- # Bump past escapee and look for next segment (not past the
- # end)
+ # Other escaped chars normally not allowed
+ elif ParserContext.INVALID_ESCAPE_SEQUENCES == ParserContext.PARSER_RAISE:
+ raise ValueError
+ elif ParserContext.INVALID_ESCAPE_SEQUENCES == ParserContext.PARSER_FIX:
+ os.write(c)
+
+ # Bump past escapee and look for next segment (not past the end)
start_pos = end_pos + 1
if start_pos >= size_pos:
break
@@ -162,9 +194,167 @@
return os.getvalue()
+
+
+def encodeParameterValue(value):
+
+ encoded = []
+ last = ''
+ for c in value:
+ if c == '\t':
+ encoded.append('^')
+ encoded.append('t')
+ elif 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):
+
+ if value is None:
+ return None
+ decoded = []
+ last = ''
+ for c in value:
+ if last == '^':
+ if c == 't':
+ decoded.append('\t')
+ elif 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=';'):
+ """
+ Each element of the list has to be separately un-escaped
+ """
+ results = []
+ item = []
+ pre_s = ''
+ for s in data:
+ if s == sep and pre_s != '\\':
+ results.append(decodeTextValue("".join(item)))
+ item = []
+ 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 "")
+
+
+
+def generateTextList(os, data, sep=';'):
+ """
+ Each element of the list must be separately escaped
+ """
+ try:
+ if isinstance(data, basestring):
+ data = (data,)
+ results = [escapeTextValue(value) for value in data]
+ os.write(sep.join(results))
+ except:
+ pass
+
+
+
+# vCard double-nested list parsing/generation
+def parseDoubleNestedList(data, maxsize):
+ results = []
+ items = [""]
+ 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:
+ results.append(decodeTextValue(items[0]))
+ else:
+ results.append("")
+
+ for _ignore in range(maxsize - len(results)):
+ results.append("")
+
+ if len(results) > maxsize:
+ if ParserContext.INVALID_ADR_N_VALUES == ParserContext.PARSER_FIX:
+ results = results[:maxsize]
+ elif ParserContext.INVALID_ADR_N_VALUES == ParserContext.PARSER_RAISE:
+ raise ValueError
+
+ return tuple(results)
+
+
+
+def generateDoubleNestedList(os, data):
+ try:
+ def _writeElement(item):
+ if isinstance(item, basestring):
+ writeTextValue(os, item)
+ else:
+ if item:
+ writeTextValue(os, item[0])
+ 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
@@ -174,8 +364,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
@@ -185,49 +375,67 @@
else:
return days_upto_month[month]
+cachedLeapYears = {}
def isLeapYear(year):
- if year <= 1752:
- return (year % 4 == 0)
- else:
- return ((year % 4 == 0) and (year % 100 != 0)) or (year % 400 == 0)
+ try:
+ return cachedLeapYears[year]
+ except KeyError:
+ if year <= 1752:
+ result = (year % 4 == 0)
+ else:
+ result = ((year % 4 == 0) and (year % 100 != 0)) or (year % 400 == 0)
+ cachedLeapYears[year] = result
+ return result
+
+cachedLeapDaysSince1970 = {}
def leapDaysSince1970(year_offset):
- if year_offset > 2:
- return (year_offset + 1) / 4
- elif year_offset < -1:
- # Python will round down negative numbers (i.e. -5/4 = -2, but we want -1), so
- # what is (year_offset - 2) in C code is actually (year_offset - 2 + 3) in Python.
- return (year_offset + 1) / 4
- else:
- return 0
-# public static int getLocalTimezoneOffsetSeconds() {
-# Calendar rightNow = Calendar.getInstance()
-# int tzoffset = rightNow.get(Calendar.ZONE_OFFSET)
-# int dstoffset = rightNow.get(Calendar.DST_OFFSET)
-# return -(tzoffset + dstoffset) / 1000
-# }
+ try:
+ return cachedLeapDaysSince1970[year_offset]
+ except KeyError:
+ if year_offset > 2:
+ result = (year_offset + 1) / 4
+ elif year_offset < -1:
+ # Python will round down negative numbers (i.e. -5/4 = -2, but we want -1), so
+ # what is (year_offset - 2) in C code is actually (year_offset - 2 + 3) in Python.
+ result = (year_offset + 1) / 4
+ else:
+ result = 0
+ cachedLeapDaysSince1970[year_offset] = result
+ return result
- # Packed date
+
+
+# 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
-
def getMonthTable(month, year, weekstart, table, today_index):
from pycalendar.datetime import PyCalendarDateTime
@@ -250,7 +458,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)
@@ -278,7 +486,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
@@ -293,12 +501,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
@@ -306,4 +516,4 @@
s1 = set(v1)
s2 = set(v2)
s3 = s1.difference(s2)
- return list(s3)
\ No newline at end of file
+ return list(s3)
Modified: PyCalendar/trunk/src/pycalendar/valarm.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/valarm.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/valarm.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,32 +14,49 @@
# limitations under the License.
##
-from attribute import PyCalendarAttribute
-from component import PyCalendarComponent
-from datetime import PyCalendarDateTime
-from duration import PyCalendarDuration
-from property import PyCalendarProperty
-from value import PyCalendarValue
-import definitions
+from pycalendar import definitions
+from pycalendar.attribute import PyCalendarAttribute
+from pycalendar.component import PyCalendarComponent
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.duration import PyCalendarDuration
+from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS
+from pycalendar.property import PyCalendarProperty
+from pycalendar.value import PyCalendarValue
class PyCalendarVAlarm(PyCalendarComponent):
- sBeginDelimiter = definitions.cICalComponent_BEGINVALARM
+ sActionMap = {
+ definitions.cICalProperty_ACTION_AUDIO: definitions.eAction_VAlarm_Audio,
+ definitions.cICalProperty_ACTION_DISPLAY: definitions.eAction_VAlarm_Display,
+ definitions.cICalProperty_ACTION_EMAIL: definitions.eAction_VAlarm_Email,
+ definitions.cICalProperty_ACTION_PROCEDURE: definitions.eAction_VAlarm_Procedure,
+ definitions.cICalProperty_ACTION_URI: definitions.eAction_VAlarm_URI,
+ definitions.cICalProperty_ACTION_NONE: definitions.eAction_VAlarm_None,
+ }
- sEndDelimiter = definitions.cICalComponent_ENDVALARM
+ sActionValueMap = {
+ definitions.eAction_VAlarm_Audio: definitions.cICalProperty_ACTION_AUDIO,
+ definitions.eAction_VAlarm_Display: definitions.cICalProperty_ACTION_DISPLAY,
+ definitions.eAction_VAlarm_Email: definitions.cICalProperty_ACTION_EMAIL,
+ definitions.eAction_VAlarm_Procedure: definitions.cICalProperty_ACTION_PROCEDURE,
+ definitions.eAction_VAlarm_URI: definitions.cICalProperty_ACTION_URI,
+ definitions.eAction_VAlarm_None: definitions.cICalProperty_ACTION_NONE,
+ }
# Classes for each action encapsulating action-specific data
class PyCalendarVAlarmAction(object):
- def __init__(self, type=None, copyit=None):
- if type is not None:
- self.mType = type
- elif copyit is not None:
- self.mType = copyit.mType
+ propertyCardinality_1 = ()
+ propertyCardinality_1_Fix_Empty = ()
+ propertyCardinality_0_1 = ()
+ propertyCardinality_1_More = ()
- def clone_it(self):
- pass
+ def __init__(self, type):
+ self.mType = type
+ def duplicate(self):
+ return PyCalendarVAlarm.PyCalendarVAlarmAction(self.mType)
+
def load(self, valarm):
pass
@@ -52,21 +69,28 @@
def getType(self):
return self.mType
+
class PyCalendarVAlarmAudio(PyCalendarVAlarmAction):
- def __init__(self, speak=None, copyit=None):
- if speak is not None:
- super(PyCalendarVAlarm.PyCalendarVAlarmAudio, self).__init__(type=definitions.eAction_VAlarm_Audio)
- self.mSpeakText = speak
- elif copyit is not None:
- super(PyCalendarVAlarm.PyCalendarVAlarmAudio, self).__init__(copyit=copyit)
- self.mSpeakText = copyit.mSpeakText
- else:
- super(PyCalendarVAlarm.PyCalendarVAlarmAudio, self).__init__(type=definitions.eAction_VAlarm_Audio)
+ propertyCardinality_1 = (
+ definitions.cICalProperty_ACTION,
+ definitions.cICalProperty_TRIGGER,
+ )
- def clone_it(self):
- return PyCalendarVAlarm.PyCalendarVAlarmAudio(self)
+ 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
+
+ def duplicate(self):
+ return PyCalendarVAlarm.PyCalendarVAlarmAudio(self.mSpeakText)
+
def load(self, valarm):
# Get properties
self.mSpeakText = valarm.loadValueString(definitions.cICalProperty_ACTION_X_SPEAKTEXT)
@@ -87,21 +111,31 @@
def getSpeakText(self):
return self.mSpeakText
+
class PyCalendarVAlarmDisplay(PyCalendarVAlarmAction):
- def __init__(self, description=None, copyit=None):
- if description is not None:
- super(PyCalendarVAlarm.PyCalendarVAlarmDisplay, self).__init__(type=definitions.eAction_VAlarm_Display)
- self.mDescription = description
- elif copyit is not None:
- super(PyCalendarVAlarm.PyCalendarVAlarmDisplay, self).__init__(copyit=copyit)
- self.mDescription = copyit.mDescription
- else:
- super(PyCalendarVAlarm.PyCalendarVAlarmDisplay, self).__init__(type=definitions.eAction_VAlarm_Display)
+ propertyCardinality_1 = (
+ definitions.cICalProperty_ACTION,
+ definitions.cICalProperty_TRIGGER,
+ )
- def clone_it(self):
- return PyCalendarVAlarm.PyCalendarVAlarmDisplay(self)
+ propertyCardinality_1_Fix_Empty = (
+ definitions.cICalProperty_DESCRIPTION,
+ )
+ propertyCardinality_0_1 = (
+ definitions.cICalProperty_DURATION,
+ definitions.cICalProperty_REPEAT,
+ definitions.cICalProperty_ACKNOWLEDGED,
+ )
+
+ def __init__(self, description=None):
+ super(PyCalendarVAlarm.PyCalendarVAlarmDisplay, self).__init__(type=definitions.eAction_VAlarm_Display)
+ self.mDescription = description
+
+ def duplicate(self):
+ return PyCalendarVAlarm.PyCalendarVAlarmDisplay(self.mDescription)
+
def load(self, valarm):
# Get properties
self.mDescription = valarm.loadValueString(definitions.cICalProperty_DESCRIPTION)
@@ -119,25 +153,38 @@
def getDescription(self):
return self.mDescription
+
class PyCalendarVAlarmEmail(PyCalendarVAlarmAction):
- def __init__(self, description=None, summary=None, attendees=None, copyit=None):
- if description is not None:
- super(PyCalendarVAlarm.PyCalendarVAlarmEmail, self).__init__(type=definitions.eAction_VAlarm_Display)
- self.mDescription = description
- self.mSummary = summary
- self.mAttendees = attendees
- elif copyit is not None:
- super(PyCalendarVAlarm.PyCalendarVAlarmEmail, self).__init__(copyit=copyit)
- self.mDescription = self.mDescription
- self.mSummary = self.mSummary
- self.mAttendees = copyit.mAttendees[:]
- else:
- super(PyCalendarVAlarm.PyCalendarVAlarmEmail, self).__init__(type=definitions.eAction_VAlarm_Display)
+ propertyCardinality_1 = (
+ definitions.cICalProperty_ACTION,
+ definitions.cICalProperty_TRIGGER,
+ )
- def clone_it(self):
- return PyCalendarVAlarm.PyCalendarVAlarmEmail(self)
+ propertyCardinality_1_Fix_Empty = (
+ definitions.cICalProperty_DESCRIPTION,
+ definitions.cICalProperty_SUMMARY,
+ )
+ propertyCardinality_0_1 = (
+ definitions.cICalProperty_DURATION,
+ definitions.cICalProperty_REPEAT,
+ definitions.cICalProperty_ACKNOWLEDGED,
+ )
+
+ propertyCardinality_1_More = (
+ definitions.cICalProperty_ATTENDEE,
+ )
+
+ def __init__(self, description=None, summary=None, attendees=None):
+ super(PyCalendarVAlarm.PyCalendarVAlarmEmail, self).__init__(type=definitions.eAction_VAlarm_Email)
+ self.mDescription = description
+ self.mSummary = summary
+ self.mAttendees = attendees
+
+ def duplicate(self):
+ return PyCalendarVAlarm.PyCalendarVAlarmEmail(self.mDescription, self.mSummary, self.mAttendees)
+
def load(self, valarm):
# Get properties
self.mDescription = valarm.loadValueString(definitions.cICalProperty_DESCRIPTION)
@@ -150,7 +197,7 @@
for iter in range:
# Get the attendee value
attendee = iter.getCalAddressValue()
- if attendee != None:
+ if attendee is not None:
self.mAttendees.append(attendee.getValue())
def add(self, valarm):
@@ -181,124 +228,180 @@
def getAttendees(self):
return self.mAttendees
+
class PyCalendarVAlarmUnknown(PyCalendarVAlarmAction):
- def __init__(self, copyit=None):
- if copyit is not None:
- super(PyCalendarVAlarm.PyCalendarVAlarmUnknown, self).__init__(copyit=copyit)
- else:
- super(PyCalendarVAlarm.PyCalendarVAlarmUnknown, self).__init__(type=definitions.eAction_VAlarm_Unknown)
+ propertyCardinality_1 = (
+ definitions.cICalProperty_ACTION,
+ definitions.cICalProperty_TRIGGER,
+ )
- def clone_it(self):
- return PyCalendarVAlarm.PyCalendarVAlarmUnknown(self)
+ 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 = (
+ definitions.cICalProperty_ACTION,
+ definitions.cICalProperty_TRIGGER,
+ definitions.cICalProperty_URL,
+ )
+
+ propertyCardinality_0_1 = (
+ definitions.cICalProperty_DURATION,
+ definitions.cICalProperty_REPEAT,
+ definitions.cICalProperty_ACKNOWLEDGED,
+ )
+
+ def __init__(self, uri=None):
+ super(PyCalendarVAlarm.PyCalendarVAlarmURI, self).__init__(type=definitions.eAction_VAlarm_URI)
+ self.mURI = uri
+
+ def duplicate(self):
+ return PyCalendarVAlarm.PyCalendarVAlarmURI(self.mURI)
+
def load(self, valarm):
- pass
+ # Get properties
+ self.mURI = valarm.loadValueString(definitions.cICalProperty_URL)
def add(self, valarm):
- pass
+ # Delete existing then add
+ self.remove(valarm)
+ prop = PyCalendarProperty(definitions.cICalProperty_URL, self.mURI)
+ valarm.addProperty(prop)
+
def remove(self, valarm):
- pass
+ valarm.removeProperties(definitions.cICalProperty_URL)
- @staticmethod
- def getVBegin():
- return PyCalendarVAlarm.sBeginDelimiter
+ def getURI(self):
+ return self.mURI
- @staticmethod
- def getVEnd():
- return PyCalendarVAlarm.sEndDelimiter
+ class PyCalendarVAlarmNone(PyCalendarVAlarmAction):
+
+ propertyCardinality_1 = (
+ definitions.cICalProperty_ACTION,
+ )
+
+ def __init__(self):
+ super(PyCalendarVAlarm.PyCalendarVAlarmNone, self).__init__(type=definitions.eAction_VAlarm_None)
+
+ def duplicate(self):
+ return PyCalendarVAlarm.PyCalendarVAlarmNone()
+
+
def getMimeComponentName(self):
# Cannot be sent as a separate MIME object
return None
- def __init__(self, calendar=None, copyit=None):
-
- if calendar is not None:
- super(PyCalendarVAlarm, self).__init__(calendar=calendar)
-
- self.mAction = definitions.eAction_VAlarm_Display
- self.mTriggerAbsolute = False
- self.mTriggerOnStart = True
- self.mTriggerOn = PyCalendarDateTime()
-
- # Set duration default to 1 hour
- self.mTriggerBy = PyCalendarDuration()
- self.mTriggerBy.setDuration(60 * 60)
-
- # Does not repeat by default
- self.mRepeats = 0
- self.mRepeatInterval = PyCalendarDuration()
- self.mRepeatInterval.setDuration(5 * 60) # Five minutes
-
- # Status
- self.mStatusInit = False
- self.mAlarmStatus = definitions.eAlarm_Status_Pending
- self.mLastTrigger = PyCalendarDateTime()
- self.mNextTrigger = PyCalendarDateTime()
- self.mDoneCount = 0
-
- # Create action data
- self.mActionData = PyCalendarVAlarm.PyCalendarVAlarmDisplay("")
+ sActionToAlarmMap = {
+ definitions.eAction_VAlarm_Audio: PyCalendarVAlarmAudio,
+ definitions.eAction_VAlarm_Display: PyCalendarVAlarmDisplay,
+ definitions.eAction_VAlarm_Email: PyCalendarVAlarmEmail,
+ definitions.eAction_VAlarm_URI: PyCalendarVAlarmURI,
+ definitions.eAction_VAlarm_None: PyCalendarVAlarmNone,
+ }
- elif copyit is not None:
- super(PyCalendarVAlarm, self).__init__(copyit=copyit)
-
- self.mAction = copyit.mAction
- self.mTriggerAbsolute = copyit.mTriggerAbsolute
- self.mTriggerOn = PyCalendarDateTime(copyit.mTriggerOn)
- self.mTriggerBy = PyCalendarDuration(copyit.mTriggerBy)
- self.mTriggerOnStart = copyit.mTriggerOnStart
-
- self.mRepeats = copyit.mRepeats
- self.mRepeatInterval = PyCalendarDuration(copyit.mRepeatInterval)
-
- self.mAlarmStatus = copyit.mAlarmStatus
- if copyit.mLastTrigger is not None:
- self.mLastTrigger = PyCalendarDateTime(copyit.mLastTrigger)
- if copyit.mNextTrigger is not None:
- self.mNextTrigger = PyCalendarDateTime(copyit.mNextTrigger)
- self.mDoneCount = copyit.mDoneCount
-
- self.mActionData = copyit.mActionData.clone_it()
+ propertyValueChecks = ICALENDAR_VALUE_CHECKS
- def clone_it(self):
- return PyCalendarVAlarm(self)
+ def __init__(self, parent=None):
+ super(PyCalendarVAlarm, self).__init__(parent=parent)
+
+ self.mAction = definitions.eAction_VAlarm_Display
+ self.mTriggerAbsolute = False
+ self.mTriggerOnStart = True
+ self.mTriggerOn = PyCalendarDateTime()
+
+ # Set duration default to 1 hour
+ self.mTriggerBy = PyCalendarDuration()
+ self.mTriggerBy.setDuration(60 * 60)
+
+ # Does not repeat by default
+ self.mRepeats = 0
+ self.mRepeatInterval = PyCalendarDuration()
+ self.mRepeatInterval.setDuration(5 * 60) # Five minutes
+
+ # Status
+ self.mStatusInit = False
+ self.mAlarmStatus = definitions.eAlarm_Status_Pending
+ self.mLastTrigger = PyCalendarDateTime()
+ self.mNextTrigger = PyCalendarDateTime()
+ self.mDoneCount = 0
+
+ # Create action data
+ self.mActionData = PyCalendarVAlarm.PyCalendarVAlarmDisplay("")
+
+
+ def duplicate(self, parent=None):
+ other = super(PyCalendarVAlarm, self).duplicate(parent=parent)
+ other.mAction = self.mAction
+ other.mTriggerAbsolute = self.mTriggerAbsolute
+ other.mTriggerOn = self.mTriggerOn.duplicate()
+ other.mTriggerBy = self.mTriggerBy.duplicate()
+ other.mTriggerOnStart = self.mTriggerOnStart
+
+ other.mRepeats = self.mRepeats
+ other.mRepeatInterval = self.mRepeatInterval.duplicate()
+
+ other.mAlarmStatus = self.mAlarmStatus
+ if self.mLastTrigger is not None:
+ other.mLastTrigger = self.mLastTrigger.duplicate()
+ if self.mNextTrigger is not None:
+ other.mNextTrigger = self.mNextTrigger.duplicate()
+ other.mDoneCount = self.mDoneCount
+
+ other.mActionData = self.mActionData.duplicate()
+ return other
+
+
def getType(self):
- return PyCalendarComponent.eVALARM
+ return definitions.cICalComponent_VALARM
- def getBeginDelimiter(self):
- return PyCalendarVAlarm.sBeginDelimiter
- def getEndDelimiter(self):
- return PyCalendarVAlarm.sEndDelimiter
-
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)
@@ -306,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)
@@ -313,6 +417,7 @@
# Do inherited
super(PyCalendarVAlarm, self).removed()
+
def changed(self):
# Always force recalc of trigger status
self.mStatusInit = False
@@ -324,6 +429,7 @@
# do top-level component changes
# super.changed()
+
def finalise(self):
# Do inherited
super(PyCalendarVAlarm, self).finalise()
@@ -331,17 +437,7 @@
# Get the ACTION
temp = self.loadValueString(definitions.cICalProperty_ACTION)
if temp is not None:
- if temp == definitions.cICalProperty_ACTION_AUDIO:
- self.mAction = definitions.eAction_VAlarm_Audio
- elif temp == definitions.cICalProperty_ACTION_DISPLAY:
- self.mAction = definitions.eAction_VAlarm_Display
- elif temp == definitions.cICalProperty_ACTION_EMAIL:
- self.mAction = definitions.eAction_VAlarm_Email
- elif temp == definitions.cICalProperty_ACTION_PROCEDURE:
- self.mAction = definitions.eAction_VAlarm_Procedure
- else:
- self.mAction = definitions.eAction_VAlarm_Unknown
-
+ self.mAction = PyCalendarVAlarm.sActionMap.get(temp, definitions.eAction_VAlarm_Unknown)
self.loadAction()
# Get the trigger
@@ -377,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:
@@ -394,6 +493,35 @@
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.
+ """
+
+ # Validate using action specific constraints
+ self.propertyCardinality_1 = self.mActionData.propertyCardinality_1
+ self.propertyCardinality_1_Fix_Empty = self.mActionData.propertyCardinality_1_Fix_Empty
+ self.propertyCardinality_0_1 = self.mActionData.propertyCardinality_0_1
+ self.propertyCardinality_1_More = self.mActionData.propertyCardinality_1_More
+
+ fixed, unfixed = super(PyCalendarVAlarm, self).validate(doFix)
+
+ # Extra constraint: both DURATION and REPEAT must be present togethe
+ if self.hasProperty(definitions.cICalProperty_DURATION) ^ self.hasProperty(definitions.cICalProperty_REPEAT):
+ # Cannot fix this
+ logProblem = "[%s] Properties must be present together: %s, %s" % (
+ self.getType(),
+ definitions.cICalProperty_DURATION,
+ definitions.cICalProperty_REPEAT,
+ )
+ unfixed.append(logProblem)
+
+ return fixed, unfixed
+
+
def editStatus(self, status):
# Remove existing
self.removeProperties(definitions.cICalProperty_ALARM_X_ALARMSTATUS)
@@ -411,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)
@@ -422,21 +551,14 @@
self.mActionData = data
# Add new properties to alarm
- action_txt = ""
- if self.mAction == definitions.eAction_VAlarm_Audio:
- action_txt = definitions.cICalProperty_ACTION_AUDIO
- elif self.mAction == definitions.eAction_VAlarm_Display:
- action_txt = definitions.cICalProperty_ACTION_DISPLAY
- elif self.mAction == definitions.eAction_VAlarm_Email:
- action_txt = definitions.cICalProperty_ACTION_EMAIL
- else:
- action_txt = definitions.cICalProperty_ACTION_PROCEDURE
+ action_txt = PyCalendarVAlarm.sActionValueMap.get(self.mAction, definitions.cICalProperty_ACTION_PROCEDURE)
prop = PyCalendarProperty(definitions.cICalProperty_ACTION, action_txt)
self.addProperty(prop)
self.mActionData.add(self)
+
def editTriggerOn(self, dt):
# Remove existing
self.removeProperties(definitions.cICalProperty_TRIGGER)
@@ -449,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)
@@ -466,6 +589,7 @@
prop.addAttribute(attr)
self.addProperty(prop)
+
def editRepeats(self, repeat, interval):
# Remove existing
self.removeProperties(definitions.cICalProperty_REPEAT)
@@ -480,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)
@@ -518,22 +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
- if self.mAction == definitions.eAction_VAlarm_Audio:
- self.mActionData = PyCalendarVAlarm.PyCalendarVAlarmAudio()
- self.mActionData.load(self)
- elif self.mAction == definitions.eAction_VAlarm_Display:
- self.mActionData = PyCalendarVAlarm.PyCalendarVAlarmDisplay()
- self.mActionData.load(self)
- elif self.mAction == definitions.eAction_VAlarm_Email:
- self.mActionData = PyCalendarVAlarm.PyCalendarVAlarmEmail()
- self.mActionData.load(self)
- else:
- self.mActionData = PyCalendarVAlarm.PyCalendarVAlarmUnknown()
- self.mActionData.load(self)
+ 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:
@@ -569,6 +688,7 @@
self.mNextTrigger = trigger
+
def getFirstTrigger(self, dt):
# If absolute trigger, use that
if self.isTriggerAbsolute():
Copied: PyCalendar/trunk/src/pycalendar/validation.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/validation.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/validation.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/validation.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,75 @@
+##
+# 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.
+##
+
+from pycalendar.plaintextvalue import PyCalendarPlainTextValue
+
+# Grabbed from http://docs.python.org/library/functools.html since we need to support Python 2.5
+def partial(func, *args, **keywords):
+ def newfunc(*fargs, **fkeywords):
+ newkeywords = keywords.copy()
+ newkeywords.update(fkeywords)
+ return func(*(args + fargs), **newkeywords)
+ newfunc.func = func
+ newfunc.args = args
+ newfunc.keywords = keywords
+ return newfunc
+
+
+
+class PropertyValueChecks(object):
+
+ @staticmethod
+ def stringValue(text, property):
+
+ value = property.getValue()
+ 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
Modified: PyCalendar/trunk/src/pycalendar/value.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/value.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/value.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -16,32 +16,62 @@
# ICalendar Value class
-class PyCalendarValue(object):
+from pycalendar.valueutils import ValueMixin
+from pycalendar import xmldefs
+import xml.etree.cElementTree as XML
- VALUETYPE_BINARY = 0
- VALUETYPE_BOOLEAN = 1
- VALUETYPE_CALADDRESS = 2
- VALUETYPE_DATE = 3
- VALUETYPE_DATETIME = 4
- VALUETYPE_DURATION = 5
- VALUETYPE_FLOAT = 6
- VALUETYPE_GEO = 7
- VALUETYPE_INTEGER = 8
- VALUETYPE_PERIOD = 9
- VALUETYPE_RECUR = 10
- VALUETYPE_TEXT = 11
- VALUETYPE_TIME = 12
- VALUETYPE_URI = 13
- VALUETYPE_UTC_OFFSET = 14
- VALUETYPE_MULTIVALUE = 15
- VALUETYPE_XNAME = 16
-
+class PyCalendarValue(ValueMixin):
+
+ (
+ VALUETYPE_ADR,
+ VALUETYPE_BINARY,
+ VALUETYPE_BOOLEAN,
+ VALUETYPE_CALADDRESS,
+ VALUETYPE_DATE,
+ VALUETYPE_DATETIME,
+ VALUETYPE_DURATION,
+ VALUETYPE_FLOAT,
+ VALUETYPE_GEO,
+ VALUETYPE_INTEGER,
+ VALUETYPE_N,
+ VALUETYPE_ORG,
+ VALUETYPE_PERIOD,
+ VALUETYPE_RECUR,
+ VALUETYPE_REQUEST_STATUS,
+ VALUETYPE_TEXT,
+ VALUETYPE_TIME,
+ VALUETYPE_UNKNOWN,
+ VALUETYPE_URI,
+ VALUETYPE_UTC_OFFSET,
+ VALUETYPE_VCARD,
+ VALUETYPE_MULTIVALUE,
+ VALUETYPE_XNAME,
+ ) = range(23)
+
_typeMap = {}
-
+ _xmlMap = {}
+
+
+ def __hash__(self):
+ return hash((self.getType(), self.getValue()))
+
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+
+ def __eq__(self, other):
+ if not isinstance(other, PyCalendarValue):
+ return False
+ return self.getType() == other.getType() and self.getValue() == other.getValue()
+
+
@classmethod
- def registerType(clz, type, cls):
+ def registerType(clz, type, cls, xmlNode):
clz._typeMap[type] = cls
-
+ clz._xmlMap[type] = xmlNode
+
+
@classmethod
def createFromType(clz, type):
# Create the type
@@ -49,21 +79,28 @@
if created:
return created()
else:
- return clz._typeMap.get("DUMMY")(type)
-
- def copy(self):
- classType = PyCalendarValue._typeMap.get(self.getRealType(), None)
- return classType(copyit=self)
+ return clz._typeMap.get(PyCalendarValue.VALUETYPE_UNKNOWN)(type)
+
def getType(self):
- raise NotImplemented
+ raise NotImplementedError
+
def getRealType(self):
return self.getType()
- def parse(self, data):
- raise NotImplemented
-
- def generate(self, os):
- raise NotImplemented
-
\ No newline at end of file
+
+ def getValue(self):
+ raise NotImplementedError
+
+
+ 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()]))
Copied: PyCalendar/trunk/src/pycalendar/valueutils.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/valueutils.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/valueutils.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/valueutils.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,49 @@
+##
+# 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.
+##
+
+# Helpers for value classes
+
+from cStringIO import StringIO
+
+class ValueMixin(object):
+
+ 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
Copied: PyCalendar/trunk/src/pycalendar/vavailability.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/vavailability.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/vavailability.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/vavailability.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,107 @@
+##
+# 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.
+##
+
+from pycalendar import definitions
+from pycalendar import itipdefinitions
+from pycalendar.component import PyCalendarComponent
+from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS
+
+class PyCalendarVAvailability(PyCalendarComponent):
+
+ propertyCardinality_1 = (
+ definitions.cICalProperty_DTSTAMP,
+ definitions.cICalProperty_UID,
+ )
+
+ propertyCardinality_0_1 = (
+ definitions.cICalProperty_BUSYTYPE,
+ definitions.cICalProperty_CLASS,
+ definitions.cICalProperty_CREATED,
+ definitions.cICalProperty_DESCRIPTION,
+ definitions.cICalProperty_DTSTART,
+ definitions.cICalProperty_LAST_MODIFIED,
+ definitions.cICalProperty_ORGANIZER,
+ definitions.cICalProperty_SEQUENCE,
+ definitions.cICalProperty_SUMMARY,
+ definitions.cICalProperty_URL,
+ definitions.cICalProperty_RECURRENCE_ID,
+ definitions.cICalProperty_DTEND,
+ definitions.cICalProperty_DURATION,
+ )
+
+ propertyValueChecks = ICALENDAR_VALUE_CHECKS
+
+ 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.
+ """
+
+ fixed, unfixed = super(PyCalendarVAvailability, self).validate(doFix)
+
+ # 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
+ logProblem = "[%s] Properties must not both be present: %s, %s" % (
+ self.getType(),
+ definitions.cICalProperty_DTEND,
+ definitions.cICalProperty_DURATION,
+ )
+ if doFix:
+ self.removeProperties(definitions.cICalProperty_DTEND)
+ 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:
+ super(PyCalendarVAvailability, self).addComponent(comp)
+ else:
+ raise ValueError
+
+
+ def sortedPropertyKeyOrder(self):
+ return (
+ definitions.cICalProperty_UID,
+ definitions.cICalProperty_DTSTART,
+ definitions.cICalProperty_DURATION,
+ definitions.cICalProperty_DTEND,
+ )
Deleted: PyCalendar/trunk/src/pycalendar/vcard/__init__.py
===================================================================
--- PyCalendar/branches/server/src/pycalendar/vcard/__init__.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vcard/__init__.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,15 +0,0 @@
-#
-# 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.
-##
Copied: PyCalendar/trunk/src/pycalendar/vcard/__init__.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/vcard/__init__.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/vcard/__init__.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/vcard/__init__.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,15 @@
+#
+# 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.
+##
Deleted: PyCalendar/trunk/src/pycalendar/vcard/card.py
===================================================================
--- PyCalendar/branches/server/src/pycalendar/vcard/card.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vcard/card.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,285 +0,0 @@
-##
-# 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.
-##
-
-from cStringIO import StringIO
-from pycalendar.componentbase import PyCalendarComponentBase
-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, \
- Property_PRODID, Property_UID
-from pycalendar.vcard.property import Property
-from pycalendar.vcard.validation import VCARD_VALUE_CHECKS
-
-class Card(PyCalendarComponentBase):
-
- sProdID = "-//mulberrymail.com//Mulberry v4.0//EN"
- sDomain = "mulberrymail.com"
-
- @staticmethod
- def setPRODID(prodid):
- Card.sProdID = prodid
-
-
- @staticmethod
- def setDomain(domain):
- Card.sDomain = domain
-
- propertyCardinality_1 = (
- definitions.Property_VERSION,
- definitions.Property_N,
- )
-
- propertyCardinality_0_1 = (
- definitions.Property_BDAY,
- definitions.Property_PRODID,
- definitions.Property_REV,
- definitions.Property_UID,
- )
-
- propertyCardinality_1_More = (
- definitions.Property_FN,
- )
-
- propertyValueChecks = VCARD_VALUE_CHECKS
-
- def __init__(self, add_defaults=True):
- super(Card, self).__init__()
-
- 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
- a tuple containing two lists: the first describes problems that were fixed, the
- 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,
- Property_PRODID,
- 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:
-
- # Look for start
- if line == card.getBeginDelimiter():
- # Next state
- state = GET_PROPERTY
-
- # Handle blank line
- elif len(line) == 0:
- # 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)
-
- elif state == GET_PROPERTY:
-
- # Look for end of object
- if line == card.getEndDelimiter():
-
- # Finalise the current calendar
- card.finalise()
-
- # Validate some things
- if not card.hasProperty("VERSION"):
- raise PyCalendarInvalidData("vCard missing VERSION", "")
-
- results.append(card)
-
- # Change state
- card = Card(add_defaults=False)
- state = LOOK_FOR_VCARD
-
- # Blank line
- elif len(line) == 0:
- # Raise if requested, otherwise just ignore
- if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
- raise PyCalendarInvalidData("vCard data has blank lines")
-
- # Must be a property
- else:
-
- # Parse attribute/value for top-level calendar item
- prop = Property()
- if prop.parse(line):
-
- # Check for valid property
- if not card.validProperty(prop):
- 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:
-
- # Look for start
- if line == self.getBeginDelimiter():
- # Next state
- state = GET_PROPERTY
-
- # Indicate success at this point
- result = True
-
- # Handle blank line
- elif len(line) == 0:
- # 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)
-
- elif state == GET_PROPERTY:
-
- # Look for end of object
- if line == self.getEndDelimiter():
-
- # Finalise the current calendar
- self.finalise()
-
- # Change state
- state = LOOK_FOR_VCARD
-
- # Blank line
- elif len(line) == 0:
- # Raise if requested, otherwise just ignore
- if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
- raise PyCalendarInvalidData("vCard data has blank lines")
-
- # Must be a property
- else:
-
- # Parse attribute/value for top-level calendar item
- prop = Property()
- try:
- if prop.parse(line):
-
- # Check for valid property
- if not self.validProperty(prop):
- raise PyCalendarInvalidData("Invalid property", str(prop))
- else:
- 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:
-
- tvalue = prop.getValue()
- if ((tvalue is None) or (tvalue.getValue() != "3.0")):
- return False
-
- return True
Copied: PyCalendar/trunk/src/pycalendar/vcard/card.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/vcard/card.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/vcard/card.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/vcard/card.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,285 @@
+##
+# 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.
+##
+
+from cStringIO import StringIO
+from pycalendar.componentbase import PyCalendarComponentBase
+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, \
+ Property_PRODID, Property_UID
+from pycalendar.vcard.property import Property
+from pycalendar.vcard.validation import VCARD_VALUE_CHECKS
+
+class Card(PyCalendarComponentBase):
+
+ sProdID = "-//mulberrymail.com//Mulberry v4.0//EN"
+ sDomain = "mulberrymail.com"
+
+ @staticmethod
+ def setPRODID(prodid):
+ Card.sProdID = prodid
+
+
+ @staticmethod
+ def setDomain(domain):
+ Card.sDomain = domain
+
+ propertyCardinality_1 = (
+ definitions.Property_VERSION,
+ definitions.Property_N,
+ )
+
+ propertyCardinality_0_1 = (
+ definitions.Property_BDAY,
+ definitions.Property_PRODID,
+ definitions.Property_REV,
+ definitions.Property_UID,
+ )
+
+ propertyCardinality_1_More = (
+ definitions.Property_FN,
+ )
+
+ propertyValueChecks = VCARD_VALUE_CHECKS
+
+ def __init__(self, add_defaults=True):
+ super(Card, self).__init__()
+
+ 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
+ a tuple containing two lists: the first describes problems that were fixed, the
+ 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,
+ Property_PRODID,
+ 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:
+
+ # Look for start
+ if line == card.getBeginDelimiter():
+ # Next state
+ state = GET_PROPERTY
+
+ # Handle blank line
+ elif len(line) == 0:
+ # 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)
+
+ elif state == GET_PROPERTY:
+
+ # Look for end of object
+ if line == card.getEndDelimiter():
+
+ # Finalise the current calendar
+ card.finalise()
+
+ # Validate some things
+ if not card.hasProperty("VERSION"):
+ raise PyCalendarInvalidData("vCard missing VERSION", "")
+
+ results.append(card)
+
+ # Change state
+ card = Card(add_defaults=False)
+ state = LOOK_FOR_VCARD
+
+ # Blank line
+ elif len(line) == 0:
+ # Raise if requested, otherwise just ignore
+ if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
+ raise PyCalendarInvalidData("vCard data has blank lines")
+
+ # Must be a property
+ else:
+
+ # Parse attribute/value for top-level calendar item
+ prop = Property()
+ if prop.parse(line):
+
+ # Check for valid property
+ if not card.validProperty(prop):
+ 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:
+
+ # Look for start
+ if line == self.getBeginDelimiter():
+ # Next state
+ state = GET_PROPERTY
+
+ # Indicate success at this point
+ result = True
+
+ # Handle blank line
+ elif len(line) == 0:
+ # 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)
+
+ elif state == GET_PROPERTY:
+
+ # Look for end of object
+ if line == self.getEndDelimiter():
+
+ # Finalise the current calendar
+ self.finalise()
+
+ # Change state
+ state = LOOK_FOR_VCARD
+
+ # Blank line
+ elif len(line) == 0:
+ # Raise if requested, otherwise just ignore
+ if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
+ raise PyCalendarInvalidData("vCard data has blank lines")
+
+ # Must be a property
+ else:
+
+ # Parse attribute/value for top-level calendar item
+ prop = Property()
+ try:
+ if prop.parse(line):
+
+ # Check for valid property
+ if not self.validProperty(prop):
+ raise PyCalendarInvalidData("Invalid property", str(prop))
+ else:
+ 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:
+
+ tvalue = prop.getValue()
+ if ((tvalue is None) or (tvalue.getValue() != "3.0")):
+ return False
+
+ return True
Deleted: PyCalendar/trunk/src/pycalendar/vcard/definitions.py
===================================================================
--- PyCalendar/branches/server/src/pycalendar/vcard/definitions.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vcard/definitions.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,88 +0,0 @@
-##
-# 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.
-##
-
-# 2426 Component
-
-VCARD = "VCARD"
-
-# 2425 Properties
-Property_SOURCE = "SOURCE"
-Property_NAME = "NAME"
-Property_PROFILE = "PROFILE"
-
-# 2426 vCard Properties
-
-# 2426 Section 3.1
-Property_FN = "FN"
-Property_N = "N"
-Property_NICKNAME = "NICKNAME"
-Property_PHOTO = "PHOTO"
-Property_BDAY = "BDAY"
-
-# 2426 Section 3.2
-Property_ADR = "ADR"
-Property_LABEL = "LABEL"
-
-# 2426 Section 3.3
-Property_TEL = "TEL"
-Property_EMAIL = "EMAIL"
-Property_MAILER = "MAILER"
-
-# 2426 Section 3.4
-Property_TZ = "TZ"
-Property_GEO = "GEO"
-
-# 2426 Section 3.5
-Property_TITLE = "TITLE"
-Property_ROLE = "ROLE"
-Property_LOGO = "LOGO"
-Property_AGENT = "AGENT"
-Property_ORG = "ORG"
-
-# 2426 Section 3.6
-Property_CATEGORIES = "CATEGORIES"
-Property_NOTE = "NOTE"
-Property_PRODID = "PRODID"
-Property_REV = "REV"
-Property_SORT_STRING = "SORT-STRING"
-Property_SOUND = "SOUND"
-Property_UID = "UID"
-Property_URL = "URL"
-Property_VERSION = "VERSION"
-
-# 2426 Section 3.7
-Property_CLASS = "CLASS"
-Property_KEY = "KEY"
-
-# 2426 Value Types
-Value_BINARY = "BINARY"
-Value_BOOLEAN = "BOOLEAN"
-Value_DATE = "DATE"
-Value_DATE_TIME = "DATE-TIME"
-Value_FLOAT = "FLOAT"
-Value_INTEGER = "INTEGER"
-Value_TEXT = "TEXT"
-Value_TIME = "TIME"
-Value_URI = "URI"
-Value_UTCOFFSET = "UTCOFFSET"
-Value_VCARD = "VCARD"
-
-Parameter_ENCODING = "ENCODING"
-Parameter_LANGUAGE = "LANGUAGE"
-Parameter_TYPE = "TYPE"
-Parameter_VALUE = "VALUE"
-
-Parameter_Value_ENCODING_B = "B"
Copied: PyCalendar/trunk/src/pycalendar/vcard/definitions.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/vcard/definitions.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/vcard/definitions.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/vcard/definitions.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,88 @@
+##
+# 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.
+##
+
+# 2426 Component
+
+VCARD = "VCARD"
+
+# 2425 Properties
+Property_SOURCE = "SOURCE"
+Property_NAME = "NAME"
+Property_PROFILE = "PROFILE"
+
+# 2426 vCard Properties
+
+# 2426 Section 3.1
+Property_FN = "FN"
+Property_N = "N"
+Property_NICKNAME = "NICKNAME"
+Property_PHOTO = "PHOTO"
+Property_BDAY = "BDAY"
+
+# 2426 Section 3.2
+Property_ADR = "ADR"
+Property_LABEL = "LABEL"
+
+# 2426 Section 3.3
+Property_TEL = "TEL"
+Property_EMAIL = "EMAIL"
+Property_MAILER = "MAILER"
+
+# 2426 Section 3.4
+Property_TZ = "TZ"
+Property_GEO = "GEO"
+
+# 2426 Section 3.5
+Property_TITLE = "TITLE"
+Property_ROLE = "ROLE"
+Property_LOGO = "LOGO"
+Property_AGENT = "AGENT"
+Property_ORG = "ORG"
+
+# 2426 Section 3.6
+Property_CATEGORIES = "CATEGORIES"
+Property_NOTE = "NOTE"
+Property_PRODID = "PRODID"
+Property_REV = "REV"
+Property_SORT_STRING = "SORT-STRING"
+Property_SOUND = "SOUND"
+Property_UID = "UID"
+Property_URL = "URL"
+Property_VERSION = "VERSION"
+
+# 2426 Section 3.7
+Property_CLASS = "CLASS"
+Property_KEY = "KEY"
+
+# 2426 Value Types
+Value_BINARY = "BINARY"
+Value_BOOLEAN = "BOOLEAN"
+Value_DATE = "DATE"
+Value_DATE_TIME = "DATE-TIME"
+Value_FLOAT = "FLOAT"
+Value_INTEGER = "INTEGER"
+Value_TEXT = "TEXT"
+Value_TIME = "TIME"
+Value_URI = "URI"
+Value_UTCOFFSET = "UTCOFFSET"
+Value_VCARD = "VCARD"
+
+Parameter_ENCODING = "ENCODING"
+Parameter_LANGUAGE = "LANGUAGE"
+Parameter_TYPE = "TYPE"
+Parameter_VALUE = "VALUE"
+
+Parameter_Value_ENCODING_B = "B"
Deleted: PyCalendar/trunk/src/pycalendar/vcard/property.py
===================================================================
--- PyCalendar/branches/server/src/pycalendar/vcard/property.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vcard/property.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,573 +0,0 @@
-##
-# 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.
-##
-
-from pycalendar import stringutils
-from pycalendar.adr import Adr
-from pycalendar.adrvalue import AdrValue
-from pycalendar.attribute import PyCalendarAttribute
-from pycalendar.datetime import PyCalendarDateTime
-from pycalendar.datetimevalue import PyCalendarDateTimeValue
-from pycalendar.exceptions import PyCalendarInvalidProperty
-from pycalendar.integervalue import PyCalendarIntegerValue
-from pycalendar.multivalue import PyCalendarMultiValue
-from pycalendar.n import N
-from pycalendar.nvalue import NValue
-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
-
-handleOptions = ("allow", "ignore", "fix", "raise")
-missingParameterValues = "fix"
-
-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,
- definitions.Property_PRODID : PyCalendarValue.VALUETYPE_TEXT,
- definitions.Property_REV : PyCalendarValue.VALUETYPE_DATETIME,
- definitions.Property_SORT_STRING : PyCalendarValue.VALUETYPE_TEXT,
- definitions.Property_SOUND : PyCalendarValue.VALUETYPE_BINARY,
- 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,
- definitions.Value_DATE : PyCalendarValue.VALUETYPE_DATE,
- definitions.Value_DATE_TIME : PyCalendarValue.VALUETYPE_DATETIME,
- definitions.Value_FLOAT : PyCalendarValue.VALUETYPE_FLOAT,
- definitions.Value_INTEGER : PyCalendarValue.VALUETYPE_INTEGER,
- definitions.Value_TEXT : PyCalendarValue.VALUETYPE_TEXT,
- definitions.Value_TIME : PyCalendarValue.VALUETYPE_TIME,
- definitions.Value_URI : PyCalendarValue.VALUETYPE_URI,
- definitions.Value_UTCOFFSET : PyCalendarValue.VALUETYPE_UTC_OFFSET,
- definitions.Value_VCARD : PyCalendarValue.VALUETYPE_VCARD,
- }
-
- sTypeValueMap = {
- PyCalendarValue.VALUETYPE_ADR : definitions.Value_TEXT,
- PyCalendarValue.VALUETYPE_BINARY : definitions.Value_BINARY,
- PyCalendarValue.VALUETYPE_BOOLEAN : definitions.Value_BOOLEAN,
- PyCalendarValue.VALUETYPE_DATE : definitions.Value_DATE,
- PyCalendarValue.VALUETYPE_DATETIME : definitions.Value_DATE_TIME,
- PyCalendarValue.VALUETYPE_FLOAT : definitions.Value_FLOAT,
- PyCalendarValue.VALUETYPE_GEO : definitions.Value_FLOAT,
- PyCalendarValue.VALUETYPE_INTEGER : definitions.Value_INTEGER,
- PyCalendarValue.VALUETYPE_N : definitions.Value_TEXT,
- PyCalendarValue.VALUETYPE_ORG : definitions.Value_TEXT,
- PyCalendarValue.VALUETYPE_TEXT : definitions.Value_TEXT,
- PyCalendarValue.VALUETYPE_TIME : definitions.Value_TIME,
- PyCalendarValue.VALUETYPE_URI : definitions.Value_URI,
- PyCalendarValue.VALUETYPE_UTC_OFFSET : definitions.Value_UTCOFFSET,
- PyCalendarValue.VALUETYPE_VCARD : definitions.Value_VCARD,
- }
-
- sMultiValues = set((
- definitions.Property_NICKNAME,
- definitions.Property_CATEGORIES,
- ))
-
- sTextVariants = set((
- definitions.Property_ADR,
- definitions.Property_N,
- definitions.Property_ORG,
- ))
-
- def __init__(self, group=None, name=None, value=None, valuetype=None):
- self._init_PyCalendarProperty()
-
- self.mGroup = group
-
- self.mName = name if name is not None else ""
-
- if isinstance(value, int):
- self._init_attr_value_int(value)
-
- elif isinstance(value, str):
- self._init_attr_value_text(value, valuetype if valuetype else Property.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarValue.VALUETYPE_TEXT))
-
- elif isinstance(value, PyCalendarDateTime):
- self._init_attr_value_datetime(value)
-
- elif isinstance(value, Adr):
- self._init_attr_value_adr(value)
-
- elif isinstance(value, N):
- self._init_attr_value_n(value)
-
- elif isinstance(value, list) or isinstance(value, tuple):
- if name.upper() == definitions.Property_ORG:
- self._init_attr_value_org(value)
- elif name.upper() == definitions.Property_GEO:
- self._init_attr_value_geo(value)
- else:
- # Assume everything else is a text list
- self._init_attr_value_text_list(value)
-
- 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():
- other.mAttributes[attrname] = [i.duplicate() for i in attrs]
- other.mValue = self.mValue.duplicate()
-
- return other
-
-
- def __hash__(self):
- return hash((
- self.mName,
- tuple([tuple(self.mAttributes[attrname]) for attrname in sorted(self.mAttributes.keys())]),
- self.mValue,
- ))
-
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
-
- def __eq__(self, other):
- if not isinstance(other, Property):
- return False
- return (
- self.mGroup == self.mGroup and
- self.mName == other.mName and
- self.mValue == other.mValue and
- 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()])
-
-
- def hasAttribute(self, attr):
- 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 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, ";:")
- if not prop_name:
- raise PyCalendarInvalidProperty("Invalid property", data)
-
- # Check for group prefix
- splits = prop_name.split(".", 1)
- if len(splits) == 2:
- # We have both group and name
- self.mGroup = splits[0]
- self.mName = splits[1]
- 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:
- raise PyCalendarInvalidProperty("Invalid property parameter", data)
- elif ParserContext.VCARD_2_NO_PARAMETER_VALUES == ParserContext.PARSER_ALLOW:
- attribute_value = None
- else: # PARSER_IGNORE and PARSER_FIX
- attribute_name = None
-
- if attribute_name.upper() == "BASE64" and ParserContext.VCARD_2_BASE64 == ParserContext.PARSER_FIX:
- attribute_name = definitions.Parameter_ENCODING
- attribute_value = definitions.Parameter_Value_ENCODING_B
- stripValueSpaces = True
- else:
- txt = txt[1:]
- attribute_value, txt = stringutils.strduptokenstr(txt, ":;,")
- if attribute_value is None:
- raise PyCalendarInvalidProperty("Invalid property", data)
-
- # Now add attribute value (decode ^-escaping)
- if attribute_name is not None:
- 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(decodeParameterValue(attribute_value2))
- elif txt[0] == ':':
- txt = txt[1:]
- if stripValueSpaces:
- txt = txt.replace(" ", "")
- self.createValue(txt)
- txt = None
-
- 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
-
-
- def getText(self):
- os = StringIO.StringIO()
- self.generate(os)
- return os.getvalue()
-
-
- def generate(self, os):
-
- # Write it out always with value
- self.generateValue(os, False)
-
-
- 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):
-
- self.setupValueAttribute()
-
- # Must write to temp buffer and then wrap
- sout = StringIO.StringIO()
- if self.mGroup:
- sout.write(self.mGroup + ".")
- sout.write(self.mName)
-
- # Write all attributes
- for key in sorted(self.mAttributes.keys()):
- for attr in self.mAttributes[key]:
- sout.write(";")
- attr.generate(sout)
-
- # Write value
- sout.write(":")
- 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("\r\n")
- value_len -= 72
- offset += 72
- sout.write(" ")
- sout.write(value[offset:])
- os.write(sout.getvalue())
- 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)
- else:
- # Look for valid utf8 range and write that out
- start = 0
- written = 0
- lineWrap = 74
- while written < len(temp):
- # Start 74 chars on from where we are
- offset = start + lineWrap
- if offset >= len(temp):
- line = temp[start:]
- os.write(line)
- written = len(temp)
- else:
- # Check whether next char is valid utf8 lead byte
- 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
-
- # Get value type from property name
- valueType = Property.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarValue.VALUETYPE_TEXT)
-
- # Check whether custom value is set
- 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)
-
- # Check for multivalued
- if self.mName.upper() in Property.sMultiValues:
- self.mValue = PyCalendarMultiValue(valueType)
- else:
- # Create the type
- self.mValue = PyCalendarValue.createFromType(valueType)
-
- # Now parse the data
- try:
- if valueType in (PyCalendarValue.VALUETYPE_DATE, PyCalendarValue.VALUETYPE_DATETIME):
- # vCard supports a slightly different, expanded form, of date
- self.mValue.parse(data, fullISO=True)
- else:
- self.mValue.parse(data)
- 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(), PyCalendarUnknownValue)
-
- # Check whether custom value is set
- 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)
-
- # Check for multivalued
- if self.mName.upper() in Property.sMultiValues:
- self.mValue = PyCalendarMultiValue(valueType)
- else:
- # Create the type
- self.mValue = PyCalendarValue.createFromType(valueType)
-
- self.mValue.setValue(value)
-
-
- def setupValueAttribute(self):
- 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. If there is no mapping available,
- # then always add VALUE if it is not TEXT.
- default_type = Property.sDefaultValueTypeMap.get(self.mName.upper())
- 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):
- # Value
- self.mValue = PyCalendarIntegerValue(value=ival)
-
- # Attributes
- self.setupValueAttribute()
-
-
- 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, PyCalendarUnknownValue):
- self.mValue.setValue(txt)
-
- # Attributes
- self.setupValueAttribute()
-
-
- def _init_attr_value_adr(self, reqstatus):
- # Value
- self.mValue = AdrValue(reqstatus)
-
- # Attributes
- self.setupValueAttribute()
-
-
- def _init_attr_value_n(self, reqstatus):
- # Value
- self.mValue = NValue(reqstatus)
-
- # Attributes
- self.setupValueAttribute()
-
-
- def _init_attr_value_org(self, reqstatus):
- # Value
- self.mValue = OrgValue(reqstatus)
-
- # 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()
- self.mValue.setValue(utcoffset.getValue())
-
- # Attributes
- self.setupValueAttribute()
Copied: PyCalendar/trunk/src/pycalendar/vcard/property.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/vcard/property.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/vcard/property.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/vcard/property.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,573 @@
+##
+# 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.
+##
+
+from pycalendar import stringutils
+from pycalendar.adr import Adr
+from pycalendar.adrvalue import AdrValue
+from pycalendar.attribute import PyCalendarAttribute
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.datetimevalue import PyCalendarDateTimeValue
+from pycalendar.exceptions import PyCalendarInvalidProperty
+from pycalendar.integervalue import PyCalendarIntegerValue
+from pycalendar.multivalue import PyCalendarMultiValue
+from pycalendar.n import N
+from pycalendar.nvalue import NValue
+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
+
+handleOptions = ("allow", "ignore", "fix", "raise")
+missingParameterValues = "fix"
+
+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,
+ definitions.Property_PRODID : PyCalendarValue.VALUETYPE_TEXT,
+ definitions.Property_REV : PyCalendarValue.VALUETYPE_DATETIME,
+ definitions.Property_SORT_STRING : PyCalendarValue.VALUETYPE_TEXT,
+ definitions.Property_SOUND : PyCalendarValue.VALUETYPE_BINARY,
+ 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,
+ definitions.Value_DATE : PyCalendarValue.VALUETYPE_DATE,
+ definitions.Value_DATE_TIME : PyCalendarValue.VALUETYPE_DATETIME,
+ definitions.Value_FLOAT : PyCalendarValue.VALUETYPE_FLOAT,
+ definitions.Value_INTEGER : PyCalendarValue.VALUETYPE_INTEGER,
+ definitions.Value_TEXT : PyCalendarValue.VALUETYPE_TEXT,
+ definitions.Value_TIME : PyCalendarValue.VALUETYPE_TIME,
+ definitions.Value_URI : PyCalendarValue.VALUETYPE_URI,
+ definitions.Value_UTCOFFSET : PyCalendarValue.VALUETYPE_UTC_OFFSET,
+ definitions.Value_VCARD : PyCalendarValue.VALUETYPE_VCARD,
+ }
+
+ sTypeValueMap = {
+ PyCalendarValue.VALUETYPE_ADR : definitions.Value_TEXT,
+ PyCalendarValue.VALUETYPE_BINARY : definitions.Value_BINARY,
+ PyCalendarValue.VALUETYPE_BOOLEAN : definitions.Value_BOOLEAN,
+ PyCalendarValue.VALUETYPE_DATE : definitions.Value_DATE,
+ PyCalendarValue.VALUETYPE_DATETIME : definitions.Value_DATE_TIME,
+ PyCalendarValue.VALUETYPE_FLOAT : definitions.Value_FLOAT,
+ PyCalendarValue.VALUETYPE_GEO : definitions.Value_FLOAT,
+ PyCalendarValue.VALUETYPE_INTEGER : definitions.Value_INTEGER,
+ PyCalendarValue.VALUETYPE_N : definitions.Value_TEXT,
+ PyCalendarValue.VALUETYPE_ORG : definitions.Value_TEXT,
+ PyCalendarValue.VALUETYPE_TEXT : definitions.Value_TEXT,
+ PyCalendarValue.VALUETYPE_TIME : definitions.Value_TIME,
+ PyCalendarValue.VALUETYPE_URI : definitions.Value_URI,
+ PyCalendarValue.VALUETYPE_UTC_OFFSET : definitions.Value_UTCOFFSET,
+ PyCalendarValue.VALUETYPE_VCARD : definitions.Value_VCARD,
+ }
+
+ sMultiValues = set((
+ definitions.Property_NICKNAME,
+ definitions.Property_CATEGORIES,
+ ))
+
+ sTextVariants = set((
+ definitions.Property_ADR,
+ definitions.Property_N,
+ definitions.Property_ORG,
+ ))
+
+ def __init__(self, group=None, name=None, value=None, valuetype=None):
+ self._init_PyCalendarProperty()
+
+ self.mGroup = group
+
+ self.mName = name if name is not None else ""
+
+ if isinstance(value, int):
+ self._init_attr_value_int(value)
+
+ elif isinstance(value, str):
+ self._init_attr_value_text(value, valuetype if valuetype else Property.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarValue.VALUETYPE_TEXT))
+
+ elif isinstance(value, PyCalendarDateTime):
+ self._init_attr_value_datetime(value)
+
+ elif isinstance(value, Adr):
+ self._init_attr_value_adr(value)
+
+ elif isinstance(value, N):
+ self._init_attr_value_n(value)
+
+ elif isinstance(value, list) or isinstance(value, tuple):
+ if name.upper() == definitions.Property_ORG:
+ self._init_attr_value_org(value)
+ elif name.upper() == definitions.Property_GEO:
+ self._init_attr_value_geo(value)
+ else:
+ # Assume everything else is a text list
+ self._init_attr_value_text_list(value)
+
+ 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():
+ other.mAttributes[attrname] = [i.duplicate() for i in attrs]
+ other.mValue = self.mValue.duplicate()
+
+ return other
+
+
+ def __hash__(self):
+ return hash((
+ self.mName,
+ tuple([tuple(self.mAttributes[attrname]) for attrname in sorted(self.mAttributes.keys())]),
+ self.mValue,
+ ))
+
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+
+ def __eq__(self, other):
+ if not isinstance(other, Property):
+ return False
+ return (
+ self.mGroup == self.mGroup and
+ self.mName == other.mName and
+ self.mValue == other.mValue and
+ 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()])
+
+
+ def hasAttribute(self, attr):
+ 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 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, ";:")
+ if not prop_name:
+ raise PyCalendarInvalidProperty("Invalid property", data)
+
+ # Check for group prefix
+ splits = prop_name.split(".", 1)
+ if len(splits) == 2:
+ # We have both group and name
+ self.mGroup = splits[0]
+ self.mName = splits[1]
+ 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:
+ raise PyCalendarInvalidProperty("Invalid property parameter", data)
+ elif ParserContext.VCARD_2_NO_PARAMETER_VALUES == ParserContext.PARSER_ALLOW:
+ attribute_value = None
+ else: # PARSER_IGNORE and PARSER_FIX
+ attribute_name = None
+
+ if attribute_name.upper() == "BASE64" and ParserContext.VCARD_2_BASE64 == ParserContext.PARSER_FIX:
+ attribute_name = definitions.Parameter_ENCODING
+ attribute_value = definitions.Parameter_Value_ENCODING_B
+ stripValueSpaces = True
+ else:
+ txt = txt[1:]
+ attribute_value, txt = stringutils.strduptokenstr(txt, ":;,")
+ if attribute_value is None:
+ raise PyCalendarInvalidProperty("Invalid property", data)
+
+ # Now add attribute value (decode ^-escaping)
+ if attribute_name is not None:
+ 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(decodeParameterValue(attribute_value2))
+ elif txt[0] == ':':
+ txt = txt[1:]
+ if stripValueSpaces:
+ txt = txt.replace(" ", "")
+ self.createValue(txt)
+ txt = None
+
+ 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
+
+
+ def getText(self):
+ os = StringIO.StringIO()
+ self.generate(os)
+ return os.getvalue()
+
+
+ def generate(self, os):
+
+ # Write it out always with value
+ self.generateValue(os, False)
+
+
+ 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):
+
+ self.setupValueAttribute()
+
+ # Must write to temp buffer and then wrap
+ sout = StringIO.StringIO()
+ if self.mGroup:
+ sout.write(self.mGroup + ".")
+ sout.write(self.mName)
+
+ # Write all attributes
+ for key in sorted(self.mAttributes.keys()):
+ for attr in self.mAttributes[key]:
+ sout.write(";")
+ attr.generate(sout)
+
+ # Write value
+ sout.write(":")
+ 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("\r\n")
+ value_len -= 72
+ offset += 72
+ sout.write(" ")
+ sout.write(value[offset:])
+ os.write(sout.getvalue())
+ 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)
+ else:
+ # Look for valid utf8 range and write that out
+ start = 0
+ written = 0
+ lineWrap = 74
+ while written < len(temp):
+ # Start 74 chars on from where we are
+ offset = start + lineWrap
+ if offset >= len(temp):
+ line = temp[start:]
+ os.write(line)
+ written = len(temp)
+ else:
+ # Check whether next char is valid utf8 lead byte
+ 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
+
+ # Get value type from property name
+ valueType = Property.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarValue.VALUETYPE_TEXT)
+
+ # Check whether custom value is set
+ 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)
+
+ # Check for multivalued
+ if self.mName.upper() in Property.sMultiValues:
+ self.mValue = PyCalendarMultiValue(valueType)
+ else:
+ # Create the type
+ self.mValue = PyCalendarValue.createFromType(valueType)
+
+ # Now parse the data
+ try:
+ if valueType in (PyCalendarValue.VALUETYPE_DATE, PyCalendarValue.VALUETYPE_DATETIME):
+ # vCard supports a slightly different, expanded form, of date
+ self.mValue.parse(data, fullISO=True)
+ else:
+ self.mValue.parse(data)
+ 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(), PyCalendarUnknownValue)
+
+ # Check whether custom value is set
+ 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)
+
+ # Check for multivalued
+ if self.mName.upper() in Property.sMultiValues:
+ self.mValue = PyCalendarMultiValue(valueType)
+ else:
+ # Create the type
+ self.mValue = PyCalendarValue.createFromType(valueType)
+
+ self.mValue.setValue(value)
+
+
+ def setupValueAttribute(self):
+ 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. If there is no mapping available,
+ # then always add VALUE if it is not TEXT.
+ default_type = Property.sDefaultValueTypeMap.get(self.mName.upper())
+ 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):
+ # Value
+ self.mValue = PyCalendarIntegerValue(value=ival)
+
+ # Attributes
+ self.setupValueAttribute()
+
+
+ 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, PyCalendarUnknownValue):
+ self.mValue.setValue(txt)
+
+ # Attributes
+ self.setupValueAttribute()
+
+
+ def _init_attr_value_adr(self, reqstatus):
+ # Value
+ self.mValue = AdrValue(reqstatus)
+
+ # Attributes
+ self.setupValueAttribute()
+
+
+ def _init_attr_value_n(self, reqstatus):
+ # Value
+ self.mValue = NValue(reqstatus)
+
+ # Attributes
+ self.setupValueAttribute()
+
+
+ def _init_attr_value_org(self, reqstatus):
+ # Value
+ self.mValue = OrgValue(reqstatus)
+
+ # 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()
+ self.mValue.setValue(utcoffset.getValue())
+
+ # Attributes
+ self.setupValueAttribute()
Deleted: PyCalendar/trunk/src/pycalendar/vcard/tests/__init__.py
===================================================================
--- PyCalendar/branches/server/src/pycalendar/vcard/tests/__init__.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vcard/tests/__init__.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,15 +0,0 @@
-#
-# 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.
-##
Copied: PyCalendar/trunk/src/pycalendar/vcard/tests/__init__.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/vcard/tests/__init__.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/vcard/tests/__init__.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/vcard/tests/__init__.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,15 @@
+#
+# 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.
+##
Deleted: PyCalendar/trunk/src/pycalendar/vcard/tests/test_card.py
===================================================================
--- PyCalendar/branches/server/src/pycalendar/vcard/tests/test_card.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vcard/tests/test_card.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,571 +0,0 @@
-##
-# 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.
-##
-
-from pycalendar.exceptions import PyCalendarInvalidData
-from pycalendar.parser import ParserContext
-from pycalendar.vcard.card import Card
-from pycalendar.vcard.property import Property
-import cStringIO as StringIO
-import difflib
-import unittest
-
-class TestCard(unittest.TestCase):
-
- data = (
- (
-"""BEGIN:VCARD
-VERSION:3.0
-N:Thompson;Default;;;
-FN:Default Thompson
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
-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 at 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
-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 at 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
-BDAY:2001-01-02
-REV:2011-01-02T12:34:56-0500
-UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson
-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
-BDAY:20010102
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at example.com
-FN:Default Thompson
-N:Thompson;Default;;;
-REV:20110102T123456-0500
-TEL;type=WORK;type=pref:1-555-555-5555
-TEL;type=CELL:1-444-444-4444
-item1.X-ABADR:us
-END:VCARD
-""".replace("\n", "\r\n"),
- ),
- (
-"""BEGIN:VCARD
-VERSION:3.0
-N:Thompson;Default;;;
-FN:Default Thompson
-EMAIL;type=INTERNET;WORK;type=pref:lthompson at 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
-BDAY:2001-01-02
-REV:2011-01-02T12:34:56-0500
-UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson
-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
-BDAY:20010102
-EMAIL;type=INTERNET;type=pref;WORK:lthompson at example.com
-FN:Default Thompson
-N:Thompson;Default;;;
-REV:20110102T123456-0500
-TEL;type=WORK;type=pref:1-555-555-5555
-TEL;type=CELL:1-444-444-4444
-item1.X-ABADR:us
-END:VCARD
-""".replace("\n", "\r\n"),
- ),
- (
-"""BEGIN:VCARD
-VERSION:3.0
-N:Picture;With;;;
-FN:With Picture
-EMAIL;type=INTERNET;type=WORK;type=pref:withpicture at example.com
-TEL;type=WORK;type=pref:777-777-7777
-TEL;type=CELL:8888888888
-item1.ADR;type=WORK;type=pref:;;1234 Golly Street;Sunnyside;CA;99999;USA
-item1.X-ABADR:us
-PHOTO;Encoding=b:QkVHSU46VkNBUkQKVkVSU0lPTjozLjAKTjpQaWN0dXJlO1dpdGg7Ozs
- KRk46V2l0aCBQaWN0dXJlCkVNQUlMO3R5cGU9SU5URVJORVQ7dHlwZT1XT1JLO3R5cGU9cH
- JlZjp3aXRocGljdHVyZUBleGFtcGxlLmNvbQpURUw7dHlwZT1XT1JLO3R5cGU9cHJlZjo3N
- zctNzc3LTc3NzcKVEVMO3R5cGU9Q0VMTDo4ODg4ODg4ODg4Cml0ZW0xLkFEUjt0eXBlPVdP
- Uks7dHlwZT1wcmVmOjs7MTIzNCBHb2xseSBTdHJlZXQ7U3VubnlzaWRlO0NBOzk5OTk5O1V
- TQQppdGVtMS5YLUFCQURSOnVzClBIT1RPO0JBU0U2NDoKVUlEOjkzNDczMUM2LTFDOTUtNE
- M0MC1CRTFGLUZBNDIxNUIyMzA3QjpBQlBlcnNvbgpFTkQ6VkNBUkQK
-UID:934731C6-1C95-4C40-BE1F-FA4215B2307B:ABPerson
-END:VCARD
-""".replace("\n", "\r\n"),
-"""BEGIN:VCARD
-VERSION:3.0
-UID:934731C6-1C95-4C40-BE1F-FA4215B2307B:ABPerson
-item1.ADR;type=WORK;type=pref:;;1234 Golly Street;Sunnyside;CA;99999;USA
-EMAIL;type=INTERNET;type=WORK;type=pref:withpicture at example.com
-FN:With Picture
-N:Picture;With;;;
-PHOTO;Encoding=b:
- QkVHSU46VkNBUkQKVkVSU0lPTjozLjAKTjpQaWN0dXJlO1dpdGg7OzsKRk46V2l0aCBQaWN0
- dXJlCkVNQUlMO3R5cGU9SU5URVJORVQ7dHlwZT1XT1JLO3R5cGU9cHJlZjp3aXRocGljdHVy
- ZUBleGFtcGxlLmNvbQpURUw7dHlwZT1XT1JLO3R5cGU9cHJlZjo3NzctNzc3LTc3NzcKVEVM
- O3R5cGU9Q0VMTDo4ODg4ODg4ODg4Cml0ZW0xLkFEUjt0eXBlPVdPUks7dHlwZT1wcmVmOjs7
- MTIzNCBHb2xseSBTdHJlZXQ7U3VubnlzaWRlO0NBOzk5OTk5O1VTQQppdGVtMS5YLUFCQURS
- OnVzClBIT1RPO0JBU0U2NDoKVUlEOjkzNDczMUM2LTFDOTUtNEM0MC1CRTFGLUZBNDIxNUIy
- MzA3QjpBQlBlcnNvbgpFTkQ6VkNBUkQK
-TEL;type=WORK;type=pref:777-777-7777
-TEL;type=CELL:8888888888
-item1.X-ABADR:us
-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 at 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 at 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,
- "\n".join(difflib.unified_diff(test1.splitlines(), test2.splitlines())),
- )
-
- 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()
- self.assertEqual(test, result, "\n".join(difflib.unified_diff(test.splitlines(), result.splitlines())))
-
- for item, result in self.data:
- _doDuplicateRoundtrip(item, result)
-
-
- def testEquality(self):
-
-
- def _doEquality(caldata):
- card1 = Card()
- card1.parse(StringIO.StringIO(caldata))
-
- card2 = Card()
- card2.parse(StringIO.StringIO(caldata))
-
- 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))
-
- card2 = Card()
- card2.parse(StringIO.StringIO(caldata))
- card2.addProperty(Property("X-FOO", "BAR"))
-
- self.assertNotEqual(card1, card2)
-
- for item, _ignore in self.data:
- _doEquality(item)
- _doNonEquality(item)
-
-
- def testMultiple(self):
-
- data = (
- (
-"""BEGIN:VCARD
-VERSION:3.0
-N:Thompson;Default;;;
-FN:Default Thompson
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
-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 at 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
-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 at 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
-END:VCARD
-BEGIN:VCARD
-VERSION:3.0
-UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E2:ABPerson
-item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U
- SA
-EMAIL;type=INTERNET;type=WORK;type=pref:athompson at example.com
-FN:Another Thompson
-N:Thompson;Another;;;
-TEL;type=WORK;type=pref:1-555-555-5556
-TEL;type=CELL:1-444-444-4445
-item1.X-ABADR:us
-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 at 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
-END:VCARD
-""".replace("\n", "\r\n"),
-"""BEGIN:VCARD
-VERSION:3.0
-UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E2:ABPerson
-item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U
- SA
-EMAIL;type=INTERNET;type=WORK;type=pref:athompson at example.com
-FN:Another Thompson
-N:Thompson;Another;;;
-TEL;type=WORK;type=pref:1-555-555-5556
-TEL;type=CELL:1-444-444-4445
-item1.X-ABADR:us
-END:VCARD
-""".replace("\n", "\r\n"),
- )),
- )
-
- 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;;;
-FN:Test Card
-EMAIL;type=INTERNET;type=WORK;type=pref:sagen at apple.com
-TEL;type=WORK;type=pref:555-1212
-TEL;type=HOME:532-1234
-PHOTO;BASE64:
- TU0AKgAAMAj////////////////////////////////////////////////////////////+///+
- SW1hZ2VNYWdpY2sgNS4zLjkgMTAvMDEvMDEgUToxNiBodHRwOi8vd3d3LmltYWdlbWFnaWNrLm9y
- ZwA=
-CATEGORIES:My Contacts
-X-ABUID:5B77BC10-E9DB-48C4-8BE1-BAB5E38E1E43\:ABPerson
-UID:128ad7ee-a656-4773-95ce-f07f77e8cc23
-REV:2011-03-23T20:20:04Z
-END:VCARD
-""".replace("\n", "\r\n")
-
- result = """BEGIN:VCARD
-VERSION:3.0
-UID:128ad7ee-a656-4773-95ce-f07f77e8cc23
-CATEGORIES:My Contacts
-EMAIL;type=INTERNET;type=WORK;type=pref:sagen at apple.com
-FN:Test Card
-N:Card;Test;;;
-PHOTO;ENCODING=B:
- TU0AKgAAMAj////////////////////////////////////////////////////////////+
- ///+SW1hZ2VNYWdpY2sgNS4zLjkgMTAvMDEvMDEgUToxNiBodHRwOi8vd3d3LmltYWdlbWFn
- aWNrLm9yZwA=
-REV:20110323T202004Z
-TEL;type=WORK;type=pref:555-1212
-TEL;type=HOME:532-1234
-X-ABUID:5B77BC10-E9DB-48C4-8BE1-BAB5E38E1E43:ABPerson
-END:VCARD
-""".replace("\n", "\r\n")
-
- card = Card.parseText(data)
- self.assertEqual(str(card), result)
-
-
- def testParseFail(self):
-
- data = (
-"""BEGIN:VCARD
-VERSION:3.0
-N:Thompson;Default;;;
-FN:Default Thompson
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
-""".replace("\n", "\r\n"),
-
-"""BEGIN:VCALENDAR
-PRODID:-//mulberrymail.com//Mulberry v4.0//EN
-VERSION:2.0
-END:VCALENDAR
-""".replace("\n", "\r\n"),
-
-"""BOGUS
-BEGIN:VCARD
-VERSION:3.0
-N:Thompson;Default;;;
-FN:Default Thompson
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
-END:VCARD
-""".replace("\n", "\r\n"),
-
-"""BOGUS
-
-BEGIN:VCARD
-VERSION:3.0
-N:Thompson;Default;;;
-FN:Default Thompson
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
-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 at 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
-END:VCARD
-BOGUS
-""".replace("\n", "\r\n"),
-
-"""BEGIN:VCARD
-VERSION:3.0
-N:Thompson;Default;;;
-FN:Default Thompson
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
-END:VCARD
-
-BOGUS
-""".replace("\n", "\r\n"),
-
- )
-
- for item in data:
- self.assertRaises(PyCalendarInvalidData, Card.parseText, item)
-
-
- def testParseBlank(self):
-
- data = (
-"""
-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 at 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
-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 at 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
-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 at 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
-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 at 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
-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 at 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
-END:VCARD
-""".replace("\n", "\r\n"),
- )
-
- save = ParserContext.BLANK_LINES_IN_DATA
- for item in data:
- ParserContext.BLANK_LINES_IN_DATA = ParserContext.PARSER_RAISE
- self.assertRaises(PyCalendarInvalidData, Card.parseText, item)
-
- ParserContext.BLANK_LINES_IN_DATA = ParserContext.PARSER_IGNORE
- lines = item.split("\r\n")
- result = "\r\n".join([line for line in lines if line]) + "\r\n"
- self.assertEqual(str(Card.parseText(item)), result)
-
- ParserContext.BLANK_LINES_IN_DATA = save
Copied: PyCalendar/trunk/src/pycalendar/vcard/tests/test_card.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/vcard/tests/test_card.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/vcard/tests/test_card.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/vcard/tests/test_card.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,571 @@
+##
+# 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.
+##
+
+from pycalendar.exceptions import PyCalendarInvalidData
+from pycalendar.parser import ParserContext
+from pycalendar.vcard.card import Card
+from pycalendar.vcard.property import Property
+import cStringIO as StringIO
+import difflib
+import unittest
+
+class TestCard(unittest.TestCase):
+
+ data = (
+ (
+"""BEGIN:VCARD
+VERSION:3.0
+N:Thompson;Default;;;
+FN:Default Thompson
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
+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 at 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
+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 at 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
+BDAY:2001-01-02
+REV:2011-01-02T12:34:56-0500
+UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson
+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
+BDAY:20010102
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at example.com
+FN:Default Thompson
+N:Thompson;Default;;;
+REV:20110102T123456-0500
+TEL;type=WORK;type=pref:1-555-555-5555
+TEL;type=CELL:1-444-444-4444
+item1.X-ABADR:us
+END:VCARD
+""".replace("\n", "\r\n"),
+ ),
+ (
+"""BEGIN:VCARD
+VERSION:3.0
+N:Thompson;Default;;;
+FN:Default Thompson
+EMAIL;type=INTERNET;WORK;type=pref:lthompson at 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
+BDAY:2001-01-02
+REV:2011-01-02T12:34:56-0500
+UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson
+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
+BDAY:20010102
+EMAIL;type=INTERNET;type=pref;WORK:lthompson at example.com
+FN:Default Thompson
+N:Thompson;Default;;;
+REV:20110102T123456-0500
+TEL;type=WORK;type=pref:1-555-555-5555
+TEL;type=CELL:1-444-444-4444
+item1.X-ABADR:us
+END:VCARD
+""".replace("\n", "\r\n"),
+ ),
+ (
+"""BEGIN:VCARD
+VERSION:3.0
+N:Picture;With;;;
+FN:With Picture
+EMAIL;type=INTERNET;type=WORK;type=pref:withpicture at example.com
+TEL;type=WORK;type=pref:777-777-7777
+TEL;type=CELL:8888888888
+item1.ADR;type=WORK;type=pref:;;1234 Golly Street;Sunnyside;CA;99999;USA
+item1.X-ABADR:us
+PHOTO;Encoding=b:QkVHSU46VkNBUkQKVkVSU0lPTjozLjAKTjpQaWN0dXJlO1dpdGg7Ozs
+ KRk46V2l0aCBQaWN0dXJlCkVNQUlMO3R5cGU9SU5URVJORVQ7dHlwZT1XT1JLO3R5cGU9cH
+ JlZjp3aXRocGljdHVyZUBleGFtcGxlLmNvbQpURUw7dHlwZT1XT1JLO3R5cGU9cHJlZjo3N
+ zctNzc3LTc3NzcKVEVMO3R5cGU9Q0VMTDo4ODg4ODg4ODg4Cml0ZW0xLkFEUjt0eXBlPVdP
+ Uks7dHlwZT1wcmVmOjs7MTIzNCBHb2xseSBTdHJlZXQ7U3VubnlzaWRlO0NBOzk5OTk5O1V
+ TQQppdGVtMS5YLUFCQURSOnVzClBIT1RPO0JBU0U2NDoKVUlEOjkzNDczMUM2LTFDOTUtNE
+ M0MC1CRTFGLUZBNDIxNUIyMzA3QjpBQlBlcnNvbgpFTkQ6VkNBUkQK
+UID:934731C6-1C95-4C40-BE1F-FA4215B2307B:ABPerson
+END:VCARD
+""".replace("\n", "\r\n"),
+"""BEGIN:VCARD
+VERSION:3.0
+UID:934731C6-1C95-4C40-BE1F-FA4215B2307B:ABPerson
+item1.ADR;type=WORK;type=pref:;;1234 Golly Street;Sunnyside;CA;99999;USA
+EMAIL;type=INTERNET;type=WORK;type=pref:withpicture at example.com
+FN:With Picture
+N:Picture;With;;;
+PHOTO;Encoding=b:
+ QkVHSU46VkNBUkQKVkVSU0lPTjozLjAKTjpQaWN0dXJlO1dpdGg7OzsKRk46V2l0aCBQaWN0
+ dXJlCkVNQUlMO3R5cGU9SU5URVJORVQ7dHlwZT1XT1JLO3R5cGU9cHJlZjp3aXRocGljdHVy
+ ZUBleGFtcGxlLmNvbQpURUw7dHlwZT1XT1JLO3R5cGU9cHJlZjo3NzctNzc3LTc3NzcKVEVM
+ O3R5cGU9Q0VMTDo4ODg4ODg4ODg4Cml0ZW0xLkFEUjt0eXBlPVdPUks7dHlwZT1wcmVmOjs7
+ MTIzNCBHb2xseSBTdHJlZXQ7U3VubnlzaWRlO0NBOzk5OTk5O1VTQQppdGVtMS5YLUFCQURS
+ OnVzClBIT1RPO0JBU0U2NDoKVUlEOjkzNDczMUM2LTFDOTUtNEM0MC1CRTFGLUZBNDIxNUIy
+ MzA3QjpBQlBlcnNvbgpFTkQ6VkNBUkQK
+TEL;type=WORK;type=pref:777-777-7777
+TEL;type=CELL:8888888888
+item1.X-ABADR:us
+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 at 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 at 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,
+ "\n".join(difflib.unified_diff(test1.splitlines(), test2.splitlines())),
+ )
+
+ 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()
+ self.assertEqual(test, result, "\n".join(difflib.unified_diff(test.splitlines(), result.splitlines())))
+
+ for item, result in self.data:
+ _doDuplicateRoundtrip(item, result)
+
+
+ def testEquality(self):
+
+
+ def _doEquality(caldata):
+ card1 = Card()
+ card1.parse(StringIO.StringIO(caldata))
+
+ card2 = Card()
+ card2.parse(StringIO.StringIO(caldata))
+
+ 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))
+
+ card2 = Card()
+ card2.parse(StringIO.StringIO(caldata))
+ card2.addProperty(Property("X-FOO", "BAR"))
+
+ self.assertNotEqual(card1, card2)
+
+ for item, _ignore in self.data:
+ _doEquality(item)
+ _doNonEquality(item)
+
+
+ def testMultiple(self):
+
+ data = (
+ (
+"""BEGIN:VCARD
+VERSION:3.0
+N:Thompson;Default;;;
+FN:Default Thompson
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
+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 at 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
+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 at 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
+END:VCARD
+BEGIN:VCARD
+VERSION:3.0
+UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E2:ABPerson
+item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U
+ SA
+EMAIL;type=INTERNET;type=WORK;type=pref:athompson at example.com
+FN:Another Thompson
+N:Thompson;Another;;;
+TEL;type=WORK;type=pref:1-555-555-5556
+TEL;type=CELL:1-444-444-4445
+item1.X-ABADR:us
+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 at 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
+END:VCARD
+""".replace("\n", "\r\n"),
+"""BEGIN:VCARD
+VERSION:3.0
+UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E2:ABPerson
+item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U
+ SA
+EMAIL;type=INTERNET;type=WORK;type=pref:athompson at example.com
+FN:Another Thompson
+N:Thompson;Another;;;
+TEL;type=WORK;type=pref:1-555-555-5556
+TEL;type=CELL:1-444-444-4445
+item1.X-ABADR:us
+END:VCARD
+""".replace("\n", "\r\n"),
+ )),
+ )
+
+ 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;;;
+FN:Test Card
+EMAIL;type=INTERNET;type=WORK;type=pref:sagen at apple.com
+TEL;type=WORK;type=pref:555-1212
+TEL;type=HOME:532-1234
+PHOTO;BASE64:
+ TU0AKgAAMAj////////////////////////////////////////////////////////////+///+
+ SW1hZ2VNYWdpY2sgNS4zLjkgMTAvMDEvMDEgUToxNiBodHRwOi8vd3d3LmltYWdlbWFnaWNrLm9y
+ ZwA=
+CATEGORIES:My Contacts
+X-ABUID:5B77BC10-E9DB-48C4-8BE1-BAB5E38E1E43\:ABPerson
+UID:128ad7ee-a656-4773-95ce-f07f77e8cc23
+REV:2011-03-23T20:20:04Z
+END:VCARD
+""".replace("\n", "\r\n")
+
+ result = """BEGIN:VCARD
+VERSION:3.0
+UID:128ad7ee-a656-4773-95ce-f07f77e8cc23
+CATEGORIES:My Contacts
+EMAIL;type=INTERNET;type=WORK;type=pref:sagen at apple.com
+FN:Test Card
+N:Card;Test;;;
+PHOTO;ENCODING=B:
+ TU0AKgAAMAj////////////////////////////////////////////////////////////+
+ ///+SW1hZ2VNYWdpY2sgNS4zLjkgMTAvMDEvMDEgUToxNiBodHRwOi8vd3d3LmltYWdlbWFn
+ aWNrLm9yZwA=
+REV:20110323T202004Z
+TEL;type=WORK;type=pref:555-1212
+TEL;type=HOME:532-1234
+X-ABUID:5B77BC10-E9DB-48C4-8BE1-BAB5E38E1E43:ABPerson
+END:VCARD
+""".replace("\n", "\r\n")
+
+ card = Card.parseText(data)
+ self.assertEqual(str(card), result)
+
+
+ def testParseFail(self):
+
+ data = (
+"""BEGIN:VCARD
+VERSION:3.0
+N:Thompson;Default;;;
+FN:Default Thompson
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
+""".replace("\n", "\r\n"),
+
+"""BEGIN:VCALENDAR
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+VERSION:2.0
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+"""BOGUS
+BEGIN:VCARD
+VERSION:3.0
+N:Thompson;Default;;;
+FN:Default Thompson
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
+END:VCARD
+""".replace("\n", "\r\n"),
+
+"""BOGUS
+
+BEGIN:VCARD
+VERSION:3.0
+N:Thompson;Default;;;
+FN:Default Thompson
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
+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 at 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
+END:VCARD
+BOGUS
+""".replace("\n", "\r\n"),
+
+"""BEGIN:VCARD
+VERSION:3.0
+N:Thompson;Default;;;
+FN:Default Thompson
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
+END:VCARD
+
+BOGUS
+""".replace("\n", "\r\n"),
+
+ )
+
+ for item in data:
+ self.assertRaises(PyCalendarInvalidData, Card.parseText, item)
+
+
+ def testParseBlank(self):
+
+ data = (
+"""
+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 at 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
+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 at 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
+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 at 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
+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 at 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
+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 at 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
+END:VCARD
+""".replace("\n", "\r\n"),
+ )
+
+ save = ParserContext.BLANK_LINES_IN_DATA
+ for item in data:
+ ParserContext.BLANK_LINES_IN_DATA = ParserContext.PARSER_RAISE
+ self.assertRaises(PyCalendarInvalidData, Card.parseText, item)
+
+ ParserContext.BLANK_LINES_IN_DATA = ParserContext.PARSER_IGNORE
+ lines = item.split("\r\n")
+ result = "\r\n".join([line for line in lines if line]) + "\r\n"
+ self.assertEqual(str(Card.parseText(item)), result)
+
+ ParserContext.BLANK_LINES_IN_DATA = save
Deleted: PyCalendar/trunk/src/pycalendar/vcard/tests/test_property.py
===================================================================
--- PyCalendar/branches/server/src/pycalendar/vcard/tests/test_property.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vcard/tests/test_property.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,116 +0,0 @@
-##
-# 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.
-##
-
-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",
- "photo;VALUE=URI:http://example.com/photo.jpg",
- "TEL;type=WORK;type=pref:1-555-555-5555",
- "REV:20060226T120000Z",
- "X-FOO:BAR",
- "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",
- )
- save = ParserContext.INVALID_ESCAPE_SEQUENCES
- for data in test_bad_data:
- ParserContext.INVALID_ESCAPE_SEQUENCES = ParserContext.PARSER_RAISE
- 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()
- prop.parse(item)
- hashes.append(hash(prop))
- hashes.sort()
- for i in range(1, len(hashes)):
- 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"),
- ("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")
Copied: PyCalendar/trunk/src/pycalendar/vcard/tests/test_property.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/vcard/tests/test_property.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/vcard/tests/test_property.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/vcard/tests/test_property.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,116 @@
+##
+# 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.
+##
+
+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",
+ "photo;VALUE=URI:http://example.com/photo.jpg",
+ "TEL;type=WORK;type=pref:1-555-555-5555",
+ "REV:20060226T120000Z",
+ "X-FOO:BAR",
+ "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",
+ )
+ save = ParserContext.INVALID_ESCAPE_SEQUENCES
+ for data in test_bad_data:
+ ParserContext.INVALID_ESCAPE_SEQUENCES = ParserContext.PARSER_RAISE
+ 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()
+ prop.parse(item)
+ hashes.append(hash(prop))
+ hashes.sort()
+ for i in range(1, len(hashes)):
+ 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"),
+ ("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")
Deleted: PyCalendar/trunk/src/pycalendar/vcard/tests/test_validation.py
===================================================================
--- PyCalendar/branches/server/src/pycalendar/vcard/tests/test_validation.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vcard/tests/test_validation.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,221 +0,0 @@
-##
-# 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.
-##
-
-from pycalendar.exceptions import PyCalendarValidationError
-from pycalendar.vcard.card import Card
-import unittest
-
-class TestValidation(unittest.TestCase):
-
- def test_basic(self):
-
- data = (
- (
- "No problems",
- """BEGIN:VCARD
-VERSION:3.0
-N:Thompson;Default;;;
-FN:Default Thompson
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
-END:VCARD
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "No VERSION",
- """BEGIN:VCARD
-VERSION:3.0
-FN:Default Thompson
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
-END:VCARD
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VCARD] Missing or too many required property: N",
- )),
- ),
- )
-
- 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",
- """BEGIN:VCARD
-VERSION:3.0
-N:Thompson;Default;;;
-FN:Default Thompson
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
-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 at 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
-END:VCARD
-""".replace("\n", "\r\n"),
- set(),
- set(),
- ),
- (
- "Unfixable only",
- """BEGIN:VCARD
-VERSION:3.0
-FN:Default Thompson
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
-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 at example.com
-FN:Default Thompson
-TEL;type=WORK;type=pref:1-555-555-5555
-TEL;type=CELL:1-444-444-4444
-item1.X-ABADR:us
-END:VCARD
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VCARD] Missing or too many required property: N",
- )),
- ),
- )
-
- for title, test_old, test_new, test_fixed, test_unfixed in data:
- card = Card.parseText(test_old)
- fixed, unfixed = card.validate(doFix=False, doRaise=False)
- self.assertEqual(str(card), 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,))
-
-
- def test_mode_raise(self):
-
- data = (
- (
- "OK",
- """BEGIN:VCARD
-VERSION:3.0
-N:Thompson;Default;;;
-FN:Default Thompson
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
-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 at 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
-END:VCARD
-""".replace("\n", "\r\n"),
- set(),
- set(),
- False,
- ),
- (
- "Unfixable only",
- """BEGIN:VCARD
-VERSION:3.0
-FN:Default Thompson
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
-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 at example.com
-FN:Default Thompson
-TEL;type=WORK;type=pref:1-555-555-5555
-TEL;type=CELL:1-444-444-4444
-item1.X-ABADR:us
-END:VCARD
-""".replace("\n", "\r\n"),
- set(),
- set((
- "[VCARD] Missing or too many required property: N",
- )),
- True,
- ),
- )
-
- for title, test_old, test_new, test_fixed, test_unfixed, test_raises in data:
- card = Card.parseText(test_old)
- if test_raises:
- self.assertRaises(PyCalendarValidationError, card.validate, doFix=False, doRaise=True)
- else:
- try:
- fixed, unfixed = card.validate(doFix=False, doRaise=False)
- except:
- self.fail(msg="Failed test: %s" % (title,))
- self.assertEqual(str(card), 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,))
Copied: PyCalendar/trunk/src/pycalendar/vcard/tests/test_validation.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/vcard/tests/test_validation.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/vcard/tests/test_validation.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/vcard/tests/test_validation.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,221 @@
+##
+# 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.
+##
+
+from pycalendar.exceptions import PyCalendarValidationError
+from pycalendar.vcard.card import Card
+import unittest
+
+class TestValidation(unittest.TestCase):
+
+ def test_basic(self):
+
+ data = (
+ (
+ "No problems",
+ """BEGIN:VCARD
+VERSION:3.0
+N:Thompson;Default;;;
+FN:Default Thompson
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
+END:VCARD
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "No VERSION",
+ """BEGIN:VCARD
+VERSION:3.0
+FN:Default Thompson
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
+END:VCARD
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VCARD] Missing or too many required property: N",
+ )),
+ ),
+ )
+
+ 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",
+ """BEGIN:VCARD
+VERSION:3.0
+N:Thompson;Default;;;
+FN:Default Thompson
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
+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 at 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
+END:VCARD
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ ),
+ (
+ "Unfixable only",
+ """BEGIN:VCARD
+VERSION:3.0
+FN:Default Thompson
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
+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 at example.com
+FN:Default Thompson
+TEL;type=WORK;type=pref:1-555-555-5555
+TEL;type=CELL:1-444-444-4444
+item1.X-ABADR:us
+END:VCARD
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VCARD] Missing or too many required property: N",
+ )),
+ ),
+ )
+
+ for title, test_old, test_new, test_fixed, test_unfixed in data:
+ card = Card.parseText(test_old)
+ fixed, unfixed = card.validate(doFix=False, doRaise=False)
+ self.assertEqual(str(card), 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,))
+
+
+ def test_mode_raise(self):
+
+ data = (
+ (
+ "OK",
+ """BEGIN:VCARD
+VERSION:3.0
+N:Thompson;Default;;;
+FN:Default Thompson
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
+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 at 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
+END:VCARD
+""".replace("\n", "\r\n"),
+ set(),
+ set(),
+ False,
+ ),
+ (
+ "Unfixable only",
+ """BEGIN:VCARD
+VERSION:3.0
+FN:Default Thompson
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at 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
+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 at example.com
+FN:Default Thompson
+TEL;type=WORK;type=pref:1-555-555-5555
+TEL;type=CELL:1-444-444-4444
+item1.X-ABADR:us
+END:VCARD
+""".replace("\n", "\r\n"),
+ set(),
+ set((
+ "[VCARD] Missing or too many required property: N",
+ )),
+ True,
+ ),
+ )
+
+ for title, test_old, test_new, test_fixed, test_unfixed, test_raises in data:
+ card = Card.parseText(test_old)
+ if test_raises:
+ self.assertRaises(PyCalendarValidationError, card.validate, doFix=False, doRaise=True)
+ else:
+ try:
+ fixed, unfixed = card.validate(doFix=False, doRaise=False)
+ except:
+ self.fail(msg="Failed test: %s" % (title,))
+ self.assertEqual(str(card), 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,))
Deleted: PyCalendar/trunk/src/pycalendar/vcard/validation.py
===================================================================
--- PyCalendar/branches/server/src/pycalendar/vcard/validation.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vcard/validation.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,23 +0,0 @@
-##
-# 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.
-##
-
-from pycalendar.validation import partial, PropertyValueChecks
-from pycalendar.vcard import definitions
-
-VCARD_VALUE_CHECKS = {
- definitions.Property_VERSION: partial(PropertyValueChecks.stringValue, "3.0"),
- definitions.Property_PROFILE: partial(PropertyValueChecks.stringValue, "VCARD"),
-}
Copied: PyCalendar/trunk/src/pycalendar/vcard/validation.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/vcard/validation.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/vcard/validation.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/vcard/validation.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,23 @@
+##
+# 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.
+##
+
+from pycalendar.validation import partial, PropertyValueChecks
+from pycalendar.vcard import definitions
+
+VCARD_VALUE_CHECKS = {
+ definitions.Property_VERSION: partial(PropertyValueChecks.stringValue, "3.0"),
+ definitions.Property_PROFILE: partial(PropertyValueChecks.stringValue, "VCARD"),
+}
Modified: PyCalendar/trunk/src/pycalendar/vevent.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/vevent.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vevent.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,66 +14,76 @@
# limitations under the License.
##
-from component import PyCalendarComponent
-from componentrecur import PyCalendarComponentRecur
-from property import PyCalendarProperty
-import definitions
-import itipdefinitions
+from pycalendar import definitions
+from pycalendar import itipdefinitions
+from pycalendar.componentrecur import PyCalendarComponentRecur
+from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS
+from pycalendar.property import PyCalendarProperty
class PyCalendarVEvent(PyCalendarComponentRecur):
- sBeginDelimiter = definitions.cICalComponent_BEGINVEVENT
+ propertyCardinality_1 = (
+ definitions.cICalProperty_DTSTAMP,
+ definitions.cICalProperty_UID,
+ )
- sEndDelimiter = definitions.cICalComponent_ENDVEVENT
+ propertyCardinality_0_1 = (
+ definitions.cICalProperty_CLASS,
+ definitions.cICalProperty_CREATED,
+ definitions.cICalProperty_DESCRIPTION,
+ definitions.cICalProperty_GEO,
+ definitions.cICalProperty_LAST_MODIFIED,
+ definitions.cICalProperty_LOCATION,
+ definitions.cICalProperty_ORGANIZER,
+ definitions.cICalProperty_PRIORITY,
+ definitions.cICalProperty_SEQUENCE,
+ # definitions.cICalProperty_STATUS, # Special fix done for multiple STATUS
+ definitions.cICalProperty_SUMMARY,
+ definitions.cICalProperty_TRANSP,
+ definitions.cICalProperty_URL,
+ definitions.cICalProperty_RECURRENCE_ID,
+ definitions.cICalProperty_RRULE,
+ definitions.cICalProperty_DTEND,
+ definitions.cICalProperty_DURATION,
+ )
- @staticmethod
- def getVBegin():
- return PyCalendarVEvent.sBeginDelimiter
+ propertyValueChecks = ICALENDAR_VALUE_CHECKS
- @staticmethod
- def getVEnd():
- return PyCalendarVEvent.sEndDelimiter
+ def __init__(self, parent=None):
+ super(PyCalendarVEvent, self).__init__(parent=parent)
+ self.mStatus = definitions.eStatus_VEvent_None
- def __init__(self, calendar = None, copyit = None):
- if calendar is not None:
- super(PyCalendarVEvent, self).__init__(calendar=calendar)
- self.mStatus = definitions.eStatus_VEvent_None
- elif copyit is not None:
- super(PyCalendarVEvent, self).__init__(copyit=copyit)
- self.mStatus = copyit.mStatus
- def clone_it(self):
- return PyCalendarVEvent(copyit=self)
+ def duplicate(self, parent=None):
+ other = super(PyCalendarVEvent, self).duplicate(parent=parent)
+ other.mStatus = self.mStatus
+ return other
+
def getType(self):
- return PyCalendarComponent.eVEVENT
+ return definitions.cICalComponent_VEVENT
- def getBeginDelimiter(self):
- return PyCalendarVEvent.sBeginDelimiter
- def getEndDelimiter(self):
- return PyCalendarVEvent.sEndDelimiter
-
def getMimeComponentName(self):
return itipdefinitions.cICalMIMEComponent_VEVENT
+
def addComponent(self, comp):
# We can embed the alarm components only
- if comp.getType() == PyCalendarComponent.eVALARM:
- if self.mEmbedded is None:
- self.mEmbedded = []
- self.mEmbedded.append(comp)
- comp.setEmbedder(self)
- return True
+ if comp.getType() == definitions.cICalComponent_VALARM:
+ super(PyCalendarVEvent, self).addComponent(comp)
else:
- return False
+ raise ValueError
+
def getStatus(self):
return self.mStatus
+
def setStatus(self, status):
self.mStatus = status
+
def finalise(self):
# Do inherited
super(PyCalendarVEvent, self).finalise()
@@ -87,6 +97,40 @@
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.
+ """
+
+ fixed, unfixed = super(PyCalendarVEvent, self).validate(doFix)
+
+ # Extra constraint: if METHOD not present, DTSTART must be
+ if self.mParentComponent and not self.mParentComponent.hasProperty(definitions.cICalProperty_METHOD):
+ if not self.hasProperty(definitions.cICalProperty_DTSTART):
+ # 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
+ logProblem = "[%s] Properties must not both be present: %s, %s" % (
+ self.getType(),
+ definitions.cICalProperty_DTEND,
+ definitions.cICalProperty_DURATION,
+ )
+ if doFix:
+ self.removeProperties(definitions.cICalProperty_DTEND)
+ fixed.append(logProblem)
+ else:
+ unfixed.append(logProblem)
+
+ return fixed, unfixed
+
+
# Editing
def editStatus(self, status):
# Only if it is different
@@ -113,3 +157,13 @@
if value is not None:
prop = PyCalendarProperty(definitions.cICalProperty_STATUS, value)
self.addProperty(prop)
+
+
+ def sortedPropertyKeyOrder(self):
+ return (
+ definitions.cICalProperty_UID,
+ definitions.cICalProperty_RECURRENCE_ID,
+ definitions.cICalProperty_DTSTART,
+ definitions.cICalProperty_DURATION,
+ definitions.cICalProperty_DTEND,
+ )
Modified: PyCalendar/trunk/src/pycalendar/vfreebusy.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/vfreebusy.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vfreebusy.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,66 +14,66 @@
# limitations under the License.
##
-from component import PyCalendarComponent
-from datetime import PyCalendarDateTime
-from freebusy import PyCalendarFreeBusy
-from period import PyCalendarPeriod
-from periodvalue import PyCalendarPeriodValue
-from property import PyCalendarProperty
-from value import PyCalendarValue
-import definitions
-import itipdefinitions
+from pycalendar import definitions
+from pycalendar import itipdefinitions
+from pycalendar.component import PyCalendarComponent
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.freebusy import PyCalendarFreeBusy
+from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS
+from pycalendar.period import PyCalendarPeriod
+from pycalendar.periodvalue import PyCalendarPeriodValue
+from pycalendar.property import PyCalendarProperty
+from pycalendar.value import PyCalendarValue
class PyCalendarVFreeBusy(PyCalendarComponent):
- sBeginDelimiter = definitions.cICalComponent_BEGINVFREEBUSY
+ propertyCardinality_1 = (
+ definitions.cICalProperty_DTSTAMP,
+ definitions.cICalProperty_UID,
+ )
- sEndDelimiter = definitions.cICalComponent_ENDVFREEBUSY
+ propertyCardinality_0_1 = (
+ definitions.cICalProperty_CONTACT,
+ definitions.cICalProperty_DTSTART,
+ definitions.cICalProperty_DTEND,
+ definitions.cICalProperty_ORGANIZER,
+ definitions.cICalProperty_URL,
+ )
- @staticmethod
- def getVBegin():
- return PyCalendarVFreeBusy.sBeginDelimiter
+ propertyValueChecks = ICALENDAR_VALUE_CHECKS
- @staticmethod
- def gGetVEnd():
- return PyCalendarVFreeBusy.sEndDelimiter
+ def __init__(self, parent=None):
+ super(PyCalendarVFreeBusy, self).__init__(parent=parent)
+ self.mStart = PyCalendarDateTime()
+ self.mHasStart = False
+ self.mEnd = PyCalendarDateTime()
+ self.mHasEnd = False
+ self.mDuration = False
+ self.mCachedBusyTime = False
+ self.mSpanPeriod = None
+ self.mBusyTime = None
- def __init__(self, calendar=None, copyit=None):
- if calendar is not None:
- super(PyCalendarVFreeBusy, self).__init__(calendar=calendar)
- self.mStart = PyCalendarDateTime()
- self.mHasStart = False
- self.mEnd = PyCalendarDateTime()
- self.mHasEnd = False
- self.mDuration = False
- self.mCachedBusyTime = False
- self.mSpanPeriod = None
- self.mBusyTime = None
- elif copyit is not None:
- super(PyCalendarVFreeBusy, self).__init__(copyit=copyit)
- self.mStart = PyCalendarDateTime(copyit.mStart)
- self.mHasStart = copyit.mHasStart
- self.mEnd = PyCalendarDateTime(copyit.mEnd)
- self.mHasEnd = copyit.mHasEnd
- self.mDuration = copyit.mDuration
- self.mCachedBusyTime = False
- self.mBusyTime = None
- def clone_it(self):
- return PyCalendarVFreeBusy(self)
+ def duplicate(self, parent=None):
+ other = super(PyCalendarVFreeBusy, self).duplicate(parent=parent)
+ other.mStart = self.mStart.duplicate()
+ other.mHasStart = self.mHasStart
+ other.mEnd = self.mEnd.duplicate()
+ other.mHasEnd = self.mHasEnd
+ other.mDuration = self.mDuration
+ other.mCachedBusyTime = False
+ other.mBusyTime = None
+ return other
+
def getType(self):
- return PyCalendarComponent.eVFREEBUSY
+ return definitions.cICalComponent_VFREEBUSY
- def getBeginDelimiter(self):
- return PyCalendarVFreeBusy.sBeginDelimiter
- def getEndDelimiter(self):
- return PyCalendarVFreeBusy.sEndDelimiter
-
def getMimeComponentName(self):
return itipdefinitions.cICalMIMEComponent_VFREEBUSY
+
def finalise(self):
# Do inherited
super(PyCalendarVFreeBusy, self).finalise()
@@ -101,14 +101,15 @@
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:
# Use the start
- self.mEnd = PyCalendarDateTime(copyit=self.mStart)
+ self.mEnd = self.mStart.duplicate()
self.mDuration = False
- # Adjust to approriate non-inclusive end point
+ # Adjust to appropiate non-inclusive end point
if self.mStart.isDateOnly():
self.mEnd.offsetDay(1)
@@ -119,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
@@ -153,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
@@ -171,12 +181,13 @@
self.addProperty(prop)
# If its an all day event and the end one day after the start, ignore it
- temp = PyCalendarDateTime(copyit=start)
+ temp = start.duplicate()
temp.offsetDay(1)
if not start.isDateOnly() or end != temp:
prop = PyCalendarProperty(definitions.cICalProperty_DTEND, end)
self.addProperty(prop)
+
def editTimingStartDuration(self, start, duration):
# Updated cached values
self.mHasStart = True
@@ -201,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
@@ -211,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:
@@ -221,6 +234,7 @@
for fb in self.mBusyTime:
list.append(PyCalendarFreeBusy(fb))
+
def cacheBusyTime(self):
# Clear out any existing cache
@@ -277,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()
@@ -302,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()
@@ -317,7 +330,16 @@
end = self.mEnd
else:
end = max_end
-
+
self.mSpanPeriod = PyCalendarPeriod(start, end)
-
+
self.mCachedBusyTime = True
+
+
+ def sortedPropertyKeyOrder(self):
+ return (
+ definitions.cICalProperty_UID,
+ definitions.cICalProperty_DTSTART,
+ definitions.cICalProperty_DURATION,
+ definitions.cICalProperty_DTEND,
+ )
Modified: PyCalendar/trunk/src/pycalendar/vjournal.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/vjournal.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vjournal.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,45 +14,57 @@
# limitations under the License.
##
-from component import PyCalendarComponent
-from componentrecur import PyCalendarComponentRecur
-import definitions
-import itipdefinitions
+from pycalendar import definitions
+from pycalendar import itipdefinitions
+from pycalendar.componentrecur import PyCalendarComponentRecur
+from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS
class PyCalendarVJournal(PyCalendarComponentRecur):
- sBeginDelimiter = definitions.cICalComponent_BEGINVJOURNAL
+ propertyCardinality_1 = (
+ definitions.cICalProperty_DTSTAMP,
+ definitions.cICalProperty_UID,
+ )
- sEndDelimiter = definitions.cICalComponent_ENDVJOURNAL
+ propertyCardinality_0_1 = (
+ definitions.cICalProperty_CLASS,
+ definitions.cICalProperty_CREATED,
+ definitions.cICalProperty_DTSTART,
+ definitions.cICalProperty_LAST_MODIFIED,
+ definitions.cICalProperty_ORGANIZER,
+ definitions.cICalProperty_RECURRENCE_ID,
+ definitions.cICalProperty_SEQUENCE,
+ # definitions.cICalProperty_STATUS, # Special fix done for multiple STATUS
+ definitions.cICalProperty_SUMMARY,
+ definitions.cICalProperty_URL,
+ definitions.cICalProperty_RRULE,
+ )
- @staticmethod
- def getVBegin():
- return PyCalendarVJournal.sBeginDelimiter
+ propertyValueChecks = ICALENDAR_VALUE_CHECKS
- @staticmethod
- def getVEnd():
- return PyCalendarVJournal.sEndDelimiter
+ def __init__(self, parent=None):
+ super(PyCalendarVJournal, self).__init__(parent=parent)
- def __init__(self, calendar=None, copyit=None):
- if calendar is not None:
- super(PyCalendarVJournal, self).__init__(calendar=calendar)
- elif copyit is not None:
- super(PyCalendarVJournal, self).__init__(copyit=copyit)
- def clone_it(self):
- return PyCalendarVJournal(copyit=self)
+ def duplicate(self, parent=None):
+ return super(PyCalendarVJournal, self).duplicate(parent=parent)
+
def getType(self):
- return PyCalendarComponent.eVJOURNAL
+ return definitions.cICalComponent_VJOURNAL
- def getBeginDelimiter(self):
- return PyCalendarVJournal.sBeginDelimiter
- def getEndDelimiter(self):
- return PyCalendarVJournal.sEndDelimiter
-
def getMimeComponentName(self):
return itipdefinitions.cICalMIMEComponent_VJOURNAL
+
def finalise(self):
super(PyCalendarVJournal, self).finalise()
+
+
+ def sortedPropertyKeyOrder(self):
+ return (
+ definitions.cICalProperty_UID,
+ definitions.cICalProperty_RECURRENCE_ID,
+ definitions.cICalProperty_DTSTART,
+ )
Modified: PyCalendar/trunk/src/pycalendar/vtimezone.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/vtimezone.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vtimezone.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,65 +14,65 @@
# limitations under the License.
##
-from component import PyCalendarComponent
-from datetime import PyCalendarDateTime
-import definitions
+from pycalendar import definitions
+from pycalendar.component import PyCalendarComponent
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS
class PyCalendarVTimezone(PyCalendarComponent):
- sBeginDelimiter = definitions.cICalComponent_BEGINVTIMEZONE
+ propertyCardinality_1 = (
+ definitions.cICalProperty_TZID,
+ )
- sEndDelimiter = definitions.cICalComponent_ENDVTIMEZONE
+ propertyCardinality_0_1 = (
+ definitions.cICalProperty_LAST_MODIFIED,
+ definitions.cICalProperty_TZURL,
+ )
- @staticmethod
- def getVBegin():
- return PyCalendarVTimezone.sBeginDelimiter
+ propertyValueChecks = ICALENDAR_VALUE_CHECKS
- @staticmethod
- def getVEnd():
- return PyCalendarVTimezone.sEndDelimiter
+ UTCOFFSET_CACHE_MAX_ENTRIES = 100000
- def __init__(self, calendar=None, copyit=None):
- if calendar is not None:
- super(PyCalendarVTimezone, self).__init__(calendar=calendar)
- self.mID = ""
- self.mSortKey = 1
- elif copyit is not None:
- super(PyCalendarVTimezone, self).__init__(copyit=copyit)
- self.mID = copyit.mID
- self.mSortKey = copyit.mSortKey
+ sortSubComponents = False
- def clone_it(self):
- return PyCalendarVTimezone(copyit=self)
+ def __init__(self, parent=None):
+ super(PyCalendarVTimezone, self).__init__(parent=parent)
+ self.mID = ""
+ self.mUTCOffsetSortKey = None
+ self.mCachedExpandAllMaxYear = None
+ self.mCachedOffsets = None
+
+ def duplicate(self, parent=None):
+ other = super(PyCalendarVTimezone, self).duplicate(parent=parent)
+ other.mID = self.mID
+ other.mUTCOffsetSortKey = self.mUTCOffsetSortKey
+ return other
+
+
def getType(self):
- return PyCalendarComponent.eVTIMEZONE
+ return definitions.cICalComponent_VTIMEZONE
- def getBeginDelimiter(self):
- return PyCalendarVTimezone.sBeginDelimiter
- def getEndDelimiter(self):
- return PyCalendarVTimezone.sEndDelimiter
-
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() == PyCalendarComponent.eVTIMEZONESTANDARD)
- or (comp.getType() == PyCalendarComponent.eVTIMEZONEDAYLIGHT)):
- if self.mEmbedded is None:
- self.mEmbedded = []
- self.mEmbedded.append(comp)
- comp.setEmbedder(self)
- return True
+ if ((comp.getType() == definitions.cICalComponent_STANDARD)
+ or (comp.getType() == definitions.cICalComponent_DAYLIGHT)):
+ super(PyCalendarVTimezone, self).addComponent(comp)
else:
- return False
+ raise ValueError
+
def getMapKey(self):
return self.mID
+
def finalise(self):
# Get TZID
temp = self.loadValueString(definitions.cICalProperty_TZID)
@@ -80,45 +80,100 @@
self.mID = temp
# Sort sub-components by DTSTART
- if self.mEmbedded is not None:
- self.mEmbedded.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.
+ """
+
+ fixed, unfixed = super(PyCalendarVTimezone, self).validate(doFix)
+
+ # Must have at least one STANDARD or DAYLIGHT sub-component
+ for component in self.mComponents:
+ if component.getType() in (definitions.cICalComponent_STANDARD, definitions.cICalComponent_DAYLIGHT):
+ break
+ else:
+ # Cannot fix a missing required component
+ logProblem = "[%s] At least one component must be present: %s or %s" % (
+ self.getType(),
+ definitions.cICalComponent_STANDARD,
+ definitions.cICalComponent_DAYLIGHT,
+ )
+ unfixed.append(logProblem)
+
+ return fixed, unfixed
+
+
def getID(self):
return self.mID
- def getSortKey(self):
- if self.mSortKey == 1:
+
+ def getUTCOffsetSortKey(self):
+ if self.mUTCOffsetSortKey is None:
# Take time from first element
- if (self.mEmbedded is not None) and (len(self.mEmbedded) > 0):
+ if len(self.mComponents) > 0:
# Initial offset provides the primary key
- utc_offset1 = self.mEmbedded[0].getUTCOffset()
+ utc_offset1 = self.mComponents[0].getUTCOffset()
# Presence of secondary is the next key
utc_offset2 = utc_offset1
- if len(self.mEmbedded) > 1:
- utc_offset2 = self.mEmbedded[1].getUTCOffset()
+ if len(self.mComponents) > 1:
+ utc_offset2 = self.mComponents[1].getUTCOffset()
# Create key
- self.mSortKey = (utc_offset1 + utc_offset2) / 2
+ self.mUTCOffsetSortKey = (utc_offset1 + utc_offset2) / 2
else:
- self.mSortKey = 0
+ self.mUTCOffsetSortKey = 0
- return self.mSortKey
+ return self.mUTCOffsetSortKey
+
def getTimezoneOffsetSeconds(self, dt):
- # Get the closet matching element to the time
- found = self.findTimezoneElement(dt)
+ """
+ Caching implementation of expansion. We cache the entire set of transitions up to one year ahead
+ of the requested time.
+ """
- # Return it
- if found is None:
- return 0
- else:
- # Get its offset
- return found.getUTCOffset()
+ # 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)
+ # item
+ temp = dt.duplicate()
+ temp.setTimezoneID(None)
+ # Check whether we need to recache
+ if self.mCachedExpandAllMaxYear is None or temp.mYear >= self.mCachedExpandAllMaxYear:
+ cacheMax = temp.duplicate()
+ cacheMax.setHHMMSS(0, 0, 0)
+ cacheMax.offsetYear(2)
+ cacheMax.setMonth(1)
+ cacheMax.setDay(1)
+ self.mCachedExpandAll = self.expandAll(None, cacheMax)
+ self.mCachedExpandAllMaxYear = cacheMax.mYear
+ self.mCachedOffsets = {}
+
+ # Now search for the transition just below the time we want
+ if len(self.mCachedExpandAll):
+ cacheKey = (temp.mYear, temp.mMonth, temp.mDay, temp.mHours, temp.mMinutes,)
+ i = self.mCachedOffsets.get(cacheKey)
+ if i is None:
+ i = PyCalendarVTimezone.tuple_bisect_right(self.mCachedExpandAll, temp)
+ 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 0
+
+
def getTimezoneDescriptor(self, dt):
result = ""
@@ -127,7 +182,7 @@
# Get it
if found is not None:
- if found.getTZName().length() == 0:
+ if len(found.getTZName()) == 0:
tzoffset = found.getUTCOffset()
negative = False
if tzoffset < 0:
@@ -149,15 +204,35 @@
return result
+
def mergeTimezone(self, tz):
pass
+
+ @staticmethod
+ def tuple_bisect_right(a, x):
+ """
+ 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
+ return lo
+
+
def findTimezoneElement(self, dt):
# 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)
# item
- temp = PyCalendarDateTime(copyit=dt)
+ temp = dt.duplicate()
temp.setTimezoneID(None)
# Had to rework this because some VTIMEZONEs have sub-components where the DST instances are interleaved. That
@@ -167,38 +242,44 @@
found = None
dt_found = PyCalendarDateTime()
- if self.mEmbedded is not None:
- for item in self.mEmbedded:
- dt_item = item.expandBelow(temp)
- if temp >= dt_item:
- if found is not None:
- # Compare with the one previously cached and switch to this
- # one if newer
- if dt_item > dt_found:
- found = item
- dt_found = dt_item
- else:
+ for item in self.mComponents:
+ dt_item = item.expandBelow(temp)
+ if temp >= dt_item:
+ if found is not None:
+ # Compare with the one previously cached and switch to this
+ # one if newer
+ if dt_item > dt_found:
found = item
dt_found = dt_item
+ else:
+ found = item
+ dt_found = dt_item
return found
- def expandAll(self, start, end):
+
+ def expandAll(self, start, end, with_name=False):
results = []
- if self.mEmbedded is not None:
- for item in self.mEmbedded:
- results.extend(item.expandAll(start, end))
+ for item in self.mComponents:
+ results.extend(item.expandAll(start, end, with_name))
results = [x for x in set(results)]
- def sortit(e1, e2):
- return PyCalendarDateTime.sort(e1[0], e2[0])
- results.sort(cmp=sortit)
+ results.sort(key=lambda x: x[0].getPosixTime())
return results
+
+ def sortedPropertyKeyOrder(self):
+ return (
+ definitions.cICalProperty_TZID,
+ definitions.cICalProperty_LAST_MODIFIED,
+ definitions.cICalProperty_TZURL,
+ )
+
+
@staticmethod
- def sortComparator(tz1, tz2):
- sort1 = tz1.getSortKey()
- sort2 = tz2.getSortKey()
- if sort1 == sort2:
- return tz1.getID().compareToIgnoreCase(tz2.getID())
- else:
- return (1, -1)[sort1 < sort2]
+ def sortByUTCOffsetComparator(tz1, tz2):
+ sort1 = tz1.getUTCOffsetSortKey()
+ sort2 = tz2.getUTCOffsetSortKey()
+ if sort1 == sort2:
+ return tz1.getID().compareToIgnoreCase(tz2.getID())
+ else:
+ return (1, -1)[sort1 < sort2]
Modified: PyCalendar/trunk/src/pycalendar/vtimezonedaylight.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/vtimezonedaylight.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vtimezonedaylight.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,38 +14,18 @@
# limitations under the License.
##
-from component import PyCalendarComponent
+from pycalendar import definitions
from vtimezoneelement import PyCalendarVTimezoneElement
-import definitions
class PyCalendarVTimezoneDaylight(PyCalendarVTimezoneElement):
- sBeginDelimiter = definitions.cICalComponent_BEGINDAYLIGHT
+ def __init__(self, parent=None):
+ super(PyCalendarVTimezoneDaylight, self).__init__(parent=parent)
- sEndDelimiter = definitions.cICalComponent_ENDDAYLIGHT
- @staticmethod
- def getVBegin():
- return PyCalendarVTimezoneDaylight.sBeginDelimiter
+ def duplicate(self, parent=None):
+ return super(PyCalendarVTimezoneDaylight, self).duplicate(parent=parent)
- @staticmethod
- def getVEnd():
- return PyCalendarVTimezoneDaylight.sEndDelimiter
- def __init__(self, calendar=None, copyit=None):
- if calendar is not None:
- super(PyCalendarVTimezoneDaylight, self).__init__(calendar=calendar)
- elif copyit is not None:
- super(PyCalendarVTimezoneDaylight, self).__init__(copyit=copyit)
-
- def clone_it(self):
- return PyCalendarVTimezoneDaylight(copyit=self)
-
def getType(self):
- return PyCalendarComponent.eVTIMEZONEDAYLIGHT
-
- def getBeginDelimiter(self):
- return PyCalendarVTimezoneDaylight.sBeginDelimiter
-
- def getEndDelimiter(self):
- return PyCalendarVTimezoneDaylight.sEndDelimiter
+ return definitions.cICalComponent_DAYLIGHT
Modified: PyCalendar/trunk/src/pycalendar/vtimezoneelement.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/vtimezoneelement.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vtimezoneelement.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,44 +14,52 @@
# limitations under the License.
##
-from datetime import PyCalendarDateTime
-from period import PyCalendarPeriod
-from recurrenceset import PyCalendarRecurrenceSet
-from value import PyCalendarValue
-from vtimezone import PyCalendarVTimezone
-import definitions
+from bisect import bisect_right
+from pycalendar import definitions
+from pycalendar.component import PyCalendarComponent
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS
+from pycalendar.period import PyCalendarPeriod
+from pycalendar.recurrenceset import PyCalendarRecurrenceSet
+from pycalendar.value import PyCalendarValue
-class PyCalendarVTimezoneElement(PyCalendarVTimezone):
+class PyCalendarVTimezoneElement(PyCalendarComponent):
- def __init__(self, calendar=None, dt=None, offset=None, copyit=None):
- if calendar is not None and dt is None:
- super(PyCalendarVTimezoneElement, self).__init__(calendar=calendar)
- self.mStart = PyCalendarDateTime()
- self.mTZName = ""
- self.mUTCOffset = 0
- self.mUTCOffsetFrom = 0
- self.mRecurrences = PyCalendarRecurrenceSet()
- self.mCachedExpandBelow = None
- self.mCachedExpandBelowItems = None
- elif calendar is not None and dt is not None:
- super(PyCalendarVTimezoneElement, self).__init__(calendar=calendar)
- self.mStart = dt
- self.mTZName = ""
- self.mUTCOffset = offset
- self.mUTCOffsetFrom = 0
- self.mRecurrences = PyCalendarRecurrenceSet()
- self.mCachedExpandBelow = None
- self.mCachedExpandBelowItems = None
- elif copyit:
- super(PyCalendarVTimezoneElement, self).__init__(copyit=copyit)
- self.mStart = PyCalendarDateTime(copyit=copyit.mStart)
- self.mTZName = copyit.mTZName
- self.mUTCOffset = copyit.mUTCOffset
- self.mUTCOffsetFrom = copyit.mUTCOffsetFrom
- self.mRecurrences = copyit.mRecurrences
- self.mCachedExpandBelow = None
- self.mCachedExpandBelowItems = None
+ propertyCardinality_1 = (
+ definitions.cICalProperty_DTSTART,
+ definitions.cICalProperty_TZOFFSETTO,
+ definitions.cICalProperty_TZOFFSETFROM,
+ )
+ propertyCardinality_0_1 = (
+ definitions.cICalProperty_RRULE,
+ )
+
+ propertyValueChecks = ICALENDAR_VALUE_CHECKS
+
+ def __init__(self, parent=None, dt=None, offset=None):
+ super(PyCalendarVTimezoneElement, self).__init__(parent=parent)
+ self.mStart = dt if dt is not None else PyCalendarDateTime()
+ self.mTZName = ""
+ self.mUTCOffset = offset if offset is not None else 0
+ self.mUTCOffsetFrom = 0
+ self.mRecurrences = PyCalendarRecurrenceSet()
+ self.mCachedExpandBelow = None
+ self.mCachedExpandBelowItems = None
+
+
+ def duplicate(self, parent=None):
+ other = super(PyCalendarVTimezoneElement, self).duplicate(parent=parent)
+ other.mStart = self.mStart.duplicate()
+ other.mTZName = self.mTZName
+ other.mUTCOffset = self.mUTCOffset
+ other.mUTCOffsetFrom = self.mUTCOffsetFrom
+ other.mRecurrences = self.mRecurrences.duplicate()
+ other.mCachedExpandBelow = None
+ other.mCachedExpandBelowItems = None
+ return other
+
+
def finalise(self):
# Get DTSTART
temp = self.loadValueDateTime(definitions.cICalProperty_DTSTART)
@@ -82,20 +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
@@ -111,39 +131,37 @@
# that the recurrence
# cache is invalidated less frequently
- temp = PyCalendarDateTime(copyit=below)
- temp.setMonth(1)
- temp.setDay(1)
- temp.setHHMMSS(0, 0, 0)
- temp.offsetYear(1)
+ temp = PyCalendarDateTime(below.getYear(), 1, 1, 0, 0, 0)
# Use cache of expansion
if self.mCachedExpandBelowItems is None:
self.mCachedExpandBelowItems = []
if self.mCachedExpandBelow is None:
- self.mCachedExpandBelow = PyCalendarDateTime(copyit=self.mStart)
+ self.mCachedExpandBelow = self.mStart.duplicate()
if temp > self.mCachedExpandBelow:
self.mCachedExpandBelowItems = []
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
- for i in range(len(self.mCachedExpandBelowItems)):
- if self.mCachedExpandBelowItems[i] > below:
- if i != 0:
- return self.mCachedExpandBelowItems[i - 1]
- break
-
- # The last one in the list is the one we want
- return self.mCachedExpandBelowItems[len(self.mCachedExpandBelowItems) - 1]
+ 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):
+ def expandAll(self, start, end, with_name):
+
+ if start is None:
+ start = self.mStart
+
# Ignore if there is no change in offset
offsetto = self.loadValueInteger(definitions.cICalProperty_TZOFFSETTO, PyCalendarValue.VALUETYPE_UTC_OFFSET)
offsetfrom = self.loadValueInteger(definitions.cICalProperty_TZOFFSETFROM, PyCalendarValue.VALUETYPE_UTC_OFFSET)
@@ -157,7 +175,10 @@
elif not self.mRecurrences.hasRecurrence():
# Return DTSTART even if it is newer
if self.mStart >= start:
- return ((self.mStart, offsetfrom, offsetto),)
+ result = (self.mStart, offsetfrom, offsetto,)
+ if with_name:
+ result += (self.getTZName(),)
+ return (result,)
else:
return ()
else:
@@ -171,25 +192,28 @@
# that the recurrence
# cache is invalidated less frequently
- temp = PyCalendarDateTime(copyit=end)
- temp.setMonth(1)
- temp.setDay(1)
- temp.setHHMMSS(0, 0, 0)
- temp.offsetYear(1)
+ temp = PyCalendarDateTime(end.getYear(), 1, 1, 0, 0, 0)
# Use cache of expansion
if self.mCachedExpandBelowItems is None:
self.mCachedExpandBelowItems = []
if self.mCachedExpandBelow is None:
- self.mCachedExpandBelow = PyCalendarDateTime(copyit=self.mStart)
+ self.mCachedExpandBelow = self.mStart.duplicate()
if temp > self.mCachedExpandBelow:
self.mCachedExpandBelowItems = []
- period = PyCalendarPeriod(start, end)
+ 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:
- # The last one in the list is the one we want
- return [(dt, offsetfrom, offsetto,) for dt in self.mCachedExpandBelowItems]
+ # Return them all within the range
+ results = []
+ for dt in self.mCachedExpandBelowItems:
+ if dt >= start and dt < end:
+ result = (dt, offsetfrom, offsetto,)
+ if with_name:
+ result += (self.getTZName(),)
+ results.append(result)
+ return results
return ()
Modified: PyCalendar/trunk/src/pycalendar/vtimezonestandard.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/vtimezonestandard.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vtimezonestandard.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,38 +14,18 @@
# limitations under the License.
##
-from component import PyCalendarComponent
-from vtimezoneelement import PyCalendarVTimezoneElement
-import definitions
+from pycalendar import definitions
+from pycalendar.vtimezoneelement import PyCalendarVTimezoneElement
class PyCalendarVTimezoneStandard(PyCalendarVTimezoneElement):
- sBeginDelimiter = definitions.cICalComponent_BEGINSTANDARD
+ def __init__(self, parent=None):
+ super(PyCalendarVTimezoneStandard, self).__init__(parent=parent)
- sEndDelimiter = definitions.cICalComponent_ENDSTANDARD
- @staticmethod
- def getVBegin():
- return PyCalendarVTimezoneStandard.sBeginDelimiter
+ def duplicate(self, parent=None):
+ return super(PyCalendarVTimezoneStandard, self).duplicate(parent=parent)
- @staticmethod
- def getVEnd():
- return PyCalendarVTimezoneStandard.sEndDelimiter
- def __init__(self, calendar=None, copyit=None):
- if calendar is not None:
- super(PyCalendarVTimezoneStandard, self).__init__(calendar=calendar)
- elif copyit is not None:
- super(PyCalendarVTimezoneStandard, self).__init__(copyit=copyit)
-
- def clone_it(self):
- return PyCalendarVTimezoneStandard(copyit=self)
-
def getType(self):
- return PyCalendarComponent.eVTIMEZONESTANDARD
-
- def getBeginDelimiter(self):
- return PyCalendarVTimezoneStandard.sBeginDelimiter
-
- def getEndDelimiter(self):
- return PyCalendarVTimezoneStandard.sEndDelimiter
+ return definitions.cICalComponent_STANDARD
Modified: PyCalendar/trunk/src/pycalendar/vtodo.py
===================================================================
--- PyCalendar/trunk/src/pycalendar/vtodo.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/pycalendar/vtodo.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -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.
@@ -14,40 +14,23 @@
# limitations under the License.
##
+from pycalendar import definitions
+from pycalendar import itipdefinitions
+from pycalendar.componentrecur import PyCalendarComponentRecur
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS
+from pycalendar.property import PyCalendarProperty
import cStringIO as StringIO
-from component import PyCalendarComponent
-from componentrecur import PyCalendarComponentRecur
-from datetime import PyCalendarDateTime
-from property import PyCalendarProperty
-import definitions
-import itipdefinitions
-
class PyCalendarVToDo(PyCalendarComponentRecur):
OVERDUE = 0
-
- DUE_NOW= 1
-
+ DUE_NOW = 1
DUE_LATER = 2
-
DONE = 3
+ CANCELLED = 4
- CANCELLED= 4
-
- sBeginDelimiter = definitions.cICalComponent_BEGINVTODO
-
- sEndDelimiter = definitions.cICalComponent_ENDVTODO
-
@staticmethod
- def getVBegin():
- return PyCalendarVToDo.sBeginDelimiter
-
- @staticmethod
- def getVEnd():
- return PyCalendarVToDo.sEndDelimiter
-
- @staticmethod
def sort_for_display(e1, e2):
s1 = e1.getMaster()
s2 = e2.getMaster()
@@ -100,54 +83,78 @@
# Just use start time - older ones at the top
return s1.mStart < s2.mStart
- def __init__(self, calendar=None, copyit=None):
- if calendar is not None:
- super(PyCalendarVToDo, self).__init__(calendar=calendar)
- self.mPriority = 0
- self.mStatus = definitions.eStatus_VToDo_None
- self.mPercentComplete = 0
- self.mCompleted = PyCalendarDateTime()
- self.mHasCompleted = False
- elif copyit is not None:
- super(PyCalendarVToDo, self).__init__(copyit=copyit)
- self.mPriority = copyit.mPriority
- self.mStatus = copyit.mStatus
- self.mPercentComplete = copyit.mPercentComplete
- self.mCompleted = PyCalendarDateTime(copyit=copyit.mCompleted)
- self.mHasCompleted = copyit.mHasCompleted
+ propertyCardinality_1 = (
+ definitions.cICalProperty_DTSTAMP,
+ definitions.cICalProperty_UID,
+ )
- def clone_it(self):
- return PyCalendarVToDo(copyit=self)
+ propertyCardinality_0_1 = (
+ definitions.cICalProperty_CLASS,
+ definitions.cICalProperty_COMPLETED,
+ definitions.cICalProperty_CREATED,
+ definitions.cICalProperty_DESCRIPTION,
+ definitions.cICalProperty_DTSTART,
+ definitions.cICalProperty_GEO,
+ definitions.cICalProperty_LAST_MODIFIED,
+ definitions.cICalProperty_LOCATION,
+ definitions.cICalProperty_ORGANIZER,
+ definitions.cICalProperty_PERCENT_COMPLETE,
+ definitions.cICalProperty_PRIORITY,
+ definitions.cICalProperty_RECURRENCE_ID,
+ definitions.cICalProperty_SEQUENCE,
+ # definitions.cICalProperty_STATUS, # Special fix done for multiple STATUS
+ definitions.cICalProperty_SUMMARY,
+ definitions.cICalProperty_URL,
+ definitions.cICalProperty_RRULE,
+ definitions.cICalProperty_DUE,
+ definitions.cICalProperty_DURATION,
+ )
+ propertyValueChecks = ICALENDAR_VALUE_CHECKS
+
+ def __init__(self, parent=None):
+ super(PyCalendarVToDo, self).__init__(parent=parent)
+ self.mPriority = 0
+ self.mStatus = definitions.eStatus_VToDo_None
+ self.mPercentComplete = 0
+ self.mCompleted = PyCalendarDateTime()
+ self.mHasCompleted = False
+
+
+ def duplicate(self, parent=None):
+ other = super(PyCalendarVToDo, self).duplicate(parent=parent)
+ other.mPriority = self.mPriority
+ other.mStatus = self.mStatus
+ other.mPercentComplete = self.mPercentComplete
+ other.mCompleted = self.mCompleted.duplicate()
+ other.mHasCompleted = self.mHasCompleted
+ return other
+
+
def getType(self):
- return PyCalendarComponent.eVTODO
+ return definitions.cICalComponent_VTODO
- def getBeginDelimiter(self):
- return PyCalendarVToDo.sBeginDelimiter
- def getEndDelimiter(self):
- return PyCalendarVToDo.sEndDelimiter
-
def getMimeComponentName(self):
return itipdefinitions.cICalMIMEComponent_VTODO
+
def addComponent(self, comp):
# We can embed the alarm components only
- if comp.getType() == PyCalendarComponent.eVALARM:
- if self.mEmbedded is None:
- self.mEmbedded = []
- self.mEmbedded.append(comp)
- comp.setEmbedder(self)
- return True
+ if comp.getType() == definitions.cICalComponent_VALARM:
+ super(PyCalendarVToDo, self).addComponent(comp)
else:
- return False
+ 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,6 +274,43 @@
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.
+ """
+
+ fixed, unfixed = super(PyCalendarVToDo, self).validate(doFix)
+
+ # Extra constraint: only one of DUE or DURATION
+ if self.hasProperty(definitions.cICalProperty_DUE) and self.hasProperty(definitions.cICalProperty_DURATION):
+ # Fix by removing the DURATION
+ logProblem = "[%s] Properties must not both be present: %s, %s" % (
+ self.getType(),
+ definitions.cICalProperty_DUE,
+ definitions.cICalProperty_DURATION,
+ )
+ if doFix:
+ self.removeProperties(definitions.cICalProperty_DURATION)
+ 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
+ logProblem = "[%s] Property must be present: %s with %s" % (
+ self.getType(),
+ definitions.cICalProperty_DTSTART,
+ definitions.cICalProperty_DURATION,
+ )
+ unfixed.append(logProblem)
+
+ return fixed, unfixed
+
+
# Editing
def editStatus(self, status):
# Only if it is different
@@ -291,14 +341,26 @@
prop = PyCalendarProperty(definitions.cICalProperty_STATUS, value)
self.addProperty(prop)
+
def editCompleted(self, completed):
# Remove existing COMPLETED item
self.removeProperties(definitions.cICalProperty_COMPLETED)
self.mHasCompleted = False
# Always UTC
- self.mCompleted = PyCalendarDateTime(copyit=completed)
+ self.mCompleted = completed.duplicate()
self.mCompleted.adjustToUTC()
self.mHasCompleted = True
prop = PyCalendarProperty(definitions.cICalProperty_STATUS_COMPLETED, self.mCompleted)
self.addProperty(prop)
+
+
+ def sortedPropertyKeyOrder(self):
+ return (
+ definitions.cICalProperty_UID,
+ definitions.cICalProperty_RECURRENCE_ID,
+ definitions.cICalProperty_DTSTART,
+ definitions.cICalProperty_DURATION,
+ definitions.cICalProperty_DUE,
+ definitions.cICalProperty_COMPLETED,
+ )
Copied: PyCalendar/trunk/src/pycalendar/vunknown.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/vunknown.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/vunknown.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/vunknown.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,66 @@
+##
+# 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.
+##
+
+from pycalendar import definitions
+from pycalendar.component import PyCalendarComponent
+from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS
+import uuid
+
+class PyCalendarUnknownComponent(PyCalendarComponent):
+
+ propertyValueChecks = ICALENDAR_VALUE_CHECKS
+
+ def __init__(self, parent=None, comptype=""):
+ super(PyCalendarUnknownComponent, self).__init__(parent=parent)
+ 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,
+ )
Copied: PyCalendar/trunk/src/pycalendar/xmldefs.py (from rev 10553, PyCalendar/branches/server/src/pycalendar/xmldefs.py)
===================================================================
--- PyCalendar/trunk/src/pycalendar/xmldefs.py (rev 0)
+++ PyCalendar/trunk/src/pycalendar/xmldefs.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,104 @@
+##
+# 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.
+##
+
+import xml.etree.cElementTree as XML
+
+# iCalendar/vCard XML definitions
+
+iCalendar20_namespace = "urn:ietf:params:xml:ns:icalendar-2.0"
+
+icalendar = "icalendar"
+components = "components"
+properties = "properties"
+parameters = "parameters"
+
+value_binary = "binary"
+value_boolean = "boolean"
+value_cal_address = "cal-address"
+value_date = "date"
+value_date_time = "date-time"
+value_duration = "duration"
+value_integer = "integer"
+value_period = "period"
+value_recur = "recur"
+value_text = "text"
+value_unknown = "unknown"
+value_uri = "uri"
+value_utc_offset = "utc-offset"
+
+period_start = "start"
+period_end = "end"
+period_duration = "duration"
+
+recur_freq = "freq"
+recur_freq_secondly = "SECONDLY"
+recur_freq_minutely = "MINUTELY"
+recur_freq_hourly = "HOURLY"
+recur_freq_daily = "DAILY"
+recur_freq_weekly = "WEEKLY"
+recur_freq_monthly = "MONTHLY"
+recur_freq_yearly = "YEARLY"
+
+recur_count = "count"
+recur_until = "until"
+recur_interval = "interval"
+
+recur_bysecond = "bysecond"
+recur_byminute = "byminute"
+recur_byhour = "byhour"
+recur_byday = "byday"
+recur_bymonthday = "bymonthday"
+recur_byyearday = "byyearday"
+recur_byweekno = "byweekno"
+recur_bymonth = "bymonth"
+recur_bysetpos = "bysetpos"
+recur_wkst = "wkst"
+
+req_status_code = "code"
+req_status_description = "description"
+req_status_data = "data"
+
+vCard40_namespace = "urn:ietf:params:xml:ns:vcard-4.0"
+
+def makeTag(namespace, name):
+ return "{%s}%s" % (namespace, name.lower(),)
+
+
+
+def toString(root):
+
+ data = """<?xml version="1.0" encoding="utf-8"?>\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()):
+ indent = "\n" + " " * (level + 1) * INDENT
+ node.text = indent
+ for child in node.getchildren():
+ child.tail = indent
+ _indentNode(child, level + 1)
+ if len(node.getchildren()):
+ node.getchildren()[-1].tail = "\n" + " " * level * INDENT
+
+ _indentNode(root, 0)
+ data += XML.tostring(root) + "\n"
+
+ return data
Deleted: PyCalendar/trunk/src/zonal/__init__.py
===================================================================
--- PyCalendar/branches/server/src/zonal/__init__.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/zonal/__init__.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,15 +0,0 @@
-##
-# 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.
-##
Copied: PyCalendar/trunk/src/zonal/__init__.py (from rev 10553, PyCalendar/branches/server/src/zonal/__init__.py)
===================================================================
--- PyCalendar/trunk/src/zonal/__init__.py (rev 0)
+++ PyCalendar/trunk/src/zonal/__init__.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,15 @@
+##
+# 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.
+##
Deleted: PyCalendar/trunk/src/zonal/rule.py
===================================================================
--- PyCalendar/branches/server/src/zonal/rule.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/zonal/rule.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,574 +0,0 @@
-##
-# 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.
-##
-
-from pycalendar import definitions
-from pycalendar.datetime import PyCalendarDateTime
-from pycalendar.utils import daysInMonth
-from pycalendar.vtimezonestandard import PyCalendarVTimezoneStandard
-from pycalendar.utcoffsetvalue import PyCalendarUTCOffsetValue
-from pycalendar.vtimezonedaylight import PyCalendarVTimezoneDaylight
-from pycalendar.property import PyCalendarProperty
-from pycalendar.recurrence import PyCalendarRecurrence
-import utils
-
-"""
-Class that maintains a TZ data Rule.
-"""
-
-__all__ = (
- "Rule",
- "RuleSet",
-)
-
-class RuleSet(object):
- """
- 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(" ")
- name = splits[1]
- assert name, "Must have a zone name: '%s'" % (lines,)
- if not self.name:
- self.name = name
- assert self.name == name, "Different zone names %s and %s: %s" % (self.name, name,)
- 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
- starting at the beginning and going up to maxYear at most.
-
- @param results: list to append results to
- @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
- @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,
- }
-
- 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,
- }
-
- 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,
- }
-
- 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,
- }
-
- 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,
- }
-
- MONTH_POS_TO_NAME = ("", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
-
-
- def __init__(self):
- self.name = ""
- self.fromYear = ""
- self.toYear = ""
- self.type = "-"
- self.inMonth = 0
- self.onDay = ""
- 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
- self.fromYear == other.fromYear and
- self.toYear == other.toYear and
- self.type == other.type and
- self.inMonth == other.inMonth and
- self.onDay == other.onDay and
- self.atTime == other.atTime and
- self.saveTime == other.saveTime and
- 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,)
- self.name = splits[1]
- self.fromYear = splits[2]
- self.toYear = splits[3]
- self.type = splits[4]
- self.inMonth = splits[5]
- self.onDay = splits[6]
- 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 = (
- "Rule",
- self.name,
- self.fromYear,
- self.toYear,
- self.type,
- self.inMonth,
- self.onDay,
- self.atTime,
- self.saveTime,
- self.letter,
- )
-
- return "\t".join(items)
-
-
- def getOffset(self):
- """
- Return the specified rule offset in seconds.
-
- @return: C{int}
- """
- splits = self.saveTime.split(":")
- hours = int(splits[0])
- if len(splits) == 2:
- minutes = int(splits[1])
- else:
- minutes = 0
- negative = hours < 0
- if negative:
- return -((-hours * 60) + minutes) * 60
- else:
- return ((hours * 60) + minutes) * 60
-
-
- def startYear(self):
- return int(self.fromYear)
-
-
- def endYear(self):
- if self.toYear == "only":
- return self.startYear()
- elif self.toYear == "max":
- 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,)
- hours = int(splits[0])
- if len(splits[1]) > 2:
- minutes = int(splits[1][:2])
- special = splits[1][2:]
- else:
- minutes = int(splits[1])
- special = ""
-
- # Special case for 24:00
- if hours == 24 and minutes == 0:
- dt.setHours(23)
- dt.setMinutes(59)
- dt.setSeconds(59)
- 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])
- elif self.onDay.find(">=") != -1:
- splits = self.onDay.split(">=")
- dt.setNextDayOfWeek(int(splits[1]), Rule.DAY_NAME_TO_DAY[splits[0]])
- else:
- try:
- day = int(self.onDay)
- dt.setDay(day)
- except:
- assert False, "onDay value is not recognized: %s" % (self.onDay,)
-
- return dt, special
-
-
- def getOnDayDetails(self, start, indicatedDay, indicatedOffset):
- """
- Get RRULE BYxxx part items from the Rule data.
-
- @param start: start date-time for the recurrence set
- @type start: L{PyCalendarDateTime}
- @param indicatedDay: the day that the Rule indicates for recurrence
- @type indicatedDay: C{int}
- @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
- 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
- if month < 1:
- month = 12
- elif difference in (-1, 6,):
- assert indicatedOffset != 1, "Bad RRULE adjustment"
- indicatedOffset -= 1
- else:
- assert False, "Unknown RRULE adjustment"
-
- try:
- # Form the appropriate RRULE bits
- day = Rule.DAY_NAME_TO_RDAY[dayOfWeek]
- offset = indicatedOffset
- bymday = None
- if offset == 1:
- offset = 1
- elif offset == 8:
- offset = 2
- elif offset == 15:
- offset = 3
- elif offset == 22:
- offset = 4
- else:
- days_in_month = daysInMonth(month, year)
- if days_in_month - offset == 6:
- offset = -1
- elif days_in_month - offset == 13:
- offset = -2
- elif days_in_month - offset == 20:
- offset = -3
- else:
- bymday = [offset + i for i in range(7) if (offset + i) <= days_in_month]
- 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
-
- @param results: list to append results to
- @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
- @type maxYear: C{int}
- """
-
- if self.startYear() >= maxYear:
- return
-
- self.fullExpand(maxYear)
- zoneoffset = zoneinfo.getUTCOffset()
- offset = self.getOffset()
- 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
- a specified maximum.
-
- @param maxYear: maximum year to expand to
- @type maxYear: C{int}
- """
- if hasattr(self, "dt_cache"):
- return self.dt_cache
- start = self.startYear()
- end = self.endYear()
- if end > maxYear:
- end = maxYear - 1
- self.dt_cache = []
- for year in xrange(start, end + 1):
- 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
- @type zonerule: L{ZoneRule}
- @param start: the start time for the first instance
- @type start: L{PyCalendarDateTime}
- @param end: the start time for the last instance
- @type end: L{PyCalendarDateTime}
- @param offsetfrom: the UTC offset-from
- @type offsetfrom: C{int}
- @param offsetto: the UTC offset-to
- @type offsetto: C{int}
- @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:
- comp = PyCalendarVTimezoneStandard(parent=vtz)
- else:
- comp = PyCalendarVTimezoneDaylight(parent=vtz)
-
- # Do offsets
- tzoffsetfrom = PyCalendarUTCOffsetValue(offsetfrom)
- tzoffsetto = PyCalendarUTCOffsetValue(offsetto)
-
- 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))
-
- # Now determine the recurrences (use RDATE if only one year or
- # number of instances is one)
- if self.toYear != "only" and instanceCount != 1:
- rrule = PyCalendarRecurrence()
- 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 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 = (
- 365, 334, 306, 275, 245, 214, 184, 153, 122, 92, 61, 31, 0 # Does not account for leap year
- )
- rrule.setByYearDay([-(daysBackStartOfMonth[Rule.MONTH_NAME_TO_POS[self.inMonth]] + i) for i in range(7)])
- 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 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 = (
- 365, 334, 306, 275, 245, 214, 184, 153, 122, 92, 61, 31, 0 # Does not account for leap year
- )
- rrule.setByYearDay([-(daysBackStartOfMonth[Rule.MONTH_NAME_TO_POS[self.inMonth]] + i) for i in range(7)])
- rrule.setByDay(
- ((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)
- rrule.setByDay(((offset, rday),))
- else:
- try:
- _ignore_day = int(self.onDay)
- except:
- assert False, "onDay value is not recognized: %s" % (self.onDay,)
-
- # Add any UNTIL
- if zonerule.getUntilDate().dt.getYear() < 9999 or self.endYear() < 9999:
- until = end.duplicate()
- until.offsetSeconds(-offsetfrom)
- until.setTimezoneUTC(True)
- rrule.setUseUntil(True)
- rrule.setUntil(until)
-
- comp.addProperty(PyCalendarProperty(definitions.cICalProperty_RRULE, rrule))
- else:
- comp.addProperty(PyCalendarProperty(definitions.cICalProperty_RDATE, start))
-
- comp.finalise()
- vtz.addComponent(comp)
Copied: PyCalendar/trunk/src/zonal/rule.py (from rev 10553, PyCalendar/branches/server/src/zonal/rule.py)
===================================================================
--- PyCalendar/trunk/src/zonal/rule.py (rev 0)
+++ PyCalendar/trunk/src/zonal/rule.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,574 @@
+##
+# 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.
+##
+
+from pycalendar import definitions
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.utils import daysInMonth
+from pycalendar.vtimezonestandard import PyCalendarVTimezoneStandard
+from pycalendar.utcoffsetvalue import PyCalendarUTCOffsetValue
+from pycalendar.vtimezonedaylight import PyCalendarVTimezoneDaylight
+from pycalendar.property import PyCalendarProperty
+from pycalendar.recurrence import PyCalendarRecurrence
+import utils
+
+"""
+Class that maintains a TZ data Rule.
+"""
+
+__all__ = (
+ "Rule",
+ "RuleSet",
+)
+
+class RuleSet(object):
+ """
+ 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(" ")
+ name = splits[1]
+ assert name, "Must have a zone name: '%s'" % (lines,)
+ if not self.name:
+ self.name = name
+ assert self.name == name, "Different zone names %s and %s: %s" % (self.name, name,)
+ 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
+ starting at the beginning and going up to maxYear at most.
+
+ @param results: list to append results to
+ @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
+ @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,
+ }
+
+ 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,
+ }
+
+ 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,
+ }
+
+ 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,
+ }
+
+ 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,
+ }
+
+ MONTH_POS_TO_NAME = ("", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
+
+
+ def __init__(self):
+ self.name = ""
+ self.fromYear = ""
+ self.toYear = ""
+ self.type = "-"
+ self.inMonth = 0
+ self.onDay = ""
+ 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
+ self.fromYear == other.fromYear and
+ self.toYear == other.toYear and
+ self.type == other.type and
+ self.inMonth == other.inMonth and
+ self.onDay == other.onDay and
+ self.atTime == other.atTime and
+ self.saveTime == other.saveTime and
+ 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,)
+ self.name = splits[1]
+ self.fromYear = splits[2]
+ self.toYear = splits[3]
+ self.type = splits[4]
+ self.inMonth = splits[5]
+ self.onDay = splits[6]
+ 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 = (
+ "Rule",
+ self.name,
+ self.fromYear,
+ self.toYear,
+ self.type,
+ self.inMonth,
+ self.onDay,
+ self.atTime,
+ self.saveTime,
+ self.letter,
+ )
+
+ return "\t".join(items)
+
+
+ def getOffset(self):
+ """
+ Return the specified rule offset in seconds.
+
+ @return: C{int}
+ """
+ splits = self.saveTime.split(":")
+ hours = int(splits[0])
+ if len(splits) == 2:
+ minutes = int(splits[1])
+ else:
+ minutes = 0
+ negative = hours < 0
+ if negative:
+ return -((-hours * 60) + minutes) * 60
+ else:
+ return ((hours * 60) + minutes) * 60
+
+
+ def startYear(self):
+ return int(self.fromYear)
+
+
+ def endYear(self):
+ if self.toYear == "only":
+ return self.startYear()
+ elif self.toYear == "max":
+ 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,)
+ hours = int(splits[0])
+ if len(splits[1]) > 2:
+ minutes = int(splits[1][:2])
+ special = splits[1][2:]
+ else:
+ minutes = int(splits[1])
+ special = ""
+
+ # Special case for 24:00
+ if hours == 24 and minutes == 0:
+ dt.setHours(23)
+ dt.setMinutes(59)
+ dt.setSeconds(59)
+ 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])
+ elif self.onDay.find(">=") != -1:
+ splits = self.onDay.split(">=")
+ dt.setNextDayOfWeek(int(splits[1]), Rule.DAY_NAME_TO_DAY[splits[0]])
+ else:
+ try:
+ day = int(self.onDay)
+ dt.setDay(day)
+ except:
+ assert False, "onDay value is not recognized: %s" % (self.onDay,)
+
+ return dt, special
+
+
+ def getOnDayDetails(self, start, indicatedDay, indicatedOffset):
+ """
+ Get RRULE BYxxx part items from the Rule data.
+
+ @param start: start date-time for the recurrence set
+ @type start: L{PyCalendarDateTime}
+ @param indicatedDay: the day that the Rule indicates for recurrence
+ @type indicatedDay: C{int}
+ @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
+ 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
+ if month < 1:
+ month = 12
+ elif difference in (-1, 6,):
+ assert indicatedOffset != 1, "Bad RRULE adjustment"
+ indicatedOffset -= 1
+ else:
+ assert False, "Unknown RRULE adjustment"
+
+ try:
+ # Form the appropriate RRULE bits
+ day = Rule.DAY_NAME_TO_RDAY[dayOfWeek]
+ offset = indicatedOffset
+ bymday = None
+ if offset == 1:
+ offset = 1
+ elif offset == 8:
+ offset = 2
+ elif offset == 15:
+ offset = 3
+ elif offset == 22:
+ offset = 4
+ else:
+ days_in_month = daysInMonth(month, year)
+ if days_in_month - offset == 6:
+ offset = -1
+ elif days_in_month - offset == 13:
+ offset = -2
+ elif days_in_month - offset == 20:
+ offset = -3
+ else:
+ bymday = [offset + i for i in range(7) if (offset + i) <= days_in_month]
+ 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
+
+ @param results: list to append results to
+ @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
+ @type maxYear: C{int}
+ """
+
+ if self.startYear() >= maxYear:
+ return
+
+ self.fullExpand(maxYear)
+ zoneoffset = zoneinfo.getUTCOffset()
+ offset = self.getOffset()
+ 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
+ a specified maximum.
+
+ @param maxYear: maximum year to expand to
+ @type maxYear: C{int}
+ """
+ if hasattr(self, "dt_cache"):
+ return self.dt_cache
+ start = self.startYear()
+ end = self.endYear()
+ if end > maxYear:
+ end = maxYear - 1
+ self.dt_cache = []
+ for year in xrange(start, end + 1):
+ 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
+ @type zonerule: L{ZoneRule}
+ @param start: the start time for the first instance
+ @type start: L{PyCalendarDateTime}
+ @param end: the start time for the last instance
+ @type end: L{PyCalendarDateTime}
+ @param offsetfrom: the UTC offset-from
+ @type offsetfrom: C{int}
+ @param offsetto: the UTC offset-to
+ @type offsetto: C{int}
+ @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:
+ comp = PyCalendarVTimezoneStandard(parent=vtz)
+ else:
+ comp = PyCalendarVTimezoneDaylight(parent=vtz)
+
+ # Do offsets
+ tzoffsetfrom = PyCalendarUTCOffsetValue(offsetfrom)
+ tzoffsetto = PyCalendarUTCOffsetValue(offsetto)
+
+ 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))
+
+ # Now determine the recurrences (use RDATE if only one year or
+ # number of instances is one)
+ if self.toYear != "only" and instanceCount != 1:
+ rrule = PyCalendarRecurrence()
+ 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 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 = (
+ 365, 334, 306, 275, 245, 214, 184, 153, 122, 92, 61, 31, 0 # Does not account for leap year
+ )
+ rrule.setByYearDay([-(daysBackStartOfMonth[Rule.MONTH_NAME_TO_POS[self.inMonth]] + i) for i in range(7)])
+ 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 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 = (
+ 365, 334, 306, 275, 245, 214, 184, 153, 122, 92, 61, 31, 0 # Does not account for leap year
+ )
+ rrule.setByYearDay([-(daysBackStartOfMonth[Rule.MONTH_NAME_TO_POS[self.inMonth]] + i) for i in range(7)])
+ rrule.setByDay(
+ ((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)
+ rrule.setByDay(((offset, rday),))
+ else:
+ try:
+ _ignore_day = int(self.onDay)
+ except:
+ assert False, "onDay value is not recognized: %s" % (self.onDay,)
+
+ # Add any UNTIL
+ if zonerule.getUntilDate().dt.getYear() < 9999 or self.endYear() < 9999:
+ until = end.duplicate()
+ until.offsetSeconds(-offsetfrom)
+ until.setTimezoneUTC(True)
+ rrule.setUseUntil(True)
+ rrule.setUntil(until)
+
+ comp.addProperty(PyCalendarProperty(definitions.cICalProperty_RRULE, rrule))
+ else:
+ comp.addProperty(PyCalendarProperty(definitions.cICalProperty_RDATE, start))
+
+ comp.finalise()
+ vtz.addComponent(comp)
Deleted: PyCalendar/trunk/src/zonal/tests/__init__.py
===================================================================
--- PyCalendar/branches/server/src/zonal/tests/__init__.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/zonal/tests/__init__.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,15 +0,0 @@
-##
-# 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.
-##
Copied: PyCalendar/trunk/src/zonal/tests/__init__.py (from rev 10553, PyCalendar/branches/server/src/zonal/tests/__init__.py)
===================================================================
--- PyCalendar/trunk/src/zonal/tests/__init__.py (rev 0)
+++ PyCalendar/trunk/src/zonal/tests/__init__.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,15 @@
+##
+# 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.
+##
Deleted: PyCalendar/trunk/src/zonal/tests/test_rule.py
===================================================================
--- PyCalendar/branches/server/src/zonal/tests/test_rule.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/zonal/tests/test_rule.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,87 +0,0 @@
-##
-# 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.
-##
-
-import unittest
-from zonal.rule import Rule, RuleSet
-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",
- "Rule\tAlgeria\t1916\t1919\t-\tOct\tSun>=1\t23:00s\t0\t-",
- "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
-Rule\tUS\t1942\tonly\t-\tFeb\t9\t2:00\t1:00\tW
-Rule\tUS\t1945\tonly\t-\tAug\t14\t23:00u\t1:00\tP
-Rule\tUS\t1945\tonly\t-\tSep\t30\t2:00\t0\tS
-Rule\tUS\t1967\t2006\t-\tOct\tlastSun\t2:00\t0\tS
-Rule\tUS\t1967\t1973\t-\tApr\tlastSun\t2:00\t1:00\tD
-Rule\tUS\t1974\tonly\t-\tJan\t6\t2:00\t1:00\tD
-Rule\tUS\t1975\tonly\t-\tFeb\t23\t2:00\t1:00\tD
-Rule\tUS\t1976\t1986\t-\tApr\tlastSun\t2:00\t1:00\tD
-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)
-
- self.assertEqual(str(ruleset), data)
Copied: PyCalendar/trunk/src/zonal/tests/test_rule.py (from rev 10553, PyCalendar/branches/server/src/zonal/tests/test_rule.py)
===================================================================
--- PyCalendar/trunk/src/zonal/tests/test_rule.py (rev 0)
+++ PyCalendar/trunk/src/zonal/tests/test_rule.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,87 @@
+##
+# 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.
+##
+
+import unittest
+from zonal.rule import Rule, RuleSet
+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",
+ "Rule\tAlgeria\t1916\t1919\t-\tOct\tSun>=1\t23:00s\t0\t-",
+ "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
+Rule\tUS\t1942\tonly\t-\tFeb\t9\t2:00\t1:00\tW
+Rule\tUS\t1945\tonly\t-\tAug\t14\t23:00u\t1:00\tP
+Rule\tUS\t1945\tonly\t-\tSep\t30\t2:00\t0\tS
+Rule\tUS\t1967\t2006\t-\tOct\tlastSun\t2:00\t0\tS
+Rule\tUS\t1967\t1973\t-\tApr\tlastSun\t2:00\t1:00\tD
+Rule\tUS\t1974\tonly\t-\tJan\t6\t2:00\t1:00\tD
+Rule\tUS\t1975\tonly\t-\tFeb\t23\t2:00\t1:00\tD
+Rule\tUS\t1976\t1986\t-\tApr\tlastSun\t2:00\t1:00\tD
+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)
+
+ self.assertEqual(str(ruleset), data)
Deleted: PyCalendar/trunk/src/zonal/tests/test_zone.py
===================================================================
--- PyCalendar/branches/server/src/zonal/tests/test_zone.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/zonal/tests/test_zone.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,98 +0,0 @@
-##
-# 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.
-##
-
-import unittest
-from zonal.zone import Zone
-from zonal.rule import RuleSet
-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
-\t\t\t-5:00\tNYC\tE%sT\t1942
-\t\t\t-5:00\tUS\tE%sT\t1946
-\t\t\t-5:00\tNYC\tE%sT\t1967
-\t\t\t-5:00\tUS\tE%sT"""
- zone = Zone()
- zone.parse(zonedef)
-
- 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
-\t\t\t-5:00\tUS\tE%sT"""
- rules = """Rule\tUS\t1918\t1919\t-\tMar\tlastSun\t2:00\t1:00\tD
-Rule\tUS\t1918\t1919\t-\tOct\tlastSun\t2:00\t0\tS
-Rule\tUS\t1942\tonly\t-\tFeb\t9\t2:00\t1:00\tW
-Rule\tUS\t1945\tonly\t-\tAug\t14\t23:00u\t1:00\tP
-Rule\tUS\t1945\tonly\t-\tSep\t30\t2:00\t0\tS
-Rule\tUS\t1967\t2006\t-\tOct\tlastSun\t2:00\t0\tS
-Rule\tUS\t1967\t1973\t-\tApr\tlastSun\t2:00\t1:00\tD
-Rule\tUS\t1974\tonly\t-\tJan\t6\t2:00\t1:00\tD
-Rule\tUS\t1975\tonly\t-\tFeb\t23\t2:00\t1:00\tD
-Rule\tUS\t1976\t1986\t-\tApr\tlastSun\t2:00\t1:00\tD
-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"""
- result = """BEGIN:VTIMEZONE
-TZID:America/New_York
-X-LIC-LOCATION:America/New_York
-BEGIN:DAYLIGHT
-DTSTART:20060402T020000
-RDATE:20060402T020000
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:20061029T020000
-RDATE:20061029T020000
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-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
-""".replace("\n", "\r\n")
-
- zone = Zone()
- zone.parse(zonedef)
-
- ruleset = RuleSet()
- ruleset.parse(rules)
- rules = {ruleset.name: ruleset}
-
- cal = PyCalendar()
- vtz = zone.vtimezone(cal, rules, 2006, 2011)
-
- self.assertEqual(str(vtz), result)
Copied: PyCalendar/trunk/src/zonal/tests/test_zone.py (from rev 10553, PyCalendar/branches/server/src/zonal/tests/test_zone.py)
===================================================================
--- PyCalendar/trunk/src/zonal/tests/test_zone.py (rev 0)
+++ PyCalendar/trunk/src/zonal/tests/test_zone.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,98 @@
+##
+# 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.
+##
+
+import unittest
+from zonal.zone import Zone
+from zonal.rule import RuleSet
+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
+\t\t\t-5:00\tNYC\tE%sT\t1942
+\t\t\t-5:00\tUS\tE%sT\t1946
+\t\t\t-5:00\tNYC\tE%sT\t1967
+\t\t\t-5:00\tUS\tE%sT"""
+ zone = Zone()
+ zone.parse(zonedef)
+
+ 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
+\t\t\t-5:00\tUS\tE%sT"""
+ rules = """Rule\tUS\t1918\t1919\t-\tMar\tlastSun\t2:00\t1:00\tD
+Rule\tUS\t1918\t1919\t-\tOct\tlastSun\t2:00\t0\tS
+Rule\tUS\t1942\tonly\t-\tFeb\t9\t2:00\t1:00\tW
+Rule\tUS\t1945\tonly\t-\tAug\t14\t23:00u\t1:00\tP
+Rule\tUS\t1945\tonly\t-\tSep\t30\t2:00\t0\tS
+Rule\tUS\t1967\t2006\t-\tOct\tlastSun\t2:00\t0\tS
+Rule\tUS\t1967\t1973\t-\tApr\tlastSun\t2:00\t1:00\tD
+Rule\tUS\t1974\tonly\t-\tJan\t6\t2:00\t1:00\tD
+Rule\tUS\t1975\tonly\t-\tFeb\t23\t2:00\t1:00\tD
+Rule\tUS\t1976\t1986\t-\tApr\tlastSun\t2:00\t1:00\tD
+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"""
+ result = """BEGIN:VTIMEZONE
+TZID:America/New_York
+X-LIC-LOCATION:America/New_York
+BEGIN:DAYLIGHT
+DTSTART:20060402T020000
+RDATE:20060402T020000
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20061029T020000
+RDATE:20061029T020000
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+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
+""".replace("\n", "\r\n")
+
+ zone = Zone()
+ zone.parse(zonedef)
+
+ ruleset = RuleSet()
+ ruleset.parse(rules)
+ rules = {ruleset.name: ruleset}
+
+ cal = PyCalendar()
+ vtz = zone.vtimezone(cal, rules, 2006, 2011)
+
+ self.assertEqual(str(vtz), result)
Deleted: PyCalendar/trunk/src/zonal/tzconvert.py
===================================================================
--- PyCalendar/branches/server/src/zonal/tzconvert.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/zonal/tzconvert.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,310 +0,0 @@
-#!/usr/bin/env python
-##
-# 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.
-##
-
-from __future__ import with_statement
-
-from difflib import unified_diff
-from pycalendar.calendar import PyCalendar
-import cStringIO as StringIO
-import getopt
-import os
-import rule
-import sys
-import zone
-
-"""
-Classes to parse a tzdata files and generate VTIMEZONE data.
-"""
-
-__all__ = (
- "tzconvert",
-)
-
-class tzconvert(object):
-
- 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")
- ctr = 0
- for line in f:
- ctr += 1
- line = line[:-1]
- while True:
- if line.startswith("#") or len(line) == 0:
- break
- elif line.startswith("Rule"):
- self.parseRule(line)
- break
- elif line.startswith("Zone"):
- line = self.parseZone(line, f)
- if line is None:
- break
- elif line.startswith("Link"):
- self.parseLink(line)
- break
- elif len(line.strip()) != 0:
- assert False, "Could not parse line %d from tzconvert file: '%s'" % (ctr, line,)
- else:
- break
- 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)
- last_line = None
- for nextline in f:
- nextline = nextline[:-1]
- if nextline.startswith("\t"):
- os.write("\n")
- os.write(nextline)
- elif nextline.startswith("#") or len(nextline) == 0:
- continue
- else:
- last_line = nextline
- break
-
- 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.
- """
- 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
- try:
- for root, dirs, files in os.walk(outputdir, topdown=False):
- for name in files:
- os.remove(os.path.join(root, name))
- for name in dirs:
- os.rmdir(os.path.join(root, name))
- except OSError:
- pass
-
- for zone in self.zones.itervalues():
- if filterzones and zone.name not in filterzones:
- continue
- 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)
- 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)
- 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
-
- print """Usage: tzconvert [options] [DIR]
-Options:
- -h Print this help and exit
- --prodid PROD-ID string to use
- --start Start year
- --end End year
-
-Arguments:
- DIR Directory containing an Olson tzdata directory to read, also
- where zoneinfo data will be written
-
-Description:
- This utility convert Olson-style timezone data in iCalendar.
- VTIMEZONE objects, one .ics file per-timezone.
-
-"""
-
- if error_msg:
- raise ValueError(error_msg)
- else:
- sys.exit(0)
-
-
-if __name__ == '__main__':
-
- # Set the PRODID value used in generated iCalendar data
- prodid = "-//mulberrymail.com//Zonal//EN"
- rootdir = "../../stuff/temp"
- startYear = 1800
- endYear = 2018
-
- options, args = getopt.getopt(sys.argv[1:], "h", ["prodid=", "root=", "start=", "end=", ])
-
- for option, value in options:
- if option == "-h":
- usage()
- elif option == "--prodid":
- prodid = value
- elif option == "--root":
- rootdir = value
- elif option == "--start":
- startYear = int(value)
- elif option == "--end":
- endYear = int(value)
- else:
- usage("Unrecognized option: %s" % (option,))
-
- # Process arguments
- if len(args) > 1:
- usage("Must have only one argument")
- if len(args) == 1:
- rootdir = os.path.expanduser(args[0])
-
- PyCalendar.sProdID = prodid
-
- zonedir = os.path.join(rootdir, "tzdata")
- zonefiles = (
- "northamerica",
- "southamerica",
- "europe",
- "africa",
- "asia",
- "australasia",
- "antarctica",
- "etcetera",
- "backward",
- )
-
- parser = tzconvert(verbose=True)
- for file in zonefiles:
- parser.parse(os.path.join(zonedir, file))
-
- if 1:
- parser.generateZoneinfoFiles(os.path.join(rootdir, "zoneinfo"), startYear, endYear, filterzones=(
- #"America/Montevideo",
- #"Europe/Paris",
- #"Africa/Cairo",
- ))
-
- if 0:
- checkName = "EST"
- 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
- print "-- PARSED --"
- print parsed
- print
- print "-- DIFF --"
- print "\n".join([line for line in unified_diff(actual.split("\n"), parsed.split("\n"))])
Copied: PyCalendar/trunk/src/zonal/tzconvert.py (from rev 10553, PyCalendar/branches/server/src/zonal/tzconvert.py)
===================================================================
--- PyCalendar/trunk/src/zonal/tzconvert.py (rev 0)
+++ PyCalendar/trunk/src/zonal/tzconvert.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,310 @@
+#!/usr/bin/env python
+##
+# 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.
+##
+
+from __future__ import with_statement
+
+from difflib import unified_diff
+from pycalendar.calendar import PyCalendar
+import cStringIO as StringIO
+import getopt
+import os
+import rule
+import sys
+import zone
+
+"""
+Classes to parse a tzdata files and generate VTIMEZONE data.
+"""
+
+__all__ = (
+ "tzconvert",
+)
+
+class tzconvert(object):
+
+ 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")
+ ctr = 0
+ for line in f:
+ ctr += 1
+ line = line[:-1]
+ while True:
+ if line.startswith("#") or len(line) == 0:
+ break
+ elif line.startswith("Rule"):
+ self.parseRule(line)
+ break
+ elif line.startswith("Zone"):
+ line = self.parseZone(line, f)
+ if line is None:
+ break
+ elif line.startswith("Link"):
+ self.parseLink(line)
+ break
+ elif len(line.strip()) != 0:
+ assert False, "Could not parse line %d from tzconvert file: '%s'" % (ctr, line,)
+ else:
+ break
+ 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)
+ last_line = None
+ for nextline in f:
+ nextline = nextline[:-1]
+ if nextline.startswith("\t"):
+ os.write("\n")
+ os.write(nextline)
+ elif nextline.startswith("#") or len(nextline) == 0:
+ continue
+ else:
+ last_line = nextline
+ break
+
+ 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.
+ """
+ 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
+ try:
+ for root, dirs, files in os.walk(outputdir, topdown=False):
+ for name in files:
+ os.remove(os.path.join(root, name))
+ for name in dirs:
+ os.rmdir(os.path.join(root, name))
+ except OSError:
+ pass
+
+ for zone in self.zones.itervalues():
+ if filterzones and zone.name not in filterzones:
+ continue
+ 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)
+ 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)
+ 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
+
+ print """Usage: tzconvert [options] [DIR]
+Options:
+ -h Print this help and exit
+ --prodid PROD-ID string to use
+ --start Start year
+ --end End year
+
+Arguments:
+ DIR Directory containing an Olson tzdata directory to read, also
+ where zoneinfo data will be written
+
+Description:
+ This utility convert Olson-style timezone data in iCalendar.
+ VTIMEZONE objects, one .ics file per-timezone.
+
+"""
+
+ if error_msg:
+ raise ValueError(error_msg)
+ else:
+ sys.exit(0)
+
+
+if __name__ == '__main__':
+
+ # Set the PRODID value used in generated iCalendar data
+ prodid = "-//mulberrymail.com//Zonal//EN"
+ rootdir = "../../stuff/temp"
+ startYear = 1800
+ endYear = 2018
+
+ options, args = getopt.getopt(sys.argv[1:], "h", ["prodid=", "root=", "start=", "end=", ])
+
+ for option, value in options:
+ if option == "-h":
+ usage()
+ elif option == "--prodid":
+ prodid = value
+ elif option == "--root":
+ rootdir = value
+ elif option == "--start":
+ startYear = int(value)
+ elif option == "--end":
+ endYear = int(value)
+ else:
+ usage("Unrecognized option: %s" % (option,))
+
+ # Process arguments
+ if len(args) > 1:
+ usage("Must have only one argument")
+ if len(args) == 1:
+ rootdir = os.path.expanduser(args[0])
+
+ PyCalendar.sProdID = prodid
+
+ zonedir = os.path.join(rootdir, "tzdata")
+ zonefiles = (
+ "northamerica",
+ "southamerica",
+ "europe",
+ "africa",
+ "asia",
+ "australasia",
+ "antarctica",
+ "etcetera",
+ "backward",
+ )
+
+ parser = tzconvert(verbose=True)
+ for file in zonefiles:
+ parser.parse(os.path.join(zonedir, file))
+
+ if 1:
+ parser.generateZoneinfoFiles(os.path.join(rootdir, "zoneinfo"), startYear, endYear, filterzones=(
+ #"America/Montevideo",
+ #"Europe/Paris",
+ #"Africa/Cairo",
+ ))
+
+ if 0:
+ checkName = "EST"
+ 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
+ print "-- PARSED --"
+ print parsed
+ print
+ print "-- DIFF --"
+ print "\n".join([line for line in unified_diff(actual.split("\n"), parsed.split("\n"))])
Deleted: PyCalendar/trunk/src/zonal/tzdump.py
===================================================================
--- PyCalendar/branches/server/src/zonal/tzdump.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/zonal/tzdump.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,133 +0,0 @@
-#!/usr/bin/env python
-##
-# 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.
-##
-
-from pycalendar.calendar import PyCalendar
-from pycalendar.datetime import PyCalendarDateTime
-import os
-import sys
-import getopt
-from pycalendar.exceptions import PyCalendarInvalidData
-
-def loadCalendar(file, verbose):
-
- cal = PyCalendar()
- if verbose:
- print "Parsing calendar data: %s" % (file,)
- fin = open(file, "r")
- try:
- cal.parse(fin)
- except PyCalendarInvalidData, e:
- print "Failed to parse bad data: %s" % (e.mData,)
- 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]))
- return expanded
-
-
-
-def formattedExpandedDates(expanded):
- items = []
- for item in expanded:
- utc = item[0].duplicate()
- utc.setTimezoneUTC(True)
- utc.offsetSeconds(-item[1])
- 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
- negative = "-"
- else:
- negative = ""
- secs = divmod(seconds, 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
-
- print """Usage: tzdump [options] FILE
-Options:
- -h Print this help and exit
- -v Be verbose
- --start Start year
- --end End year
-
-Arguments:
- FILE iCalendar file containing a single VTIMEZONE
-
-Description:
- This utility will dump the transitions in a VTIMEZONE over
- the request time range.
-
-"""
-
- if error_msg:
- raise ValueError(error_msg)
- 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:
- if option == "-h":
- usage()
- elif option == "-v":
- verbose = True
- elif option == "--start":
- startYear = int(value)
- elif option == "--end":
- endYear = int(value)
- else:
- usage("Unrecognized option: %s" % (option,))
-
- # Process arguments
- if len(args) != 1:
- usage("Must have one argument")
- fpath = os.path.expanduser(args[0])
-
- start = PyCalendarDateTime(year=startYear, month=1, day=1)
- end = PyCalendarDateTime(year=endYear, month=1, day=1)
-
- cal = loadCalendar(fpath, verbose)
- dates = getExpandedDates(cal, start, end)
- print formattedExpandedDates(dates)
Copied: PyCalendar/trunk/src/zonal/tzdump.py (from rev 10553, PyCalendar/branches/server/src/zonal/tzdump.py)
===================================================================
--- PyCalendar/trunk/src/zonal/tzdump.py (rev 0)
+++ PyCalendar/trunk/src/zonal/tzdump.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+##
+# 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.
+##
+
+from pycalendar.calendar import PyCalendar
+from pycalendar.datetime import PyCalendarDateTime
+import os
+import sys
+import getopt
+from pycalendar.exceptions import PyCalendarInvalidData
+
+def loadCalendar(file, verbose):
+
+ cal = PyCalendar()
+ if verbose:
+ print "Parsing calendar data: %s" % (file,)
+ fin = open(file, "r")
+ try:
+ cal.parse(fin)
+ except PyCalendarInvalidData, e:
+ print "Failed to parse bad data: %s" % (e.mData,)
+ 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]))
+ return expanded
+
+
+
+def formattedExpandedDates(expanded):
+ items = []
+ for item in expanded:
+ utc = item[0].duplicate()
+ utc.setTimezoneUTC(True)
+ utc.offsetSeconds(-item[1])
+ 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
+ negative = "-"
+ else:
+ negative = ""
+ secs = divmod(seconds, 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
+
+ print """Usage: tzdump [options] FILE
+Options:
+ -h Print this help and exit
+ -v Be verbose
+ --start Start year
+ --end End year
+
+Arguments:
+ FILE iCalendar file containing a single VTIMEZONE
+
+Description:
+ This utility will dump the transitions in a VTIMEZONE over
+ the request time range.
+
+"""
+
+ if error_msg:
+ raise ValueError(error_msg)
+ 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:
+ if option == "-h":
+ usage()
+ elif option == "-v":
+ verbose = True
+ elif option == "--start":
+ startYear = int(value)
+ elif option == "--end":
+ endYear = int(value)
+ else:
+ usage("Unrecognized option: %s" % (option,))
+
+ # Process arguments
+ if len(args) != 1:
+ usage("Must have one argument")
+ fpath = os.path.expanduser(args[0])
+
+ start = PyCalendarDateTime(year=startYear, month=1, day=1)
+ end = PyCalendarDateTime(year=endYear, month=1, day=1)
+
+ cal = loadCalendar(fpath, verbose)
+ dates = getExpandedDates(cal, start, end)
+ print formattedExpandedDates(dates)
Deleted: PyCalendar/trunk/src/zonal/tzverify.py
===================================================================
--- PyCalendar/branches/server/src/zonal/tzverify.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/zonal/tzverify.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,283 +0,0 @@
-#!/usr/bin/env python
-##
-# 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.
-##
-
-from pycalendar.calendar import PyCalendar
-from pycalendar.datetime import PyCalendarDateTime
-from tzconvert import tzconvert
-import os
-import sys
-import getopt
-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 = []
- def scanForICS(dirpath):
- for fname in os.listdir(dirpath):
- fpath = os.path.join(dirpath, fname)
- if os.path.isdir(fpath):
- scanForICS(fpath)
- elif fname.endswith(".ics"):
- for skip in skips:
- if skip in fpath:
- break
- else:
- if verbose:
- print "Found calendar data: %s" % (fpath,)
- paths.append(fpath)
- scanForICS(zoneinfopath)
-
- if not quiet:
- print "Parsing calendar data in: %s" % (zoneinfopath,)
- return loadCalendar(paths, verbose)
-
-
-
-def loadCalendar(files, verbose):
-
- cal = PyCalendar()
- for file in files:
- if verbose:
- print "Parsing calendar data: %s" % (file,)
- fin = open(file, "r")
- try:
- cal.parse(fin)
- except PyCalendarInvalidData, e:
- print "Failed to parse bad data: %s" % (e.mData,)
- raise
- return CalendarZonesWrapper(calendar=cal)
-
-
-
-def parseTZData(zonedir, zonefiles):
- parser = tzconvert()
- for file in zonefiles:
- zonefile = os.path.join(zonedir, file)
- if not os.path.exists(zonefile):
- print "Zone file '%s' does not exist." % (zonefile,)
- 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
- if not quiet:
- print "\nChecking TZID: %s" % (tzid,)
- calendardates1 = calendar1.expandTransitions(tzid, start, end)
- calendardates2 = calendar2.expandTransitions(tzid, start, end)
- if verbose:
- print "Calendar 1 dates: %s" % (formattedExpandedDates(calendardates1),)
- print "Calendar 2 dates: %s" % (formattedExpandedDates(calendardates2),)
- set1 = set(calendardates1)
- set2 = set(calendardates2)
- d1 = set1.difference(set2)
- for i in set(d1):
- if i[0] == start:
- d1.discard(i)
- break
- if i[1] == i[2]:
- d1.discard(i)
- d2 = set2.difference(set1)
- for i in set(d2):
- if i[1] == i[2]:
- d2.discard(i)
- if d1:
- print "In calendar 1 but not in calendar 2 tzid=%s: %s" % (tzid, formattedExpandedDates(d1),)
- if d2:
- 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()
-
- db = cal.getVTimezoneDB()
- for vtz in db:
- tzid = vtz.getID()
- results.add(tzid)
-
- 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]))
- 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
- negative = "-"
- else:
- negative = ""
- secs = divmod(seconds, 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
-
- print """Usage: tzverify [options] DIR1 DIR2
-Options:
- -h Print this help and exit
- -v Be verbose
- -q Be quiet
- --start Start year
- --end End year
-
-Arguments:
- DIR1 Directories containing two sets of zoneinfo data
- DIR2 to be compared
-
-Description:
- This utility will compare iCalendar zoneinfo hierarchies by expanding
- timezone transitions and comparing.
-
-"""
-
- if error_msg:
- raise ValueError(error_msg)
- else:
- sys.exit(0)
-
-if __name__ == '__main__':
-
- verbose = False
- quiet = False
- startYear = 1933
- endYear = 2018
- zonedir1 = None
- zonedir2 = None
-
- options, args = getopt.getopt(sys.argv[1:], "hvq", ["start=", "end=", ])
-
- for option, value in options:
- if option == "-h":
- usage()
- elif option == "-v":
- verbose = True
- elif option == "-q":
- quiet = True
- elif option == "--start":
- startYear = int(value)
- elif option == "--end":
- endYear = int(value)
- else:
- usage("Unrecognized option: %s" % (option,))
-
- # Process arguments
- if len(args) != 2:
- usage("Must have two arguments")
- zonedir1 = os.path.expanduser(args[0])
- zonedir2 = os.path.expanduser(args[1])
-
- start = PyCalendarDateTime(year=startYear, month=1, day=1)
- end = PyCalendarDateTime(year=endYear, month=1, day=1)
-
- zonefiles = (
- "northamerica",
- "southamerica",
- "europe",
- "africa",
- "asia",
- "australasia",
- "antarctica",
- )
-
- skips = (
- #"Europe/Sofia",
- #"Africa/Cairo",
- )
-
- checkcalendar1 = loadCalendarFromZoneinfo(zonedir1, skips, verbose, quiet)
- checkcalendar2 = loadCalendarFromZoneinfo(zonedir2, skips, verbose, quiet)
-
- compareCalendars(
- checkcalendar1,
- checkcalendar2,
- start,
- end,
- filterTzids=(
- #"America/Goose_Bay",
- ),
- verbose=verbose,
- quiet=quiet,
- )
Copied: PyCalendar/trunk/src/zonal/tzverify.py (from rev 10553, PyCalendar/branches/server/src/zonal/tzverify.py)
===================================================================
--- PyCalendar/trunk/src/zonal/tzverify.py (rev 0)
+++ PyCalendar/trunk/src/zonal/tzverify.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,283 @@
+#!/usr/bin/env python
+##
+# 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.
+##
+
+from pycalendar.calendar import PyCalendar
+from pycalendar.datetime import PyCalendarDateTime
+from tzconvert import tzconvert
+import os
+import sys
+import getopt
+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 = []
+ def scanForICS(dirpath):
+ for fname in os.listdir(dirpath):
+ fpath = os.path.join(dirpath, fname)
+ if os.path.isdir(fpath):
+ scanForICS(fpath)
+ elif fname.endswith(".ics"):
+ for skip in skips:
+ if skip in fpath:
+ break
+ else:
+ if verbose:
+ print "Found calendar data: %s" % (fpath,)
+ paths.append(fpath)
+ scanForICS(zoneinfopath)
+
+ if not quiet:
+ print "Parsing calendar data in: %s" % (zoneinfopath,)
+ return loadCalendar(paths, verbose)
+
+
+
+def loadCalendar(files, verbose):
+
+ cal = PyCalendar()
+ for file in files:
+ if verbose:
+ print "Parsing calendar data: %s" % (file,)
+ fin = open(file, "r")
+ try:
+ cal.parse(fin)
+ except PyCalendarInvalidData, e:
+ print "Failed to parse bad data: %s" % (e.mData,)
+ raise
+ return CalendarZonesWrapper(calendar=cal)
+
+
+
+def parseTZData(zonedir, zonefiles):
+ parser = tzconvert()
+ for file in zonefiles:
+ zonefile = os.path.join(zonedir, file)
+ if not os.path.exists(zonefile):
+ print "Zone file '%s' does not exist." % (zonefile,)
+ 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
+ if not quiet:
+ print "\nChecking TZID: %s" % (tzid,)
+ calendardates1 = calendar1.expandTransitions(tzid, start, end)
+ calendardates2 = calendar2.expandTransitions(tzid, start, end)
+ if verbose:
+ print "Calendar 1 dates: %s" % (formattedExpandedDates(calendardates1),)
+ print "Calendar 2 dates: %s" % (formattedExpandedDates(calendardates2),)
+ set1 = set(calendardates1)
+ set2 = set(calendardates2)
+ d1 = set1.difference(set2)
+ for i in set(d1):
+ if i[0] == start:
+ d1.discard(i)
+ break
+ if i[1] == i[2]:
+ d1.discard(i)
+ d2 = set2.difference(set1)
+ for i in set(d2):
+ if i[1] == i[2]:
+ d2.discard(i)
+ if d1:
+ print "In calendar 1 but not in calendar 2 tzid=%s: %s" % (tzid, formattedExpandedDates(d1),)
+ if d2:
+ 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()
+
+ db = cal.getVTimezoneDB()
+ for vtz in db:
+ tzid = vtz.getID()
+ results.add(tzid)
+
+ 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]))
+ 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
+ negative = "-"
+ else:
+ negative = ""
+ secs = divmod(seconds, 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
+
+ print """Usage: tzverify [options] DIR1 DIR2
+Options:
+ -h Print this help and exit
+ -v Be verbose
+ -q Be quiet
+ --start Start year
+ --end End year
+
+Arguments:
+ DIR1 Directories containing two sets of zoneinfo data
+ DIR2 to be compared
+
+Description:
+ This utility will compare iCalendar zoneinfo hierarchies by expanding
+ timezone transitions and comparing.
+
+"""
+
+ if error_msg:
+ raise ValueError(error_msg)
+ else:
+ sys.exit(0)
+
+if __name__ == '__main__':
+
+ verbose = False
+ quiet = False
+ startYear = 1933
+ endYear = 2018
+ zonedir1 = None
+ zonedir2 = None
+
+ options, args = getopt.getopt(sys.argv[1:], "hvq", ["start=", "end=", ])
+
+ for option, value in options:
+ if option == "-h":
+ usage()
+ elif option == "-v":
+ verbose = True
+ elif option == "-q":
+ quiet = True
+ elif option == "--start":
+ startYear = int(value)
+ elif option == "--end":
+ endYear = int(value)
+ else:
+ usage("Unrecognized option: %s" % (option,))
+
+ # Process arguments
+ if len(args) != 2:
+ usage("Must have two arguments")
+ zonedir1 = os.path.expanduser(args[0])
+ zonedir2 = os.path.expanduser(args[1])
+
+ start = PyCalendarDateTime(year=startYear, month=1, day=1)
+ end = PyCalendarDateTime(year=endYear, month=1, day=1)
+
+ zonefiles = (
+ "northamerica",
+ "southamerica",
+ "europe",
+ "africa",
+ "asia",
+ "australasia",
+ "antarctica",
+ )
+
+ skips = (
+ #"Europe/Sofia",
+ #"Africa/Cairo",
+ )
+
+ checkcalendar1 = loadCalendarFromZoneinfo(zonedir1, skips, verbose, quiet)
+ checkcalendar2 = loadCalendarFromZoneinfo(zonedir2, skips, verbose, quiet)
+
+ compareCalendars(
+ checkcalendar1,
+ checkcalendar2,
+ start,
+ end,
+ filterTzids=(
+ #"America/Goose_Bay",
+ ),
+ verbose=verbose,
+ quiet=quiet,
+ )
Deleted: PyCalendar/trunk/src/zonal/utils.py
===================================================================
--- PyCalendar/branches/server/src/zonal/utils.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/zonal/utils.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,53 +0,0 @@
-##
-# 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.
-##
-
-class DateTime(object):
- """
- 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):
- 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":
- new_dt.offsetSeconds(offset)
- 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":
- pass
- elif self.mode == "s":
- new_dt.offsetSeconds(-stdoffset)
- else:
- new_dt.offsetSeconds(-offset)
- return new_dt
Copied: PyCalendar/trunk/src/zonal/utils.py (from rev 10553, PyCalendar/branches/server/src/zonal/utils.py)
===================================================================
--- PyCalendar/trunk/src/zonal/utils.py (rev 0)
+++ PyCalendar/trunk/src/zonal/utils.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,53 @@
+##
+# 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.
+##
+
+class DateTime(object):
+ """
+ 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):
+ 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":
+ new_dt.offsetSeconds(offset)
+ 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":
+ pass
+ elif self.mode == "s":
+ new_dt.offsetSeconds(-stdoffset)
+ else:
+ new_dt.offsetSeconds(-offset)
+ return new_dt
Deleted: PyCalendar/trunk/src/zonal/zone.py
===================================================================
--- PyCalendar/branches/server/src/zonal/zone.py 2013-01-26 17:29:10 UTC (rev 10553)
+++ PyCalendar/trunk/src/zonal/zone.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -1,548 +0,0 @@
-##
-# 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.
-##
-
-from pycalendar.datetime import PyCalendarDateTime
-from pycalendar.vtimezone import PyCalendarVTimezone
-from pycalendar.property import PyCalendarProperty
-from pycalendar import definitions
-from pycalendar.vtimezonestandard import PyCalendarVTimezoneStandard
-from pycalendar.utcoffsetvalue import PyCalendarUTCOffsetValue
-import utils
-import rule
-
-"""
-Class that maintains a TZ data Zone.
-"""
-
-__all__ = (
- "Zone",
- "ZoneRule",
-)
-
-class Zone(object):
- """
- 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]
- self.name = splits[1]
- rule = ZoneRule(self)
- rule.parse(line, 0)
- self.rules.append(rule)
- for line in splitlines[1:]:
- if len(line) == 0:
- continue
- rule = ZoneRule(self)
- 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:
- items = (
- "Zone " + self.name,
- rule.generate(),
- )
- else:
- items = (
- "",
- "",
- "",
- rule.generate(),
- )
- lines.append("\t".join(items))
- return "\n".join(lines)
-
-
- def expand(self, rules, minYear, maxYear):
- """
- Expand this zone into a set of transitions.
-
- @param rules: parsed Rules for the tzdb
- @type rules: C{dict}
- @param minYear: starting year
- @type minYear: C{int}
- @param maxYear: ending year
- @type maxYear: C{int}
-
- @return: C{list} of C{tuple} for (
- transition date-time,
- offset to,
- offset from,
- 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()
- last_offset = start_offset
- last_stdoffset = start_stdoffset
- first = True
- for zonerule in self.rules:
- 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]))
-
- # Now scan transitions looking for real changes and note those
- results = []
- last_transition = (startdt, start_offset, start_offset)
- for transition in transitions:
- dtutc, to_offset, zonerule, rule = transition
- dt = dtutc.duplicate()
- dt.offsetSeconds(last_transition[1])
-
- if dtutc.getYear() >= minYear:
- if dt > last_transition[0]:
- results.append((dt, last_transition[1], to_offset, zonerule, rule))
- elif dt <= last_transition[0]:
- if len(results):
- results[-1] = ((results[-1][0], results[-1][1], to_offset, zonerule, None))
- 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.
- @param startYear: a C{int} containing the first year that should be present
- 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
- for rule in ruleorder:
- if rule:
- # Accumulate rule portions with the same offset pairs
- lastOffsetPair = (rulemap[rule][0][1], rulemap[rule][0][2],)
- startIndex = 0
- for index in xrange(len(rulemap[rule])):
- offsetPair = (rulemap[rule][index][1], rulemap[rule][index][2],)
- if offsetPair != lastOffsetPair:
- rule.vtimezone(
- vtz,
- lastZoneRule,
- rulemap[rule][startIndex][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,
- rulemap[rule][startIndex][0],
- rulemap[rule][index][0],
- rulemap[rule][startIndex][1],
- rulemap[rule][startIndex][2],
- len(rulemap[rule]),
- )
- else:
- lastZoneRule.vtimezone(
- vtz,
- lastZoneRule,
- rulemap[rule][0][0],
- rulemap[rule][-1][0],
- rulemap[rule][0][1],
- rulemap[rule][0][2],
- )
- del ruleorder[:]
- 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:
- _generateRuleData()
- if rule not in ruleorder:
- ruleorder.append(rule)
- rulemap.setdefault(rule, []).append((dt, offsetfrom, offsetto,))
- lastZoneRule = zonerule
-
- # Do left overs
- _generateRuleData()
-
- self._compressRDateComponents(vtz)
-
- vtz.finalise()
- return vtz
-
-
- def _compressRDateComponents(self, vtz):
- """
- Compress sub-components with RDATEs into a single component with multiple
- RDATEs assuming all other properties are the same.
-
- @param vtz: the VTIMEZONE object to compress
- @type vtz: L{PyCalendarVTimezone}
- """
-
- # Map the similar sub-components together
- similarMap = {}
- for item in vtz.mComponents:
- item.finalise()
- key = (
- item.getType(),
- item.getTZName(),
- item.getUTCOffset(),
- item.getUTCOffsetFrom(),
- )
- if item.hasProperty(definitions.cICalProperty_RDATE):
- similarMap.setdefault(key, []).append(item)
-
- # Merge similar
- for values in similarMap.itervalues():
- if len(values) > 1:
- mergeTo = values[0]
- for mergeFrom in values[1:]:
- # Copy RDATE from to and remove from actual timezone
- prop = mergeFrom.getProperties()[definitions.cICalProperty_RDATE][0]
- 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
- self.rule == other.rule and
- self.format == other.format and
- 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]
- self.rule = splits[3 - offset]
- 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 = (
- self.gmtoff,
- self.rule,
- self.format,
- )
- if self.until:
- items = items + (self.until,)
- return "\t".join(items)
-
-
- def getUntilDate(self):
-
- if hasattr(self, "_cached_until"):
- return self._cached_until
-
- year = 9999
- month = 12
- day = 1
- hours = 0
- minutes = 0
- seconds = 0
- mode = None
- if self.until and not self.until.startswith("#"):
- splits = self.until.split(" ")
- year = int(splits[0])
- month = 1
- day = 1
- hours = 0
- minutes = 0
- seconds = 0
- mode = None
- if len(splits) > 1 and not splits[1].startswith("#"):
- month = int(rule.Rule.MONTH_NAME_TO_POS[splits[1]])
- if len(splits) > 2 and not splits[2].startswith("#"):
- if splits[2] == "lastSun":
- dt = PyCalendarDateTime(year=year, month=month, day=1)
- dt.setDayOfWeekInMonth(-1, PyCalendarDateTime.SUNDAY)
- splits[2] = dt.getDay()
- elif splits[2] == "lastSat":
- dt = PyCalendarDateTime(year=year, month=month, day=1)
- dt.setDayOfWeekInMonth(-1, PyCalendarDateTime.SATURDAY)
- splits[2] = dt.getDay()
- elif splits[2] == "Sun>=1":
- dt = PyCalendarDateTime(year=year, month=month, day=1)
- dt.setDayOfWeekInMonth(1, PyCalendarDateTime.SUNDAY)
- splits[2] = dt.getDay()
- day = int(splits[2])
- if len(splits) > 3 and not splits[3].startswith("#"):
- splits = splits[3].split(":")
- hours = int(splits[0])
- minutes = int(splits[1][:2])
- if len(splits[1]) > 2:
- mode = splits[1][2:]
- else:
- mode = None
- if len(splits) > 2:
- seconds = int(splits[2])
-
- dt = PyCalendarDateTime(year=year, month=month, day=day, hours=hours, minutes=minutes, seconds=seconds)
- 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
- negative = splits[0][0] == "-"
- self._cached_uutc_offset = ((hours * 60) + minutes) * 60 + seconds
- if negative:
- 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 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)
-
- # Sort the results by date
- tempresults.sort(cmp=lambda x, y: x[0].compareDateTime(y[0]))
-
- found_one = False
- found_start = False
- last_offset = lastOffset
- last_stdoffset = lastStdOffset
- finalUntil = self.getUntilDate()
- for dt, to_offset, rule in tempresults:
- dtutc = dt.getUTC(last_offset, last_stdoffset)
- if dtutc >= lastUntilUTC:
- if not found_start and dtutc != lastUntilUTC:
- # Insert a start item
- if not found_one:
- last_offset = self.getUTCOffset()
- last_stdoffset = self.getUTCOffset()
- 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
-
- results.append((dtutc, to_offset, self, rule))
-
- last_offset = to_offset
- last_stdoffset = self.getUTCOffset()
- found_one = True
-
- 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():
- splits = self.rule.split(":")
- to_offset = 60 * 60 * int(splits[0])
- if len(splits) > 1:
- to_offset += 60 * int(splits[1])
-
- # Always add a transition for the start of this rule
- 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)
-
- # Do offsets
- tzoffsetfrom = PyCalendarUTCOffsetValue(offsetfrom)
- tzoffsetto = PyCalendarUTCOffsetValue(offsetto)
-
- 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))
-
- # Recurrence
- comp.addProperty(PyCalendarProperty(definitions.cICalProperty_RDATE, start))
-
- comp.finalise()
- vtz.addComponent(comp)
-
-if __name__ == '__main__':
- rulesdef = """Rule\tUS\t1918\t1919\t-\tMar\tlastSun\t2:00\t1:00\tD
-Rule\tUS\t1918\t1919\t-\tOct\tlastSun\t2:00\t0\tS
-Rule\tUS\t1942\tonly\t-\tFeb\t9\t2:00\t1:00\tW # War
-Rule\tUS\t1945\tonly\t-\tAug\t14\t23:00u\t1:00\tP # Peace
-Rule\tUS\t1945\tonly\t-\tSep\t30\t2:00\t0\tS
-Rule\tUS\t1967\t2006\t-\tOct\tlastSun\t2:00\t0\tS
-Rule\tUS\t1967\t1973\t-\tApr\tlastSun\t2:00\t1:00\tD
-Rule\tUS\t1974\tonly\t-\tJan\t6\t2:00\t1:00\tD
-Rule\tUS\t1975\tonly\t-\tFeb\t23\t2:00\t1:00\tD
-Rule\tUS\t1976\t1986\t-\tApr\tlastSun\t2:00\t1:00\tD
-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"""
- rules = {}
- import rule
- ruleset = rule.RuleSet()
- ruleset.parse(rulesdef)
- rules[ruleset.name] = ruleset
-
- zonedef = """Zone America/New_York -4:56:02\t-\tLMT\t1883 Nov 18 12:03:58
-\t\t\t-5:00\tUS\tE%sT\t1920
-\t\t\t-5:00\tNYC\tE%sT\t1942
-\t\t\t-5:00\tUS\tE%sT\t1946
-\t\t\t-5:00\tNYC\tE%sT\t1967
-\t\t\t-5:00\tUS\tE%sT"""
- zone = Zone()
- zone.parse(zonedef)
Copied: PyCalendar/trunk/src/zonal/zone.py (from rev 10553, PyCalendar/branches/server/src/zonal/zone.py)
===================================================================
--- PyCalendar/trunk/src/zonal/zone.py (rev 0)
+++ PyCalendar/trunk/src/zonal/zone.py 2013-01-26 18:24:59 UTC (rev 10554)
@@ -0,0 +1,548 @@
+##
+# 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.
+##
+
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.vtimezone import PyCalendarVTimezone
+from pycalendar.property import PyCalendarProperty
+from pycalendar import definitions
+from pycalendar.vtimezonestandard import PyCalendarVTimezoneStandard
+from pycalendar.utcoffsetvalue import PyCalendarUTCOffsetValue
+import utils
+import rule
+
+"""
+Class that maintains a TZ data Zone.
+"""
+
+__all__ = (
+ "Zone",
+ "ZoneRule",
+)
+
+class Zone(object):
+ """
+ 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]
+ self.name = splits[1]
+ rule = ZoneRule(self)
+ rule.parse(line, 0)
+ self.rules.append(rule)
+ for line in splitlines[1:]:
+ if len(line) == 0:
+ continue
+ rule = ZoneRule(self)
+ 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:
+ items = (
+ "Zone " + self.name,
+ rule.generate(),
+ )
+ else:
+ items = (
+ "",
+ "",
+ "",
+ rule.generate(),
+ )
+ lines.append("\t".join(items))
+ return "\n".join(lines)
+
+
+ def expand(self, rules, minYear, maxYear):
+ """
+ Expand this zone into a set of transitions.
+
+ @param rules: parsed Rules for the tzdb
+ @type rules: C{dict}
+ @param minYear: starting year
+ @type minYear: C{int}
+ @param maxYear: ending year
+ @type maxYear: C{int}
+
+ @return: C{list} of C{tuple} for (
+ transition date-time,
+ offset to,
+ offset from,
+ 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()
+ last_offset = start_offset
+ last_stdoffset = start_stdoffset
+ first = True
+ for zonerule in self.rules:
+ 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]))
+
+ # Now scan transitions looking for real changes and note those
+ results = []
+ last_transition = (startdt, start_offset, start_offset)
+ for transition in transitions:
+ dtutc, to_offset, zonerule, rule = transition
+ dt = dtutc.duplicate()
+ dt.offsetSeconds(last_transition[1])
+
+ if dtutc.getYear() >= minYear:
+ if dt > last_transition[0]:
+ results.append((dt, last_transition[1], to_offset, zonerule, rule))
+ elif dt <= last_transition[0]:
+ if len(results):
+ results[-1] = ((results[-1][0], results[-1][1], to_offset, zonerule, None))
+ 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.
+ @param startYear: a C{int} containing the first year that should be present
+ 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
+ for rule in ruleorder:
+ if rule:
+ # Accumulate rule portions with the same offset pairs
+ lastOffsetPair = (rulemap[rule][0][1], rulemap[rule][0][2],)
+ startIndex = 0
+ for index in xrange(len(rulemap[rule])):
+ offsetPair = (rulemap[rule][index][1], rulemap[rule][index][2],)
+ if offsetPair != lastOffsetPair:
+ rule.vtimezone(
+ vtz,
+ lastZoneRule,
+ rulemap[rule][startIndex][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,
+ rulemap[rule][startIndex][0],
+ rulemap[rule][index][0],
+ rulemap[rule][startIndex][1],
+ rulemap[rule][startIndex][2],
+ len(rulemap[rule]),
+ )
+ else:
+ lastZoneRule.vtimezone(
+ vtz,
+ lastZoneRule,
+ rulemap[rule][0][0],
+ rulemap[rule][-1][0],
+ rulemap[rule][0][1],
+ rulemap[rule][0][2],
+ )
+ del ruleorder[:]
+ 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:
+ _generateRuleData()
+ if rule not in ruleorder:
+ ruleorder.append(rule)
+ rulemap.setdefault(rule, []).append((dt, offsetfrom, offsetto,))
+ lastZoneRule = zonerule
+
+ # Do left overs
+ _generateRuleData()
+
+ self._compressRDateComponents(vtz)
+
+ vtz.finalise()
+ return vtz
+
+
+ def _compressRDateComponents(self, vtz):
+ """
+ Compress sub-components with RDATEs into a single component with multiple
+ RDATEs assuming all other properties are the same.
+
+ @param vtz: the VTIMEZONE object to compress
+ @type vtz: L{PyCalendarVTimezone}
+ """
+
+ # Map the similar sub-components together
+ similarMap = {}
+ for item in vtz.mComponents:
+ item.finalise()
+ key = (
+ item.getType(),
+ item.getTZName(),
+ item.getUTCOffset(),
+ item.getUTCOffsetFrom(),
+ )
+ if item.hasProperty(definitions.cICalProperty_RDATE):
+ similarMap.setdefault(key, []).append(item)
+
+ # Merge similar
+ for values in similarMap.itervalues():
+ if len(values) > 1:
+ mergeTo = values[0]
+ for mergeFrom in values[1:]:
+ # Copy RDATE from to and remove from actual timezone
+ prop = mergeFrom.getProperties()[definitions.cICalProperty_RDATE][0]
+ 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
+ self.rule == other.rule and
+ self.format == other.format and
+ 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]
+ self.rule = splits[3 - offset]
+ 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 = (
+ self.gmtoff,
+ self.rule,
+ self.format,
+ )
+ if self.until:
+ items = items + (self.until,)
+ return "\t".join(items)
+
+
+ def getUntilDate(self):
+
+ if hasattr(self, "_cached_until"):
+ return self._cached_until
+
+ year = 9999
+ month = 12
+ day = 1
+ hours = 0
+ minutes = 0
+ seconds = 0
+ mode = None
+ if self.until and not self.until.startswith("#"):
+ splits = self.until.split(" ")
+ year = int(splits[0])
+ month = 1
+ day = 1
+ hours = 0
+ minutes = 0
+ seconds = 0
+ mode = None
+ if len(splits) > 1 and not splits[1].startswith("#"):
+ month = int(rule.Rule.MONTH_NAME_TO_POS[splits[1]])
+ if len(splits) > 2 and not splits[2].startswith("#"):
+ if splits[2] == "lastSun":
+ dt = PyCalendarDateTime(year=year, month=month, day=1)
+ dt.setDayOfWeekInMonth(-1, PyCalendarDateTime.SUNDAY)
+ splits[2] = dt.getDay()
+ elif splits[2] == "lastSat":
+ dt = PyCalendarDateTime(year=year, month=month, day=1)
+ dt.setDayOfWeekInMonth(-1, PyCalendarDateTime.SATURDAY)
+ splits[2] = dt.getDay()
+ elif splits[2] == "Sun>=1":
+ dt = PyCalendarDateTime(year=year, month=month, day=1)
+ dt.setDayOfWeekInMonth(1, PyCalendarDateTime.SUNDAY)
+ splits[2] = dt.getDay()
+ day = int(splits[2])
+ if len(splits) > 3 and not splits[3].startswith("#"):
+ splits = splits[3].split(":")
+ hours = int(splits[0])
+ minutes = int(splits[1][:2])
+ if len(splits[1]) > 2:
+ mode = splits[1][2:]
+ else:
+ mode = None
+ if len(splits) > 2:
+ seconds = int(splits[2])
+
+ dt = PyCalendarDateTime(year=year, month=month, day=day, hours=hours, minutes=minutes, seconds=seconds)
+ 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
+ negative = splits[0][0] == "-"
+ self._cached_uutc_offset = ((hours * 60) + minutes) * 60 + seconds
+ if negative:
+ 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 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)
+
+ # Sort the results by date
+ tempresults.sort(cmp=lambda x, y: x[0].compareDateTime(y[0]))
+
+ found_one = False
+ found_start = False
+ last_offset = lastOffset
+ last_stdoffset = lastStdOffset
+ finalUntil = self.getUntilDate()
+ for dt, to_offset, rule in tempresults:
+ dtutc = dt.getUTC(last_offset, last_stdoffset)
+ if dtutc >= lastUntilUTC:
+ if not found_start and dtutc != lastUntilUTC:
+ # Insert a start item
+ if not found_one:
+ last_offset = self.getUTCOffset()
+ last_stdoffset = self.getUTCOffset()
+ 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
+
+ results.append((dtutc, to_offset, self, rule))
+
+ last_offset = to_offset
+ last_stdoffset = self.getUTCOffset()
+ found_one = True
+
+ 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():
+ splits = self.rule.split(":")
+ to_offset = 60 * 60 * int(splits[0])
+ if len(splits) > 1:
+ to_offset += 60 * int(splits[1])
+
+ # Always add a transition for the start of this rule
+ 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)
+
+ # Do offsets
+ tzoffsetfrom = PyCalendarUTCOffsetValue(offsetfrom)
+ tzoffsetto = PyCalendarUTCOffsetValue(offsetto)
+
+ 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))
+
+ # Recurrence
+ comp.addProperty(PyCalendarProperty(definitions.cICalProperty_RDATE, start))
+
+ comp.finalise()
+ vtz.addComponent(comp)
+
+if __name__ == '__main__':
+ rulesdef = """Rule\tUS\t1918\t1919\t-\tMar\tlastSun\t2:00\t1:00\tD
+Rule\tUS\t1918\t1919\t-\tOct\tlastSun\t2:00\t0\tS
+Rule\tUS\t1942\tonly\t-\tFeb\t9\t2:00\t1:00\tW # War
+Rule\tUS\t1945\tonly\t-\tAug\t14\t23:00u\t1:00\tP # Peace
+Rule\tUS\t1945\tonly\t-\tSep\t30\t2:00\t0\tS
+Rule\tUS\t1967\t2006\t-\tOct\tlastSun\t2:00\t0\tS
+Rule\tUS\t1967\t1973\t-\tApr\tlastSun\t2:00\t1:00\tD
+Rule\tUS\t1974\tonly\t-\tJan\t6\t2:00\t1:00\tD
+Rule\tUS\t1975\tonly\t-\tFeb\t23\t2:00\t1:00\tD
+Rule\tUS\t1976\t1986\t-\tApr\tlastSun\t2:00\t1:00\tD
+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"""
+ rules = {}
+ import rule
+ ruleset = rule.RuleSet()
+ ruleset.parse(rulesdef)
+ rules[ruleset.name] = ruleset
+
+ zonedef = """Zone America/New_York -4:56:02\t-\tLMT\t1883 Nov 18 12:03:58
+\t\t\t-5:00\tUS\tE%sT\t1920
+\t\t\t-5:00\tNYC\tE%sT\t1942
+\t\t\t-5:00\tUS\tE%sT\t1946
+\t\t\t-5:00\tNYC\tE%sT\t1967
+\t\t\t-5:00\tUS\tE%sT"""
+ zone = Zone()
+ zone.parse(zonedef)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130126/9cf31226/attachment-0001.html>
More information about the calendarserver-changes
mailing list