[CalendarServer-changes] [7208] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Thu Mar 17 11:54:11 PDT 2011


Revision: 7208
          http://trac.macosforge.org/projects/calendarserver/changeset/7208
Author:   cdaboo at apple.com
Date:     2011-03-17 11:54:11 -0700 (Thu, 17 Mar 2011)
Log Message:
-----------
Merge of pycalendar branch.

Modified Paths:
--------------
    CalendarServer/trunk/benchmark
    CalendarServer/trunk/calendarserver/tools/anonymize.py
    CalendarServer/trunk/calendarserver/tools/purge.py
    CalendarServer/trunk/calendarserver/tools/test/test_purge.py
    CalendarServer/trunk/calendarserver/tools/test/test_purge_old_events.py
    CalendarServer/trunk/contrib/tools/dtraceanalyze.py
    CalendarServer/trunk/support/Makefile.Apple
    CalendarServer/trunk/support/build.sh
    CalendarServer/trunk/twext/python/datetime.py
    CalendarServer/trunk/twistedcaldav/caldavxml.py
    CalendarServer/trunk/twistedcaldav/customxml.py
    CalendarServer/trunk/twistedcaldav/datafilters/calendardata.py
    CalendarServer/trunk/twistedcaldav/datafilters/test/test_calendardata.py
    CalendarServer/trunk/twistedcaldav/datafilters/test/test_privateevents.py
    CalendarServer/trunk/twistedcaldav/dateops.py
    CalendarServer/trunk/twistedcaldav/freebusyurl.py
    CalendarServer/trunk/twistedcaldav/ical.py
    CalendarServer/trunk/twistedcaldav/instance.py
    CalendarServer/trunk/twistedcaldav/localization.py
    CalendarServer/trunk/twistedcaldav/mail.py
    CalendarServer/trunk/twistedcaldav/method/put_common.py
    CalendarServer/trunk/twistedcaldav/method/report_common.py
    CalendarServer/trunk/twistedcaldav/query/calendarquery.py
    CalendarServer/trunk/twistedcaldav/query/calendarqueryfilter.py
    CalendarServer/trunk/twistedcaldav/query/expression.py
    CalendarServer/trunk/twistedcaldav/query/sqlgenerator.py
    CalendarServer/trunk/twistedcaldav/query/test/test_calendarquery.py
    CalendarServer/trunk/twistedcaldav/query/test/test_queryfilter.py
    CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py
    CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
    CalendarServer/trunk/twistedcaldav/scheduling/itip.py
    CalendarServer/trunk/twistedcaldav/scheduling/processing.py
    CalendarServer/trunk/twistedcaldav/scheduling/scheduler.py
    CalendarServer/trunk/twistedcaldav/scheduling/test/test_icaldiff.py
    CalendarServer/trunk/twistedcaldav/scheduling/test/test_implicit.py
    CalendarServer/trunk/twistedcaldav/scheduling/test/test_itip.py
    CalendarServer/trunk/twistedcaldav/sharing.py
    CalendarServer/trunk/twistedcaldav/storebridge.py
    CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
    CalendarServer/trunk/twistedcaldav/test/test_localization.py
    CalendarServer/trunk/twistedcaldav/test/test_mail.py
    CalendarServer/trunk/twistedcaldav/test/test_multiget.py
    CalendarServer/trunk/twistedcaldav/test/test_timezones.py
    CalendarServer/trunk/twistedcaldav/test/test_upgrade.py
    CalendarServer/trunk/twistedcaldav/test/test_validation.py
    CalendarServer/trunk/twistedcaldav/timezones.py
    CalendarServer/trunk/twistedcaldav/timezoneservice.py
    CalendarServer/trunk/txdav/caldav/datastore/index_file.py
    CalendarServer/trunk/txdav/caldav/datastore/sql.py
    CalendarServer/trunk/txdav/caldav/datastore/test/common.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
    CalendarServer/trunk/txdav/caldav/datastore/util.py
    CalendarServer/trunk/txdav/caldav/icalendarstore.py
    CalendarServer/trunk/txdav/common/datastore/sql.py
    CalendarServer/trunk/txdav/common/datastore/sql_legacy.py

Added Paths:
-----------
    CalendarServer/trunk/benchreport
    CalendarServer/trunk/twistedcaldav/test/test_caldavxml.py
    CalendarServer/trunk/twistedcaldav/test/test_customxml.py

Property Changed:
----------------
    CalendarServer/trunk/
    CalendarServer/trunk/support/build.sh
    CalendarServer/trunk/txdav/caldav/datastore/index_file.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
    CalendarServer/trunk/txdav/carddav/datastore/index_file.py
    CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py


Property changes on: CalendarServer/trunk
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
   + /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593

Modified: CalendarServer/trunk/benchmark
===================================================================
--- CalendarServer/trunk/benchmark	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/benchmark	2011-03-17 18:54:11 UTC (rev 7208)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 ##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 Apple Inc. 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.
@@ -21,6 +21,6 @@
 
 wd="$(cd "$(dirname "$0")" && pwd -L)";
 
-export PYTHONPATH="$("${wd}/run" -p)";
+export PYTHONPATH="$("${wd}/run" -p):${wd}/../CalDAVClientLibrary/src";
 
 exec "${wd}/contrib/performance/benchmark" "$@";

Copied: CalendarServer/trunk/benchreport (from rev 7206, CalendarServer/branches/users/cdaboo/pycalendar/benchreport)
===================================================================
--- CalendarServer/trunk/benchreport	                        (rev 0)
+++ CalendarServer/trunk/benchreport	2011-03-17 18:54:11 UTC (rev 7208)
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+##
+# Copyright (c) 2005-2011 Apple Inc. 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.
+##
+
+set -e;
+set -u;
+
+wd="$(cd "$(dirname "$0")" && pwd -L)";
+
+export PYTHONPATH="$("${wd}/run" -p):${wd}/../CalDAVClientLibrary/src";
+
+args="$@"
+
+echo "Parameter: 1";
+"${wd}/contrib/performance/report" ${args} 1 HTTP summarize;
+echo;
+echo "Parameter: 9";
+"${wd}/contrib/performance/report" ${args} 9 HTTP summarize;
+echo;
+echo "Parameter: 81";
+"${wd}/contrib/performance/report" ${args} 81 HTTP summarize;

Modified: CalendarServer/trunk/calendarserver/tools/anonymize.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/anonymize.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/calendarserver/tools/anonymize.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -28,12 +28,13 @@
 import sys
 import urllib
 import uuid
-import vobject
 import xattr
 import zlib
 
 from twext.python.plistlib import readPlistFromString
 
+from pycalendar.calendar import PyCalendar
+
 COPY_CAL_XATTRS = (
     'WebDAV:{DAV:}resourcetype',
     'WebDAV:{urn:ietf:params:xml:ns:caldav}calendar-timezone',
@@ -275,62 +276,62 @@
 
 
 def anonymizeData(directoryMap, data):
-    vobj = vobject.readComponents(data).next()
+    pyobj = PyCalendar.parseText(data)
 
     # Delete property from the top level
     try:
-        for prop in vobj.contents['x-wr-calname']:
-            prop.value = anonymize(prop.value)
+        for prop in pyobj.getProperties('x-wr-calname'):
+            prop.setValue(anonymize(prop.getValue().getValue()))
     except KeyError:
         pass
 
-    for comp in vobj.components():
+    for comp in pyobj.getComponents():
 
         # Replace with anonymized CUAs:
         for propName in ('organizer', 'attendee'):
             try:
-                for prop in list(comp.contents[propName]):
-                    cua = prop.value
+                for prop in tuple(comp.getProperties(propName)):
+                    cua = prop.getValue().getValue()
                     record = directoryMap.lookupCUA(cua)
                     if record is None:
                         # print "Can't find record for", cua
                         record = directoryMap.addRecord(cua=cua)
                         if record is None:
-                            comp.remove(prop)
+                            comp.removeProperty(prop)
                             continue
-                    prop.value = "urn:uuid:%s" % (record['guid'],)
-                    if prop.params.has_key('X-CALENDARSERVER-EMAIL'):
-                        prop.params['X-CALENDARSERVER-EMAIL'] = (record['email'],)
+                    prop.setValue("urn:uuid:%s" % (record['guid'],))
+                    if prop.hasAttribute('X-CALENDARSERVER-EMAIL'):
+                        prop.setAttribute('X-CALENDARSERVER-EMAIL', record['email'])
                     else:
-                        prop.params['EMAIL'] = (record['email'],)
-                    prop.params['CN'] = (record['name'],)
+                        prop.setAttribute('EMAIL', record['email'])
+                    prop.setAttribute('CN', record['name'])
             except KeyError:
                 pass
 
         # Replace with anonymized text:
         for propName in ('summary', 'location', 'description'):
             try:
-                for prop in comp.contents[propName]:
-                    prop.value = anonymize(prop.value)
+                for prop in comp.getProperties(propName):
+                    prop.setValue(anonymize(prop.getValue().getValue()))
             except KeyError:
                 pass
 
         # Replace with anonymized URL:
         try:
-            for prop in comp.contents['url']:
-                prop.value = "http://example.com/%s/" % (anonymize(prop.value),)
+            for prop in comp.getProperties('url'):
+                prop.setValue("http://example.com/%s/" % (anonymize(prop.getValue().getValue()),))
         except KeyError:
             pass
 
         # Remove properties:
         for propName in ('x-apple-dropbox', 'attach'):
             try:
-                for prop in list(comp.contents[propName]):
-                    comp.remove(prop)
+                for prop in tuple(comp.getProperties(propName)):
+                    comp.removeProperty(prop)
             except KeyError:
                 pass
 
-    return vobj.serialize()
+    return pyobj.serialize()
 
 
 class DirectoryMap(object):

Modified: CalendarServer/trunk/calendarserver/tools/purge.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/purge.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/calendarserver/tools/purge.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -20,11 +20,8 @@
 import sys
 from errno import ENOENT, EACCES
 
-from datetime import date, timedelta, datetime
 from getopt import getopt, GetoptError
 
-from vobject.icalendar import utc
-
 from twisted.application.service import Service
 from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks, returnValue
@@ -45,6 +42,7 @@
 from calendarserver.tap.util import getRootResource
 from calendarserver.tools.principals import removeProxy
 from calendarserver.tools.util import loadConfig
+from pycalendar.datetime import PyCalendarDateTime
 
 log = Logger()
 
@@ -289,7 +287,10 @@
     if dryrun:
         verbose = True
 
-    cutoff = (date.today()-timedelta(days=days)).strftime("%Y%m%dT000000Z")
+    cutoff = PyCalendarDateTime.getToday()
+    cutoff.setDateOnly(False)
+    cutoff.offsetDay(-days)
+    cutoff = cutoff.getText()
     PurgeOldEventsService.cutoff = cutoff
     PurgeOldEventsService.batchSize = batchSize
     PurgeOldEventsService.dryrun = dryrun
@@ -536,7 +537,7 @@
     @type event: L{twistedcaldav.ical.Component}
 
     @param when: the cutoff date (anything after which is removed)
-    @type when: datetime with tzinfo
+    @type when: PyCalendarDateTime
 
     @param cua: Calendar User Address of principal being purged, to compare
         to see if it's the organizer of the event or just an attendee
@@ -547,7 +548,8 @@
     @return: one of the 4 constants above to indicate what action to take
     """
 
-    whenDate = when.date()
+    whenDate = when.duplicate()
+    whenDate.setDateOnly(True)
 
     master = event.masterComponent()
 
@@ -557,14 +559,9 @@
 
     # Anything completely in the future is deleted
     dtstart = master.getStartDateUTC()
-    if isinstance(dtstart, datetime):
-        isDateTime = True
-        if dtstart > when:
-            return CANCELEVENT_SHOULD_DELETE
-    else:
-        isDateTime = False
-        if dtstart > whenDate:
-            return CANCELEVENT_SHOULD_DELETE
+    isDateTime = not dtstart.isDateOnly()
+    if dtstart > when:
+        return CANCELEVENT_SHOULD_DELETE
 
     organizer = master.getOrganizer()
 
@@ -584,19 +581,15 @@
     # Set the UNTIL on RRULE to cease at the cutoff
     if master.hasProperty("RRULE"):
         for rrule in master.properties("RRULE"):
-            tokens = {}
-            tokens.update([valuePart.split("=") for valuePart in rrule.value().split(";")])
-            if tokens.has_key("COUNT"):
-                dirty = True
-                del tokens["COUNT"]
+            rrule = rrule.value()
+            if rrule.getUseCount():
+                rrule.setUseCount(False)
 
+            rrule.setUseUntil(True)
             if isDateTime:
-                tokens["UNTIL"] = when.strftime("%Y%m%dT%H%M%SZ")
+                rrule.setUntil(when)
             else:
-                tokens["UNTIL"] = when.strftime("%Y%m%d")
-
-            newValue = ";".join(["%s=%s" % (key, value,) for key, value in tokens.iteritems()])
-            rrule.setValue(newValue)
+                rrule.setUntil(whenDate)
             dirty = True
 
     # Remove any EXDATEs and RDATEs beyond the cutoff
@@ -605,18 +598,14 @@
             for exdate_rdate in master.properties(dateType):
                 newValues = []
                 for value in exdate_rdate.value():
-                    if isinstance(value, datetime):
-                        if value < when:
-                            newValues.append(value)
+                    if value.getValue() < when:
+                        newValues.append(value)
                     else:
-                        if value < whenDate:
-                            newValues.append(value)
+                        exdate_rdate.value().remove(value)
+                        dirty = True
                 if not newValues:
                     master.removeProperty(exdate_rdate)
                     dirty = True
-                else:
-                    exdate_rdate.setValue(newValues)
-                    dirty = True
 
 
     # Remove any overridden components beyond the cutoff
@@ -624,12 +613,8 @@
         if component.name() == "VEVENT":
             dtstart = component.getStartDateUTC()
             remove = False
-            if isinstance(dtstart, datetime):
-                if dtstart > when:
-                    remove = True
-            else:
-                if dtstart > whenDate:
-                    remove = True
+            if dtstart > when:
+                remove = True
             if remove:
                 event.removeComponent(component)
                 dirty = True
@@ -645,8 +630,7 @@
     when=None):
 
     if when is None:
-        when = datetime.now(tz=utc)
-    # when = datetime(2010, 12, 6, 12, 0, 0, 0, utc)
+        when = PyCalendarDateTime.getNowUTC()
 
     # Does the record exist?
     record = directory.recordWithGUID(guid)
@@ -676,7 +660,7 @@
     calendarHome = yield principal.calendarHome(request)
 
     # Anything in the past is left alone
-    whenString = when.strftime("%Y%m%dT%H%M%SZ")
+    whenString = when.getText()
     filter =  caldavxml.Filter(
           caldavxml.ComponentFilter(
               caldavxml.ComponentFilter(

Modified: CalendarServer/trunk/calendarserver/tools/test/test_purge.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_purge.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/calendarserver/tools/test/test_purge.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -15,20 +15,26 @@
 ##
 
 
-from twistedcaldav.ical import Component
 from calendarserver.tools.purge import cancelEvent
 from calendarserver.tools.purge import CANCELEVENT_MODIFIED, CANCELEVENT_SHOULD_DELETE
-from vobject.icalendar import utc
 
-from datetime import datetime, timedelta
+from twistedcaldav.ical import Component
 from twistedcaldav.test.util import TestCase
 
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.timezone import PyCalendarTimezone
 
 
 
-future = (datetime.utcnow() + timedelta(days=1)).strftime("%Y%m%dT%H%M%SZ")
-past = (datetime.utcnow() - timedelta(days=1)).strftime("%Y%m%dT%H%M%SZ")
 
+future = PyCalendarDateTime.getNowUTC()
+future.offsetDay(1)
+future = future.getText()
+
+past = PyCalendarDateTime.getNowUTC()
+past.offsetDay(-1)
+past = past.getText()
+
 # For test_purgeExistingGUID
 
 # No organizer/attendee
@@ -218,7 +224,7 @@
     def test_cancelRepeating(self):
         # A repeating event where purged CUA is organizer
         event = Component.fromString(REPEATING_1_ICS_BEFORE)
-        action = cancelEvent(event, datetime(2010, 12, 6, 12, 0, 0, 0, utc),
+        action = cancelEvent(event, PyCalendarDateTime(2010, 12, 6, 12, 0, 0, PyCalendarTimezone(utc=True)),
             "urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1")
         self.assertEquals(action, CANCELEVENT_MODIFIED)
         self.assertEquals(str(event), REPEATING_1_ICS_AFTER)
@@ -226,7 +232,7 @@
     def test_cancelAllDayRepeating(self):
         # A repeating All Day event where purged CUA is organizer
         event = Component.fromString(REPEATING_2_ICS_BEFORE)
-        action = cancelEvent(event, datetime(2010, 12, 6, 12, 0, 0, 0, utc),
+        action = cancelEvent(event, PyCalendarDateTime(2010, 12, 6, 12, 0, 0, PyCalendarTimezone(utc=True)),
             "urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1")
         self.assertEquals(action, CANCELEVENT_MODIFIED)
         self.assertEquals(str(event), REPEATING_2_ICS_AFTER)
@@ -234,21 +240,21 @@
     def test_cancelFutureEvent(self):
         # A future event
         event = Component.fromString(FUTURE_EVENT_ICS)
-        action = cancelEvent(event, datetime(2010, 12, 6, 12, 0, 0, 0, utc),
+        action = cancelEvent(event, PyCalendarDateTime(2010, 12, 6, 12, 0, 0, PyCalendarTimezone(utc=True)),
             "urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1")
         self.assertEquals(action, CANCELEVENT_SHOULD_DELETE)
 
     def test_cancelNonMeeting(self):
         # A repeating non-meeting event
         event = Component.fromString(REPEATING_NON_MEETING_ICS)
-        action = cancelEvent(event, datetime(2010, 12, 6, 12, 0, 0, 0, utc),
+        action = cancelEvent(event, PyCalendarDateTime(2010, 12, 6, 12, 0, 0, PyCalendarTimezone(utc=True)),
             "urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1")
         self.assertEquals(action, CANCELEVENT_SHOULD_DELETE)
 
     def test_cancelAsAttendee(self):
         # A repeating meeting event where purged CUA is an attendee
         event = Component.fromString(REPEATING_ATTENDEE_MEETING_ICS)
-        action = cancelEvent(event, datetime(2010, 12, 6, 12, 0, 0, 0, utc),
+        action = cancelEvent(event, PyCalendarDateTime(2010, 12, 6, 12, 0, 0, PyCalendarTimezone(utc=True)),
             "urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1")
         self.assertEquals(action, CANCELEVENT_SHOULD_DELETE)
 
@@ -263,20 +269,20 @@
 PRODID:-//Apple Inc.//iCal 4.0.4//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;BYMONTH=11;BYDAY=1SU
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
 TZNAME:PST
 TZOFFSETFROM:-0700
 TZOFFSETTO:-0800
 END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:20070311T020000
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
-TZNAME:PDT
-TZOFFSETFROM:-0800
-TZOFFSETTO:-0700
-END:DAYLIGHT
 END:VTIMEZONE
 BEGIN:VEVENT
 UID:59E260E3-1644-4BDF-BBC6-6130B0C3A520
@@ -337,20 +343,20 @@
 PRODID:-//Apple Inc.//iCal 4.0.4//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;BYMONTH=11;BYDAY=1SU
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
 TZNAME:PST
 TZOFFSETFROM:-0700
 TZOFFSETTO:-0800
 END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:20070311T020000
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
-TZNAME:PDT
-TZOFFSETFROM:-0800
-TZOFFSETTO:-0700
-END:DAYLIGHT
 END:VTIMEZONE
 BEGIN:VEVENT
 UID:59E260E3-1644-4BDF-BBC6-6130B0C3A520
@@ -498,20 +504,20 @@
 PRODID:-//Apple Inc.//iCal 4.0.4//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;BYMONTH=11;BYDAY=1SU
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
 TZNAME:PST
 TZOFFSETFROM:-0700
 TZOFFSETTO:-0800
 END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:20070311T020000
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
-TZNAME:PDT
-TZOFFSETFROM:-0800
-TZOFFSETTO:-0700
-END:DAYLIGHT
 END:VTIMEZONE
 BEGIN:VEVENT
 UID:97B243D3-D252-4034-AA6D-9AE34E063991
@@ -532,20 +538,20 @@
 PRODID:-//Apple Inc.//iCal 4.0.4//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;BYMONTH=11;BYDAY=1SU
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
 TZNAME:PST
 TZOFFSETFROM:-0700
 TZOFFSETTO:-0800
 END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:20070311T020000
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
-TZNAME:PDT
-TZOFFSETFROM:-0800
-TZOFFSETTO:-0700
-END:DAYLIGHT
 END:VTIMEZONE
 BEGIN:VEVENT
 UID:4E4D0C8C-6546-4777-9BF5-AD629C05E7D5
@@ -567,20 +573,20 @@
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 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;BYMONTH=11;BYDAY=1SU
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
 TZNAME:PST
 TZOFFSETFROM:-0700
 TZOFFSETTO:-0800
 END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:20070311T020000
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
-TZNAME:PDT
-TZOFFSETFROM:-0800
-TZOFFSETTO:-0700
-END:DAYLIGHT
 END:VTIMEZONE
 BEGIN:VEVENT
 UID:111A679F-EF8E-4CA5-9262-7C805E2C184D

Modified: CalendarServer/trunk/calendarserver/tools/test/test_purge_old_events.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_purge_old_events.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/calendarserver/tools/test/test_purge_old_events.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -17,23 +17,23 @@
 """
 Tests for calendarserver.tools.purge
 """
+from calendarserver.tap.util import getRootResource
+from calendarserver.tools.purge import purgeOldEvents, purgeGUID, purgeOrphanedAttachments
 
-from twisted.trial import unittest
-from twisted.internet.defer import inlineCallbacks, returnValue
 from twext.web2.http_headers import MimeType
-from twistedcaldav.vcard import Component as VCardComponent
 
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.trial import unittest
 
+from twistedcaldav.config import config
+from twistedcaldav.memcacher import Memcacher
+from twistedcaldav.vcard import Component as VCardComponent
 
 from txdav.common.datastore.test.util import buildStore, populateCalendarsFrom, CommonCommonTests
 
-from calendarserver.tap.util import getRootResource
-from calendarserver.tools.purge import purgeOldEvents, purgeGUID, purgeOrphanedAttachments
-from twistedcaldav.config import config
-from twistedcaldav.memcacher import Memcacher
-from vobject.icalendar import utc
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.timezone import PyCalendarTimezone
 
-import datetime
 import os
 
 
@@ -384,7 +384,7 @@
 
     @inlineCallbacks
     def test_eventsOlderThan(self):
-        cutoff = datetime.datetime(2010, 4, 1)
+        cutoff = PyCalendarDateTime(2010, 4, 1, 0, 0, 0)
         txn = self._sqlCalendarStore.newTransaction()
 
         # Query for all old events
@@ -410,7 +410,7 @@
 
     @inlineCallbacks
     def test_removeOldEvents(self):
-        cutoff = datetime.datetime(2010, 4, 1)
+        cutoff = PyCalendarDateTime(2010, 4, 1, 0, 0, 0)
         txn = self._sqlCalendarStore.newTransaction()
 
         # Remove oldest event
@@ -473,7 +473,7 @@
         self.assertTrue(os.path.exists(attachmentPath))
 
         # Delete all old events (including the event containing the attachment)
-        cutoff = datetime.datetime(2010, 4, 1)
+        cutoff = PyCalendarDateTime(2010, 4, 1, 0, 0, 0)
         count = (yield txn.removeOldEvents(cutoff))
 
         # Just look for orphaned attachments but don't delete
@@ -499,18 +499,18 @@
 
         # Dry run
         total = (yield purgeOldEvents(self._sqlCalendarStore, self.directory,
-            self.rootResource, datetime.datetime(2010, 4, 1), 2, dryrun=True,
+            self.rootResource, PyCalendarDateTime(2010, 4, 1, 0, 0, 0), 2, dryrun=True,
             verbose=False))
         self.assertEquals(total, 4)
 
         # Actually remove
         total = (yield purgeOldEvents(self._sqlCalendarStore, self.directory,
-            self.rootResource, datetime.datetime(2010, 4, 1), 2, verbose=False))
+            self.rootResource, PyCalendarDateTime(2010, 4, 1, 0, 0, 0), 2, verbose=False))
         self.assertEquals(total, 4)
 
         # There should be no more left
         total = (yield purgeOldEvents(self._sqlCalendarStore, self.directory,
-            self.rootResource, datetime.datetime(2010, 4, 1), 2, verbose=False))
+            self.rootResource, PyCalendarDateTime(2010, 4, 1, 0, 0, 0), 2, verbose=False))
         self.assertEquals(total, 0)
 
     test_purgeOldEvents.todo = "New lazy indexing broke this"
@@ -537,7 +537,7 @@
         # Purge home1
         total, ignored = (yield purgeGUID("home1", self.directory,
             self.rootResource, verbose=False, proxies=False,
-            when=datetime.datetime(2010, 4, 1, 12, 0, 0, 0, utc)))
+            when=PyCalendarDateTime(2010, 4, 1, 12, 0, 0, 0, PyCalendarTimezone(utc=True))))
 
         # 2 items deleted: 1 event and 1 vcard
         self.assertEquals(total, 2)
@@ -555,7 +555,7 @@
 
         # Remove old events first
         total = (yield purgeOldEvents(self._sqlCalendarStore, self.directory,
-            self.rootResource, datetime.datetime(2010, 4, 1), 2, verbose=False))
+            self.rootResource, PyCalendarDateTime(2010, 4, 1, 0, 0, 0), 2, verbose=False))
         self.assertEquals(total, 4)
 
         # Dry run

Modified: CalendarServer/trunk/contrib/tools/dtraceanalyze.py
===================================================================
--- CalendarServer/trunk/contrib/tools/dtraceanalyze.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/contrib/tools/dtraceanalyze.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -31,8 +31,10 @@
         
         prefix_maps = {
             "/usr/share/caldavd/lib/python/": "{caldavd}/",
+            "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.6": "{Python}",
             "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6": "{Python}",
             "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5": "{Python}",
+            "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python": "{Extras}",
             "/System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python": "{Extras}",
             "/System/Library/Frameworks/Python.framework/Versions/2.5/Extras/lib/python": "{Extras}",
         }

Modified: CalendarServer/trunk/support/Makefile.Apple
===================================================================
--- CalendarServer/trunk/support/Makefile.Apple	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/support/Makefile.Apple	2011-03-17 18:54:11 UTC (rev 7208)
@@ -39,25 +39,26 @@
 # Build
 #
 
-.phony: $(Project) vobject build setup prep install install-ossfiles buildit
+.phony: $(Project) vobject pycalendar build setup prep install install-ossfiles buildit
 
 CalDAVTester::          $(BuildDirectory)/CalDAVTester
 PyKerberos::            $(BuildDirectory)/PyKerberos
 PyOpenDirectory::       $(BuildDirectory)/PyOpenDirectory
 PyXML-0.8.4::           $(BuildDirectory)/PyXML-0.8.4
 vobject::               $(BuildDirectory)/vobject
+pycalendar::            $(BuildDirectory)/pycalendar
 PyGreSQL-4.0::          $(BuildDirectory)/PyGreSQL-4.0
-sqlparse-0.1.2::	$(BuildDirectory)/sqlparse-0.1.2
+sqlparse-0.1.2::        $(BuildDirectory)/sqlparse-0.1.2
 $(Project)::            $(BuildDirectory)/$(Project)
 
-build:: PyKerberos PyOpenDirectory PyXML-0.8.4 vobject PyGreSQL-4.0 sqlparse-0.1.2 $(Project)
+build:: PyKerberos PyOpenDirectory PyXML-0.8.4 vobject pycalendar PyGreSQL-4.0 sqlparse-0.1.2 $(Project)
 
 setup:
 	$(_v) ./run -g
 
-prep:: setup CalDAVTester.tgz PyKerberos.tgz PyOpenDirectory.tgz PyXML-0.8.4.tgz vobject.tgz PyGreSQL-4.0.tgz sqlparse-0.1.2.tgz
+prep:: setup CalDAVTester.tgz PyKerberos.tgz PyOpenDirectory.tgz PyXML-0.8.4.tgz vobject.tgz pycalendar.tgz PyGreSQL-4.0.tgz sqlparse-0.1.2.tgz
 
-PyKerberos PyOpenDirectory PyXML-0.8.4 vobject PyGreSQL-4.0 sqlparse-0.1.2 $(Project)::
+PyKerberos PyOpenDirectory PyXML-0.8.4 vobject pycalendar PyGreSQL-4.0 sqlparse-0.1.2 $(Project)::
 	@echo "Building $@..."
 	$(_v) cd $(BuildDirectory)/$@ && $(Environment) $(PYTHON) setup.py build
 
@@ -72,6 +73,7 @@
 	$(_v) cd $(BuildDirectory)/PyOpenDirectory  && $(Environment) $(PYTHON) setup.py install $(PY_INSTALL_FLAGS)
 	$(_v) cd $(BuildDirectory)/PyXML-0.8.4      && $(Environment) $(PYTHON) setup.py install $(PY_INSTALL_FLAGS)
 	$(_v) cd $(BuildDirectory)/vobject          && $(Environment) $(PYTHON) setup.py install $(PY_INSTALL_FLAGS)
+	$(_v) cd $(BuildDirectory)/pycalendar       && $(Environment) $(PYTHON) setup.py install $(PY_INSTALL_FLAGS)
 	$(_v) cd $(BuildDirectory)/PyGreSQL-4.0     && $(Environment) $(PYTHON) setup.py install $(PY_INSTALL_FLAGS)
 	$(_v) cd $(BuildDirectory)/sqlparse-0.1.2   && $(Environment) $(PYTHON) setup.py install $(PY_INSTALL_FLAGS)
 	$(_v) for so in $$(find "$(DSTROOT)$(PY_HOME)/lib" -type f -name '*.so'); do $(STRIP) -Sx "$${so}"; done 

Modified: CalendarServer/trunk/support/build.sh
===================================================================
--- CalendarServer/trunk/support/build.sh	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/support/build.sh	2011-03-17 18:54:11 UTC (rev 7208)
@@ -632,6 +632,11 @@
     "vobject" "vobject" "vobject" \
     "http://svn.osafoundation.org/vobject/trunk";
 
+  # XXX actually PyCalendar should be imported in-place.
+  py_dependency -fie -r 144 \
+    "pycalendar" "pycalendar" "pycalendar" \
+    "http://svn.mulberrymail.com/repos/PyCalendar/branches/server";
+
   #
   # Tool dependencies.  The code itself doesn't depend on these, but
   # they are useful to developers.


Property changes on: CalendarServer/trunk/support/build.sh
___________________________________________________________________
Added: svn:mergeinfo
   + /CalendarServer/branches/config-separation/support/build.sh:4379-4443
/CalendarServer/branches/egg-info-351/support/build.sh:4589-4615
/CalendarServer/branches/generic-sqlstore/support/build.sh:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/support/build.sh:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/support/build.sh:5911-5935
/CalendarServer/branches/new-store/support/build.sh:5594-5934
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/support/build.sh:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/support/build.sh:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/support/build.sh:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/support/build.sh:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/support/build.sh:7085-7206
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/support/build.sh:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/support/build.sh:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/support/build.sh:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/support/build.sh:4971-5080
/CalendarServer/branches/users/glyph/dalify/support/build.sh:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect/support/build.sh:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres/support/build.sh:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/support/build.sh:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/support/build.sh:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/support/build.sh:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/support/build.sh:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/support/build.sh:6490-6550
/CalendarServer/branches/users/glyph/sql-store/support/build.sh:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/support/build.sh:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/support/build.sh:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/support/build.sh:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/support/build.sh:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/support/build.sh:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/support/build.sh:4068-4075
/CalendarServer/branches/users/sagen/resources-2/support/build.sh:5084-5093
/CalendarServer/branches/users/wsanchez/transations/support/build.sh:5515-5593

Modified: CalendarServer/trunk/twext/python/datetime.py
===================================================================
--- CalendarServer/trunk/twext/python/datetime.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twext/python/datetime.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -24,7 +24,6 @@
     "dateordatetime",
     "timerange",
     "asTimeZone",
-    "asUTC",
     "iCalendarString",
 ]
 
@@ -32,7 +31,7 @@
 datetime = __import__("datetime").datetime
 
 from vobject.icalendar import dateTimeToString, dateToString
-from vobject.icalendar import utc, getTzid as tzWithID
+from vobject.icalendar import utc
 
 
 # FIXME, add constants for begining/end of time
@@ -297,21 +296,17 @@
 # Convenience functions
 ##
 
-def asTimeZone(dateOrDatetime, tzinfo):
+def asTimeZone(pydt, pytz):
     """
-    Convert a L{date} or L{datetime} to the given time zone.
+    Convert a L{PyCalendarDateTime} to the given time zone.
     """
-    return dateordatetime(dateOrDatetime).asTimeZone(tzinfo).dateOrDatetime()
+    dup = pydt.duplicate()
+    dup.adjustTimezone(pytz)
+    return dup
 
-def asUTC(dateOrDatetime):
+def iCalendarString(pydt):
     """
-    Convert a L{date} or L{datetime} to UTC.
-    """
-    return dateordatetime(dateOrDatetime).asUTC().dateOrDatetime()
-
-def iCalendarString(dateOrDatetime):
-    """
-    Convert a L{date} or L{datetime} to a string appropriate for use
+    Convert a L{PyCalendarDateTime} to a string appropriate for use
     in an iCalendar property.
     """
-    return dateordatetime(dateOrDatetime).iCalendarString()
+    return pydt.getText()

Modified: CalendarServer/trunk/twistedcaldav/caldavxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/caldavxml.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/caldavxml.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -25,16 +25,13 @@
 See draft spec: http://ietf.webdav.org/caldav/draft-dusseault-caldav.txt
 """
 
-import datetime
+from pycalendar.datetime import PyCalendarDateTime
 
-from vobject.icalendar import utc, TimezoneComponent
-
 from twext.web2.dav import davxml
 
 from twext.python.log import Logger
 
 from twistedcaldav.ical import Component as iComponent
-from twistedcaldav.ical import parse_date_or_datetime
 
 log = Logger()
 
@@ -93,8 +90,8 @@
         if "start" not in attributes and "end" not in attributes:
             raise ValueError("One of 'start' or 'end' must be present in CALDAV:time-range")
         
-        self.start = parse_date_or_datetime(attributes["start"]) if "start" in attributes else None
-        self.end = parse_date_or_datetime(attributes["end"]) if "end" in attributes else None
+        self.start = PyCalendarDateTime.parseText(attributes["start"]) if "start" in attributes else None
+        self.end = PyCalendarDateTime.parseText(attributes["end"]) if "end" in attributes else None
 
     def valid(self, level=0):
         """
@@ -103,16 +100,16 @@
         @return:      True if valid, False otherwise
         """
         
-        if self.start is not None and not isinstance(self.start, datetime.datetime):
+        if self.start is not None and self.start.isDateOnly():
             log.msg("start attribute in <time-range> is not a date-time: %s" % (self.start,))
             return False
-        if self.end is not None and not isinstance(self.end, datetime.datetime):
+        if self.end is not None and self.end.isDateOnly():
             log.msg("end attribute in <time-range> is not a date-time: %s" % (self.end,))
             return False
-        if self.start is not None and self.start.tzinfo != utc:
+        if self.start is not None and not self.start.utc():
             log.msg("start attribute in <time-range> is not UTC: %s" % (self.start,))
             return False
-        if self.end is not None and self.end.tzinfo != utc:
+        if self.end is not None and not self.end.utc():
             log.msg("end attribute in <time-range> is not UTC: %s" % (self.end,))
             return False
 
@@ -148,10 +145,7 @@
         found = False
 
         for subcomponent in calendar.subcomponents():
-            if (
-                subcomponent.name() == "VTIMEZONE" and
-                isinstance(subcomponent._vobject, TimezoneComponent)
-            ):
+            if (subcomponent.name() == "VTIMEZONE"):
                 if found:
                     return False
                 else:

Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/customxml.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -31,11 +31,8 @@
 from twistedcaldav import caldavxml, carddavxml
 from twistedcaldav.ical import Component as iComponent
 
-from vobject.icalendar import utc
-from vobject.icalendar import dateTimeToString
+from pycalendar.datetime import PyCalendarDateTime
 
-import datetime
-
 calendarserver_namespace = "http://calendarserver.org/ns/"
 
 calendarserver_proxy_compliance = (
@@ -512,7 +509,7 @@
 
     def __init__(self, *children):
         super(DTStamp, self).__init__(children)
-        self.children = (davxml.PCDATAElement(dateTimeToString(datetime.datetime.now(tz=utc))),)
+        self.children = (davxml.PCDATAElement(PyCalendarDateTime.getNowUTC().getText()),)
 
 class Action (davxml.WebDAVElement):
     """

Modified: CalendarServer/trunk/twistedcaldav/datafilters/calendardata.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/calendardata.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/datafilters/calendardata.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -19,6 +19,7 @@
 from twistedcaldav.datafilters.filter import CalendarFilter
 from twistedcaldav.dateops import clipPeriod
 from twistedcaldav.ical import Component
+from pycalendar.period import PyCalendarPeriod
 
 __all__ = [
     "CalendarDataFilter",
@@ -156,7 +157,7 @@
             for property in component.properties("FREEBUSY"):
                 newvalue = []
                 for period in property.value():
-                    clipped = clipPeriod(period, (self.calendardata.freebusy_set.start, self.calendardata.freebusy_set.end))
+                    clipped = clipPeriod(period.getValue(), PyCalendarPeriod(self.calendardata.freebusy_set.start, self.calendardata.freebusy_set.end))
                     if clipped:
                         newvalue.append(clipped)
                 if len(newvalue):

Modified: CalendarServer/trunk/twistedcaldav/datafilters/test/test_calendardata.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/test/test_calendardata.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/datafilters/test/test_calendardata.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -94,8 +94,6 @@
 """.replace("\n", "\r\n")
         
         result = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//PYVOBJECT//NONSGML Version 1//EN
 BEGIN:VEVENT
 UID:12345-67890
 DTSTART:20080601T120000Z

Modified: CalendarServer/trunk/twistedcaldav/datafilters/test/test_privateevents.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/test/test_privateevents.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/datafilters/test/test_privateevents.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -66,6 +66,7 @@
         data = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-CALENDARSERVER-ACCESS:PUBLIC
 BEGIN:VEVENT
 UID:12345-67890
 DTSTART:20080601T120000Z
@@ -74,7 +75,6 @@
 ATTENDEE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
 END:VEVENT
-X-CALENDARSERVER-ACCESS:PUBLIC
 END:VCALENDAR
 """.replace("\n", "\r\n")
         
@@ -87,6 +87,7 @@
         data = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-CALENDARSERVER-ACCESS:PRIVATE
 BEGIN:VEVENT
 UID:12345-67890
 DTSTART:20080601T120000Z
@@ -95,7 +96,6 @@
 ATTENDEE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
 END:VEVENT
-X-CALENDARSERVER-ACCESS:PRIVATE
 END:VCALENDAR
 """.replace("\n", "\r\n")
         
@@ -109,6 +109,7 @@
         data = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-CALENDARSERVER-ACCESS:CONFIDENTIAL
 BEGIN:VEVENT
 UID:12345-67890
 DTSTART:20080601T120000Z
@@ -120,19 +121,18 @@
 ORGANIZER;CN=User 01:mailto:user1 at example.com
 SUMMARY:Confidential
 END:VEVENT
-X-CALENDARSERVER-ACCESS:CONFIDENTIAL
 END:VCALENDAR
 """.replace("\n", "\r\n")
         
         filtered = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-CALENDARSERVER-ACCESS:CONFIDENTIAL
 BEGIN:VEVENT
 UID:12345-67890
 DTSTART:20080601T120000Z
 DTEND:20080601T130000Z
 END:VEVENT
-X-CALENDARSERVER-ACCESS:CONFIDENTIAL
 END:VCALENDAR
 """.replace("\n", "\r\n")
         
@@ -145,6 +145,7 @@
         data = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-CALENDARSERVER-ACCESS:RESTRICTED
 BEGIN:VEVENT
 UID:12345-67890
 DTSTART:20080601T120000Z
@@ -156,13 +157,13 @@
 ORGANIZER;CN=User 01:mailto:user1 at example.com
 SUMMARY:Confidential
 END:VEVENT
-X-CALENDARSERVER-ACCESS:RESTRICTED
 END:VCALENDAR
 """.replace("\n", "\r\n")
         
         filtered = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-CALENDARSERVER-ACCESS:RESTRICTED
 BEGIN:VEVENT
 UID:12345-67890
 DTSTART:20080601T120000Z
@@ -170,7 +171,6 @@
 LOCATION:My office
 SUMMARY:Confidential
 END:VEVENT
-X-CALENDARSERVER-ACCESS:RESTRICTED
 END:VCALENDAR
 """.replace("\n", "\r\n")
         

Modified: CalendarServer/trunk/twistedcaldav/dateops.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/dateops.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/dateops.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -13,6 +13,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.timezone import PyCalendarTimezone
+from pycalendar.period import PyCalendarPeriod
+import datetime
+from vobject.icalendar import utc
 
 """
 Date/time Utilities
@@ -30,91 +35,98 @@
 ]
 
 import calendar
-import datetime
-from vobject.icalendar import utc
 
-from twext.python.datetime import dateordatetime
-
 def normalizeForIndex(dt):
     """
-    Normalize a L{datetime.date} or L{datetime.datetime} object for use in the Index.
-    If it's a L{datetime.date}, convert to L{datetime.datetime} with HH:MM:SS set to 00:00:00 in UTC.
-    If it's a L{datetime.datetime}, convert to UTC.
-    @param dt: a L{datetime.date} or L{datetime.datetime} object to normalize
-    @return: the normalized date or datetime
+    Normalize a L{PyCalendarDateTime} object for use in the Index.
+    Convert to date-time in UTC.
+    @param dt: a L{PyCalendarDateTime} object to normalize
+    @return: the normalized PyCalendarDateTime
     """
-    if not isinstance(dt, datetime.date):
-        raise TypeError("%r is not a datetime.date instance" % (dt,))
+    if not isinstance(dt, PyCalendarDateTime):
+        raise TypeError("%r is not a PyCalendarDateTime instance" % (dt,))
     
-    if isinstance(dt, datetime.datetime):
-        if dt.tzinfo is not None:
-            return dt.astimezone(utc)
-        else:
-            return dt
+    dt = dt.duplicate()
+    if dt.isDateOnly():
+        dt.setDateOnly(False)
+        dt.setHHMMSS(0, 0, 0)
+        dt.setTimezoneID(None)  # Keep it floating
+        return dt
+    elif dt.floating():
+        return dt
     else:
-        return datetime.datetime.fromordinal(dt.toordinal())
+        dt.adjustToUTC()
+        return dt
 
 def normalizeToUTC(dt):
     """
-    Normalize a L{datetime.date} or L{datetime.datetime} object to UTC.
+    Normalize a L{PyCalendarDateTime} object to UTC.
     """
-    if not isinstance(dt, datetime.date):
-        raise TypeError("%r is not a datetime.date instance" % (dt,))
+    if not isinstance(dt, PyCalendarDateTime):
+        raise TypeError("%r is not a PyCalendarDateTime instance" % (dt,))
     
-    if isinstance(dt, datetime.datetime):
-        if dt.tzinfo is not None:
-            return dt.astimezone(utc)
-        else:
-            return dt.replace(tzinfo=utc)
+    dt = dt.duplicate()
+    if dt.isDateOnly():
+        dt.setDateOnly(False)
+        dt.setHHMMSS(0, 0, 0)
+        dt.setTimezoneUTC(True)
+        return dt
+    elif dt.floating():
+        dt.setTimezoneUTC(True)
+        return dt
     else:
-        return datetime.datetime.fromordinal(dt.toordinal()).replace(tzinfo=utc)
+        dt.adjustToUTC()
+        return dt
 
-def floatoffset(dt, tzinfo):
+def floatoffset(dt, pytz):
     """
     Apply the timezone offset to the supplied time, then force tz to utc. This gives the local
     date-time as if the local tz were UTC. It can be used in floating time comparisons with UTC date-times.
     
-    @param dt: a L{datetime.datetime} object to normalize
-    @param tzinfo: a L{datetime.tzinfo} object to apply offset from
-    @return: the normalized datetime
+    @param dt: a L{PyCalendarDateTime} object to normalize
+    @param pytz: a L{PyCalendarTimezone} object to apply offset from
+    @return: the normalized PyCalendarDateTime
     """
     
-    if tzinfo is None:
-        tzinfo = utc
-    return dt.astimezone(tzinfo).replace(tzinfo=utc)
+    if pytz is None:
+        pytz = PyCalendarTimezone(utc=True)
+    
+    dt = dt.duplicate()
+    dt.adjustTimezone(pytz)
+    dt.setTimezoneUTC(True)
+    return dt
 
+def adjustFloatingToTimezone(dtadjust, dtcopyfrom, pytz=None):
+    
+    dtadjust = dtadjust.duplicate()
+    dtadjust.setTimezone(pytz if pytz else dtcopyfrom.getTimezone())
+    return dtadjust
+
 def compareDateTime(dt1, dt2, defaulttz=None):
-    dt1 = dateordatetime(dt1, defaultTZ=defaulttz)
-    dt2 = dateordatetime(dt2, defaultTZ=defaulttz)
-    if dt1 == dt2:
-        return 0
-    elif dt1 < dt2:
-        return -1
-    else:
-        return 1
+    
+    if dt1.floating() and not dt2.floating():
+        dt1 = adjustFloatingToTimezone(dt1, dt2, defaulttz)
+    elif dt2.floating() and not dt1.floating():
+        dt2 = adjustFloatingToTimezone(dt2, dt1, defaulttz)
+    
+    return dt1.compareDateTime(dt2)
 
 def differenceDateTime(start, end, defaulttz = None):
-    return dateordatetime(end, defaultTZ=defaulttz) - dateordatetime(start)
 
-#def timeRangesOverlap(start1, end1, start2, end2, defaulttz = None):
-#    def dodt(d):
-#        if d is None:
-#            return None
-#        else:
-#            return dateordatetime(d, defaulttz)
-#
-#    dodt1 = timerange(dodt(start1), dodt(end1))
-#    dodt2 = timerange(dodt(start2), dodt(end2))
-#
-#    return dodt1.overlapsWith(dodt2)
+    if start.floating() and not end.floating():
+        start = adjustFloatingToTimezone(start, end, defaulttz)
+    elif end.floating() and not start.floating():
+        end = adjustFloatingToTimezone(end, start, defaulttz)
 
+    return end - start
+
 def timeRangesOverlap(start1, end1, start2, end2, defaulttz = None):
-    # Can't compare datetime.date and datetime.datetime objects, so normalize
-    # to date if they are mixed.
-    if isinstance(start1, datetime.datetime) and (start2 is not None) and not isinstance(start2, datetime.datetime): start1 = start1.date()
-    if isinstance(start2, datetime.datetime) and (start1 is not None) and not isinstance(start1, datetime.datetime): start2 = start2.date()
-    if isinstance(end1,   datetime.datetime) and (end2 is not None) and not isinstance(end2,   datetime.datetime): end1   = end1.date()
-    if isinstance(end2,   datetime.datetime) and (end1 is not None) and not isinstance(end1,   datetime.datetime): end2   = end2.date()
+    # Can't compare date-time and date only, so normalize
+    # to date only if they are mixed.
+    if (start1 is not None) and not start1.isDateOnly() and (start2 is not None) and start2.isDateOnly(): start1 = start1.setDateOnly(True)
+    if (start2 is not None) and not start2.isDateOnly() and (start1 is not None) and start1.isDateOnly(): start2 = start2.setDateOnly(True)
+    if (end1 is not None) and not end1.isDateOnly() and (end2 is not None) and end2.isDateOnly(): end1 = end1.setDateOnly(True)
+    if (end2 is not None) and not end2.isDateOnly() and (end1 is not None) and end1.isDateOnly(): end2 = end2.setDateOnly(True)
 
     # Note that start times are inclusive and end times are not.
     if start1 is not None and start2 is not None:
@@ -133,83 +145,61 @@
     else:
         return False
 
-def periodEnd(p):
+def normalizePeriodList(periods):
     """
-    Calculate the end datetime of the period. Since a period is a
-    tuple consisting of a pair of L{datetime.datetime}'s, or one
-    L{datetime.datetime} and one L{datetime.timedelta}, we may need
-    to add the duration to the start to get the actual end.
-    @param p: the period whose end is to be determined.
-    @return: the L{datetime.datetime} for the end.
-    """
-    assert len(p) == 2, "Period is not a tuple of two items: %r" % (p,)
-    assert isinstance(p[0], datetime.datetime), "Period start is not a datetime: %r" % (p,)
-    assert isinstance(p[1], datetime.datetime) or isinstance(p[1], datetime.timedelta), "Period end is not a datetime or timedelta: %r" % (p,)
-    
-    if isinstance(p[1], datetime.timedelta):
-        return p[0] + p[1]
-    else:
-        return p[1]
-    
-def normalizePeriodList(list):
-    """
     Normalize the list of periods by merging overlapping or consecutive ranges
     and sorting the list by each periods start.
-    @param list: a list of tuples of L{datetime.datetime} pairs. The list is changed in place.
+    @param list: a list of tuples of L{PyCalendarPeriod}. The list is changed in place.
     """
     
     # First sort the list
     def sortPeriods(p1, p2):
         """
         Compare two periods. Sort by their start and then end times.
-        A period is a tuple consisting of a pair of L{datetime.datetime}'s, or one
-        L{datetime.datetime} and one L{datetime.timedelta}.
+        A period is a L{PyCalendarPeriod}.
         @param p1: first period
         @param p2: second period
         @return: 1 if p1>p2, 0 if p1==p2, -1 if p1<p2
         """
 
-        assert len(p1) == 2, "Period is not a tuple of two items: %r" % (p1,)
-        assert isinstance(p1[0], datetime.datetime), "Period start is not a datetime: %r" % (p1,)
-        assert isinstance(p1[1], datetime.datetime) or isinstance(p1[1], datetime.timedelta), "Period end is not a datetime or timedelta: %r" % (p1,)
+        assert isinstance(p1, PyCalendarPeriod), "Period is not a PyCalendarPeriod: %r" % (p1,)
+        assert isinstance(p2, PyCalendarPeriod), "Period is not a PyCalendarPeriod: %r" % (p2,)
         
-        assert len(p2) == 2, "Period is not a tuple of two items: %r" % (p2,)
-        assert isinstance(p2[0], datetime.datetime), "Period start is not a datetime: %r" % (p2,)
-        assert isinstance(p2[1], datetime.datetime) or isinstance(p2[1], datetime.timedelta), "Period end is not a datetime or timedelta: %r" % (p2,)
         
-        
-        if p1[0] == p2[0]:
-            cmp1 = periodEnd(p1)
-            cmp2 = periodEnd(p2)
+        if p1.getStart() == p2.getStart():
+            cmp1 = p1.getEnd()
+            cmp2 = p2.getEnd()
         else:
-            cmp1 = p1[0]
-            cmp2 = p2[0]
+            cmp1 = p1.getStart()
+            cmp2 = p2.getStart()
         
         return compareDateTime(cmp1, cmp2)
 
-    list.sort(cmp=sortPeriods)
+    for period in periods:
+        period.adjustToUTC()
+    periods.sort(cmp=sortPeriods)
     
     # Now merge overlaps and consecutive periods
     index = None
     p = None
     pe = None
-    for i in xrange(len(list)):
+    for i in xrange(len(periods)):
         if p is None:
             index = i
-            p = list[i]
-            pe = periodEnd(p)
+            p = periods[i]
+            pe = p.getEnd()
             continue
-        ie = periodEnd(list[i])
-        if (pe >= list[i][0]):
+        ie = periods[i].getEnd()
+        if (pe >= periods[i].getStart()):
             if ie > pe:
-                list[index] = (list[index][0], ie)
+                periods[index] = PyCalendarPeriod(periods[index].getStart(), ie)
                 pe = ie
-            list[i] = None
+            periods[i] = None
         else:
             index = i
-            p = list[i]
-            pe = periodEnd(p)
-    list[:] = [x for x in list if x]
+            p = periods[i]
+            pe =p.getEnd()
+    periods[:] = [x for x in periods if x]
 
 def clipPeriod(period, clipPeriod):
     """
@@ -219,10 +209,10 @@
     @return: the (start, end) tuple for the clipped period, or
              None if the period is outside the clip period
     """
-    start = period[0]
-    end = periodEnd(period)
-    clipStart = clipPeriod[0]
-    clipEnd = periodEnd(clipPeriod)
+    start = period.getStart()
+    end = period.getEnd()
+    clipStart = clipPeriod.getStart()
+    clipEnd = clipPeriod.getEnd()
 
     if start < clipStart:
         start = clipStart
@@ -234,11 +224,25 @@
         return None
     else:
         # Try to preserve use of duration in period
-        if isinstance(period[1], datetime.timedelta):
-            return (start, end - start)
-        else:
-            return (start, end)
+        result = PyCalendarPeriod(start, end)
+        result.setUseDuration(period.getUseDuration())
+        return result
 
+def pyCalendarTodatetime(pydt):
+    
+    if pydt.isDateOnly():
+        return datetime.date(year=pydt.getYear(), month=pydt.getMonth(), day=pydt.getDay())
+    else:
+        return datetime.datetime(
+            year=pydt.getYear(),
+            month=pydt.getMonth(),
+            day=pydt.getDay(),
+            hour=pydt.getHours(),
+            minute=pydt.getMinutes(),
+            second=pydt.getSeconds(),
+            tzinfo=utc
+        )
+
 SQL_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%f"
 
 def parseSQLTimestamp(ts):
@@ -247,6 +251,18 @@
         ts += ".0"
     return datetime.datetime.strptime(ts, SQL_TIMESTAMP_FORMAT)
 
+def parseSQLTimestampToPyCalendar(ts):
+    """
+    Parse an SQL formated timestamp into a PyCalendarDateTime
+    @param ts: the SQL timestamp
+    @type ts: C{str}
+    
+    @return: L{PyCalendarDateTime} result
+    """
+    
+    dt = datetime.datetime.strptime(ts[:19], "%Y-%m-%d %H:%M:%S")
+    return PyCalendarDateTime(year=dt.year, month=dt.month, day=dt.day, hours=dt.hour, minutes=dt.minute, seconds=dt.second)
+
 def datetimeMktime(dt):
 
     assert isinstance(dt, datetime.date)

Modified: CalendarServer/trunk/twistedcaldav/freebusyurl.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/freebusyurl.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/freebusyurl.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -22,10 +22,6 @@
     "FreeBusyURLResource",
 ]
 
-import datetime
-
-from vobject.icalendar import utc
-
 from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 
 from twext.python.log import Logger
@@ -44,14 +40,15 @@
 from twistedcaldav.config import config
 from twistedcaldav.customxml import calendarserver_namespace
 from twistedcaldav.ical import Property
-from twistedcaldav.ical import parse_datetime
-from twistedcaldav.ical import parse_duration
 from twistedcaldav.resource import CalDAVResource, ReadOnlyNoCopyResourceMixIn
 from twistedcaldav.schedule import deliverSchedulePrivilegeSet
 from twistedcaldav.scheduling.caldav import ScheduleViaCalDAV
 from twistedcaldav.scheduling.cuaddress import LocalCalendarUser
 from twistedcaldav.scheduling.scheduler import Scheduler
 
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.duration import PyCalendarDuration
+
 log = Logger()
 
 
@@ -181,15 +178,15 @@
         # Start/end/duration must be valid iCalendar DATE-TIME UTC or DURATION values
         try:
             if self.start:
-                self.start = parse_datetime(self.start)
-                if self.start.tzinfo != utc:
+                self.start = PyCalendarDateTime.parseText(self.start)
+                if not self.start.utc():
                     raise ValueError()
             if self.end:
-                self.end = parse_datetime(self.end)
-                if self.end.tzinfo != utc:
+                self.end = PyCalendarDateTime.parseText(self.end)
+                if not self.end.utc():
                     raise ValueError()
             if self.duration:
-                self.duration = parse_duration(self.duration)
+                self.duration = PyCalendarDuration.parseText(self.duration)
         except ValueError:
             raise HTTPError(ErrorResponse(
                 responsecode.BAD_REQUEST,
@@ -208,7 +205,7 @@
             ))
         
         # Duration must be positive
-        if self.duration and self.duration.days < 0:
+        if self.duration and self.duration.getTotalSeconds() < 0:
             raise HTTPError(ErrorResponse(
                 responsecode.BAD_REQUEST,
                 (calendarserver_namespace, "valid-query-parameters"),
@@ -217,12 +214,12 @@
         
         # Now fill in the missing pieces
         if self.start is None:
-            now = datetime.datetime.now()
-            self.start = now.replace(hour=0, minute=0, second=0, tzinfo=utc)
+            self.start = PyCalendarDateTime.getNowUTC()
+            self.start.setHHMMSS(0, 0, 0)
         if self.duration:
             self.end = self.start + self.duration
         if self.end is None:
-            self.end = self.start + datetime.timedelta(days=config.FreeBusyURL.TimePeriod)
+            self.end = self.start + PyCalendarDuration(days=config.FreeBusyURL.TimePeriod)
             
         # End > start
         if self.end <= self.start:

Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/ical.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -14,6 +14,7 @@
 # limitations under the License.
 ##
 
+
 """
 iCalendar Utilities
 """
@@ -24,26 +25,15 @@
     "allowedComponents",
     "Property",
     "Component",
-    "FixedOffset",
-    "parse_date",
-    "parse_time",
-    "parse_datetime",
-    "parse_date_or_datetime",
-    "parse_duration",
     "tzexpand",
 ]
 
 import cStringIO as StringIO
-import datetime
+import codecs
 import heapq
 import itertools
 
-from vobject import newFromBehavior, readComponents
-from vobject.base import Component as vComponent, ContentLine as vContentLine, ParseError as vParseError
-from vobject.icalendar import TimezoneComponent, dateTimeToString, deltaToOffset, getTransition, stringToDate, stringToDateTime, stringToDurations, utc
-
 from twext.python.log import Logger
-from twext.python.datetime import dateordatetime, timerange, asUTC, iCalendarString
 from twext.web2.stream import IStream
 from twext.web2.dav.util import allDataFromStream
 
@@ -51,6 +41,17 @@
 from twistedcaldav.instance import InstanceList
 from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
 
+from pycalendar import definitions
+from pycalendar.attribute import PyCalendarAttribute
+from pycalendar.calendar import PyCalendar
+from pycalendar.componentbase import PyCalendarComponentBase
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.duration import PyCalendarDuration
+from pycalendar.exceptions import PyCalendarInvalidData
+from pycalendar.period import PyCalendarPeriod
+from pycalendar.property import PyCalendarProperty
+from pycalendar.timezone import PyCalendarTimezone
+from pycalendar.utcoffsetvalue import PyCalendarUTCOffsetValue
 
 log = Logger()
 
@@ -81,7 +82,7 @@
     "GEO":          (None, {"VALUE": "FLOAT"}),
     "LOCATION":     (None, {"VALUE": "TEXT"}),
     "PERCENT-COMPLETE": (None, {"VALUE": "INTEGER"}),
-    "PRIORITY":     ("0", {"VALUE": "INTEGER"}),
+    "PRIORITY":     (0, {"VALUE": "INTEGER"}),
     "RESOURCES":    (None, {"VALUE": "TEXT"}),
     "STATUS":       (None, {"VALUE": "TEXT"}),
     "SUMMARY":      (None, {"VALUE": "TEXT"}),
@@ -116,12 +117,12 @@
     "RDATE":        (None, {"VALUE": "DATE-TIME"}),
     "RRULE":        (None, {"VALUE": "RECUR"}),
     "ACTION":       (None, {"VALUE": "TEXT"}),
-    "REPEAT":       ("0", {"VALUE": "INTEGER"}),
+    "REPEAT":       (0, {"VALUE": "INTEGER"}),
     "TRIGGER":      (None, {"VALUE": "DURATION"}),
     "CREATED":      (None, {"VALUE": "DATE-TIME"}),
     "DTSTAMP":      (None, {"VALUE": "DATE-TIME"}),
     "LAST-MODIFIED": (None, {"VALUE": "DATE-TIME"}),
-    "SEQUENCE":     ("0", {"VALUE": "INTEGER"}),
+    "SEQUENCE":     (0, {"VALUE": "INTEGER"}),
     "REQUEST-STATUS": (None, {"VALUE": "TEXT"}),
 }
 
@@ -151,18 +152,19 @@
             assert value  is None
             assert params is None
 
-            vobj = kwargs["vobject"]
+            pyobj = kwargs["pycalendar"]
 
-            if not isinstance(vobj, vContentLine):
-                raise TypeError("Not a vContentLine: %r" % (property,))
+            if not isinstance(pyobj, PyCalendarProperty):
+                raise TypeError("Not a PyCalendarProperty: %r" % (property,))
 
-            self._vobject = vobj
+            self._pycalendar = pyobj
         else:
-            # Convert params dictionary to list of lists format used by vobject
-            lparams = [[key] + lvalue for key, lvalue in params.items()]
-            self._vobject = vContentLine(name, lparams, value, isNative=True)
+            # Convert params dictionary to list of lists format used by pycalendar
+            self._pycalendar = PyCalendarProperty(name, value)
+            for attrname, attrvalue in params.items():
+                self._pycalendar.addAttribute(PyCalendarAttribute(attrname, attrvalue))
 
-    def __str__ (self): return self._vobject.serialize()
+    def __str__ (self): return str(self._pycalendar)
     def __repr__(self): return "<%s: %r: %r>" % (self.__class__.__name__, self.name(), self.value())
 
     def __hash__(self):
@@ -171,7 +173,7 @@
     def __ne__(self, other): return not self.__eq__(other)
     def __eq__(self, other):
         if not isinstance(other, Property): return False
-        return self.name() == other.name() and self.value() == other.value() and self.params() == other.params()
+        return self._pycalendar == other._pycalendar
 
     def __gt__(self, other): return not (self.__eq__(other) or self.__lt__(other))
     def __lt__(self, other):
@@ -186,44 +188,66 @@
     def __ge__(self, other): return self.__eq__(other) or self.__gt__(other)
     def __le__(self, other): return self.__eq__(other) or self.__lt__(other)
 
-    def name  (self): return self._vobject.name
+    def name  (self): return self._pycalendar.getName()
 
-    def value (self): return self._vobject.value
+    def value (self): return self._pycalendar.getValue().getValue()
+
+    def strvalue (self): return str(self._pycalendar.getValue())
+
     def setValue(self, value):
-        self._vobject.value = value
+        self._pycalendar.setValue(value)
 
-    def params(self):
+    def parameterNames(self):
         """
-        Returns a mapping object containing parameters for this property.
-
-        Keys are parameter names, values are sequences containing
-        values for the named parameter.
+        Returns a set containing parameter names for this property.
         """
-        return self._vobject.params
+        result = set()
+        for pyattrlist in self._pycalendar.getAttributes().values():
+            for pyattr in pyattrlist:
+                result.add(pyattr.getName())
+        return result
 
-    def paramValue(self, name):
+    def parameterValue(self, name, default=None):
         """
         Returns a single value for the given parameter.  Raises
         InvalidICalendarDataError if the parameter has more than one value.
         """
-        values = self._vobject.params.get(name, [None,])
-        assert type(values) is list, "vobject returned non-list value for parameter %r in property %r" % (name, self)
-        if len(values) != 1:
-            raise InvalidICalendarDataError("Not exactly one %s value in property %r" % (name, self))
-        return values[0]
+        try:
+            return self._pycalendar.getAttributeValue(name)
+        except KeyError:
+            return default
 
-    def containsTimeRange(self, start, end, tzinfo=None):
+    def hasParameter(self, paramname):
+        return self._pycalendar.hasAttribute(paramname)
+
+    def setParameter(self, paramname, paramvalue):
+        self._pycalendar.replaceAttribute(PyCalendarAttribute(paramname, paramvalue))
+
+    def removeParameter(self, paramname):
+        self._pycalendar.removeAttributes(paramname)
+
+    def removeAllParameters(self):
+        self._pycalendar.setAttributes({})
+
+    def removeParameterValue(self, paramname, paramvalue):
+        
+        for attr in tuple(self._pycalendar.getAttributes()):
+            if attr.getName() == paramname:
+                for value in attr.getValues():
+                    if value == paramvalue:
+                        if not attr.removeValue(value):
+                            self._pycalendar.removeAttributes(paramname)
+
+    def containsTimeRange(self, start, end, defaulttz=None):
         """
         Determines whether this property contains a date/date-time within the specified
         start/end period.
         The only properties allowed for this query are: COMPLETED, CREATED, DTSTAMP and
         LAST-MODIFIED (caldav -09).
-        @param start: a L{datetime.datetime} or L{datetime.date} specifying the
-            beginning of the given time span.
-        @param end: a L{datetime.datetime} or L{datetime.date} specifying the
-            end of the given time span.  C{end} may be None, indicating that
-            there is no end date.
-        @param tzinfo: the default L{datetime.tzinfo} to use in datetime comparisons.
+        @param start: a L{PyCalendarDateTime} specifying the beginning of the given time span.
+        @param end: a L{PyCalendarDateTime} specifying the end of the given time span.
+            C{end} may be None, indicating that there is no end date.
+        @param defaulttz: the default L{PyTimezone} to use in datetime comparisons.
         @return: True if the property's date/date-time value is within the given time range,
                  False if not, or the property is not an appropriate date/date-time value.
         """
@@ -234,24 +258,11 @@
             return False
         
         # get date/date-time value
-        dt = self.value()
-        assert isinstance(dt, datetime.date), "Not a date/date-time value: %r" % (self,)
+        dt = self._pycalendar.getValue().getValue()
+        assert isinstance(dt, PyCalendarDateTime), "Not a date/date-time value: %r" % (self,)
         
-        return timeRangesOverlap(dt, None, start, end, tzinfo)
+        return timeRangesOverlap(dt, None, start, end, defaulttz)
 
-    def transformAllFromNative(self):
-        transformed = self._vobject.isNative
-        if transformed:
-            self._vobject = self._vobject.transformFromNative()
-            self._vobject.transformChildrenFromNative()
-        return transformed
-        
-    def transformAllToNative(self):
-        transformed = not self._vobject.isNative
-        if transformed:
-            self._vobject = self._vobject.transformToNative()
-            self._vobject.transformChildrenToNative()
-        return transformed
 
 class Component (object):
     """
@@ -323,6 +334,14 @@
         """
         if type(string) is unicode:
             string = string.encode("utf-8")
+        else:
+            # Valid utf-8 please
+            string.decode("utf-8")
+        
+        # No BOMs please
+        if string[:3] == codecs.BOM_UTF8:
+            string = string[3:]
+
         return clazz.fromStream(StringIO.StringIO(string))
 
     @classmethod
@@ -333,15 +352,15 @@
         @return: a L{Component} representing the first component described by
             C{stream}.
         """
+        cal = PyCalendar()
         try:
-            return clazz(None, vobject=readComponents(stream, findBegin=False).next())
-        except UnicodeDecodeError, e:
+            result = cal.parse(stream)
+        except PyCalendarInvalidData:
+            result = None
+        if not result:
             stream.seek(0)
-            raise InvalidICalendarDataError("%s: %s" % (e, stream.read()))
-        except vParseError, e:
-            raise InvalidICalendarDataError(e)
-        except StopIteration, e:
-            raise InvalidICalendarDataError(e)
+            raise InvalidICalendarDataError("%s" % (stream.read(),))
+        return clazz(None, pycalendar=cal)
 
     @classmethod
     def fromIStream(clazz, stream):
@@ -369,14 +388,14 @@
             component.
         """
         if name is None:
-            if "vobject" in kwargs:
-                vobj = kwargs["vobject"]
+            if "pycalendar" in kwargs:
+                pyobj = kwargs["pycalendar"]
 
-                if vobj is not None:
-                    if not isinstance(vobj, vComponent):
-                        raise TypeError("Not a vComponent: %r" % (vobj,))
+                if pyobj is not None:
+                    if not isinstance(pyobj, PyCalendarComponentBase):
+                        raise TypeError("Not a PyCalendarComponentBase: %r" % (pyobj,))
 
-                self._vobject = vobj
+                self._pycalendar = pyobj
             else:
                 raise AssertionError("name may not be None")
 
@@ -393,59 +412,37 @@
             else:
                 self._parent = None
         else:
-            self._vobject = newFromBehavior(name)
+            # FIXME: figure out creating an arbitrary component
+            self._pycalendar = PyCalendar(add_defaults=False) if name == "VCALENDAR" else PyCalendar.makeComponent(name, None)
             self._parent = None
 
-    def __str__ (self): return self._vobject.serialize()
-    def __repr__(self): return "<%s: %r>" % (self.__class__.__name__, str(self._vobject))
+    def __str__ (self):
+        return str(self._pycalendar)
 
+    def __repr__(self):
+        return "<%s: %r>" % (self.__class__.__name__, str(self._pycalendar))
+
     def __hash__(self):
         return hash(str(self))
 
-    def __ne__(self, other): return not self.__eq__(other)
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
     def __eq__(self, other):
         if not isinstance(other, Component):
             return False
+        return self._pycalendar == other._pycalendar
 
-        my_properties = set(self.properties())
-        for property in other.properties():
-            if property in my_properties:
-                my_properties.remove(property)
-            else:
-                return False
-        if my_properties:
-            return False
-
-        my_subcomponents = set(self.subcomponents())
-        for subcomponent in other.subcomponents():
-            for testcomponent in my_subcomponents:
-                if subcomponent == testcomponent:
-                    my_subcomponents.remove(testcomponent)
-                    break
-            else:
-                return False
-        if my_subcomponents:
-            return False
-
-        return True
-
     # FIXME: Should this not be in __eq__?
     def same(self, other):
-        return self._vobject == other._vobject
+        return self._pycalendar == other._pycalendar
     
     def name(self):
         """
         @return: the name of the iCalendar type of this component.
         """
-        return self._vobject.name
+        return self._pycalendar.getType()
 
-    def setBehavior(self, behavior):
-        """
-        Set the behavior of the underlying iCal object.
-        @param behavior: the behavior type to set.
-        """
-        self._vobject.setBehavior(behavior)
-
     def mainType(self):
         """
         Determine the primary type of iCal component in this calendar.
@@ -507,20 +504,20 @@
         Return the overridden iCal component in this calendar matching the supplied RECURRENCE-ID property.
 
         @param recurrence_id: The RECURRENCE-ID property value to match.
-        @type recurrence_id: L{datetime.datetime} or L{datetime.date}
+        @type recurrence_id: L{PyCalendarDateTime}
         @return: the L{Component} for the overridden component,
             or C{None} if there isn't one.
         """
         assert self.name() == "VCALENDAR", "Must be a VCALENDAR: %r" % (self,)
         
         if isinstance(recurrence_id, str):
-            recurrence_id = parse_date_or_datetime(recurrence_id) if recurrence_id else None
+            recurrence_id = PyCalendarDateTime.parseText(recurrence_id) if recurrence_id else None
 
         for component in self.subcomponents():
             if component.name() in ignoredComponents:
                 continue
             rid = component.getRecurrenceIDUTC()
-            if rid and recurrence_id and dateordatetime(rid) == recurrence_id:
+            if rid and recurrence_id and rid == recurrence_id:
                 return component
             elif rid is None and recurrence_id is None:
                 return component
@@ -544,7 +541,7 @@
         Duplicate this object and all its contents.
         @return: the duplicated calendar.
         """
-        return Component(None, vobject=vComponent.duplicate(self._vobject))
+        return Component(None, pycalendar=self._pycalendar.duplicate())
         
     def subcomponents(self):
         """
@@ -552,9 +549,8 @@
             of this component.
         """
         return (
-            Component(None, vobject=c, parent=self)
-            for c in self._vobject.getChildren()
-            if isinstance(c, vComponent)
+            Component(None, pycalendar=c, parent=self)
+            for c in self._pycalendar.getComponents()
         )
 
     def addComponent(self, component):
@@ -563,7 +559,7 @@
         @param component: the L{Component} to add as a subcomponent of this
             component.
         """
-        self._vobject.add(component._vobject)
+        self._pycalendar.addComponent(component._pycalendar)
         component._parent = self
 
     def removeComponent(self, component):
@@ -571,17 +567,14 @@
         Removes a subcomponent from this component.
         @param component: the L{Component} to remove.
         """
-        self._vobject.remove(component._vobject)
+        self._pycalendar.removeComponent(component._pycalendar)
 
     def hasProperty(self, name):
         """
         @param name: the name of the property whose existence is being tested.
         @return: True if the named property exists, False otherwise.
         """
-        try:
-            return len(self._vobject.contents[name.lower()]) > 0
-        except KeyError:
-            return False
+        return self._pycalendar.hasProperty(name)
 
     def getProperty(self, name):
         """
@@ -602,18 +595,15 @@
         @return: an iterable of L{Property} objects, one for each property of
             this component.
         """
+        properties = []
         if name is None:
-            properties = self._vobject.getChildren()
-        else:
-            try:
-                properties = self._vobject.contents[name.lower()]
-            except KeyError:
-                return ()
+            [properties.extend(i) for i in self._pycalendar.getProperties().values()]
+        elif self._pycalendar.countProperty(name) > 0:
+            properties = self._pycalendar.getProperties(name)
 
         return (
-            Property(None, None, None, vobject=p)
+            Property(None, None, None, pycalendar=p)
             for p in properties
-            if isinstance(p, vContentLine)
         )
 
     def propertyValue(self, name):
@@ -625,42 +615,15 @@
         return None
 
 
-    def propertyNativeValue(self, name):
-        """
-        Return the native property value for the named property in the supplied component.
-        NB Assumes a single property exists in the component.
-        @param name: the name of the property whose value is required
-        @return: the native property value
-        """
-        properties = tuple(self.properties(name))
-
-        if len(properties) == 1:
-            transormed = properties[0].transformAllToNative()
-    
-            result = properties[0].value()
-    
-            if transormed:
-                properties[0].transformAllFromNative()
-                
-            return result
-
-        elif len(properties) > 1:
-            raise InvalidICalendarDataError("More than one %s property in component %r" % (name, self))
-        else:
-            return None
-
     def getStartDateUTC(self):
         """
         Return the start date or date-time for the specified component
         converted to UTC.
         @param component: the Component whose start should be returned.
-        @return: the datetime.date or datetime.datetime for the start.
+        @return: the L{PyCalendarDateTime} for the start.
         """
-        dtstart = self.propertyNativeValue("DTSTART")
-        if dtstart is not None:
-            return asUTC(dtstart)
-        else:
-            return None
+        dtstart = self.propertyValue("DTSTART")
+        return dtstart.duplicateAsUTC() if dtstart is not None else None
  
     def getEndDateUTC(self):
         """
@@ -668,51 +631,41 @@
         taking into account the presence or absence of DTEND/DURATION properties.
         The returned date-time is converted to UTC.
         @param component: the Component whose end should be returned.
-        @return: the datetime.date or datetime.datetime for the end.
+        @return: the L{PyCalendarDateTime} for the end.
         """
-        dtend = self.propertyNativeValue("DTEND")
+        dtend = self.propertyValue("DTEND")
         if dtend is None:
-            dtstart = self.propertyNativeValue("DTSTART")
-            duration = self.propertyNativeValue("DURATION")
+            dtstart = self.propertyValue("DTSTART")
+            duration = self.propertyValue("DURATION")
             if duration is not None:
                 dtend = dtstart + duration
 
-        if dtend is not None:
-            return asUTC(dtend)
-        else:
-            return None
+        return dtend.duplicateAsUTC() if dtend is not None else None
 
     def getDueDateUTC(self):
         """
         Return the due date or date-time for the specified component
         converted to UTC. Use DTSTART/DURATION if no DUE property.
         @param component: the Component whose start should be returned.
-        @return: the datetime.date or datetime.datetime for the start.
+        @return: the L{PyCalendarDateTime} for the start.
         """
-        due = self.propertyNativeValue("DUE")
+        due = self.propertyValue("DUE")
         if due is None:
-            dtstart = self.propertyNativeValue("DTSTART")
-            duration = self.propertyNativeValue("DURATION")
+            dtstart = self.propertyValue("DTSTART")
+            duration = self.propertyValue("DURATION")
             if dtstart is not None and duration is not None:
                 due = dtstart + duration
 
-        if due is not None:
-            return asUTC(due)
-        else:
-            return None
+        return due.duplicateAsUTC() if due is not None else None
  
     def getRecurrenceIDUTC(self):
         """
         Return the recurrence-id for the specified component.
         @param component: the Component whose r-id should be returned.
-        @return: the datetime.date or datetime.datetime for the r-id.
+        @return: the L{PyCalendarDateTime} for the r-id.
         """
-        rid = self.propertyNativeValue("RECURRENCE-ID")
-
-        if rid is not None:
-            return asUTC(rid)
-        else:
-            return None
+        rid = self.propertyValue("RECURRENCE-ID")
+        return rid.duplicateAsUTC() if rid is not None else None
  
     def getRange(self):
         """
@@ -722,7 +675,7 @@
         """
         ridprop = self.getProperty("RECURRENCE-ID")
         if ridprop is not None:
-            range = ridprop.paramValue("RANGE")
+            range = ridprop.parameterValue("RANGE")
             if range is not None:
                 return (range == "THISANDFUTURE")
 
@@ -733,7 +686,7 @@
         Return the trigger information for the specified alarm component.
         @param component: the Component whose start should be returned.
         @return: ta tuple consisting of:
-            trigger : the 'native' trigger value (either datetime.date or datetime.timedelta)
+            trigger : the 'native' trigger value
             related : either True (for START) or False (for END)
             repeat : an integer for the REPEAT count
             duration: the repeat duration if present, otherwise None
@@ -741,38 +694,33 @@
         assert self.name() == "VALARM", "Component is not a VAlARM: %r" % (self,)
         
         # The trigger value
-        trigger = self.propertyNativeValue("TRIGGER")
+        trigger = self.propertyValue("TRIGGER")
         if trigger is None:
             raise InvalidICalendarDataError("VALARM has no TRIGGER property: %r" % (self,))
         
         # The related parameter
-        related = self.getProperty("TRIGGER").paramValue("RELATED")
+        related = self.getProperty("TRIGGER").parameterValue("RELATED")
         if related is None:
             related = True
         else:
             related = (related == "START")
         
         # Repeat property
-        repeat = self.propertyNativeValue("REPEAT")
+        repeat = self.propertyValue("REPEAT")
         if repeat is None: repeat = 0
         else: repeat = int(repeat)
         
         # Duration property
-        duration = self.propertyNativeValue("DURATION")
+        duration = self.propertyValue("DURATION")
 
         if repeat > 0 and duration is None:
             raise InvalidICalendarDataError("VALARM has invalid REPEAT/DURATIOn properties: %r" % (self,))
 
         return (trigger, related, repeat, duration)
  
-    def getRRuleSet(self, addRDate = False):
-        self.transformAllToNative()
-        return self._vobject.getrruleset(addRDate)
+    def getRecurrenceSet(self):
+        return self._pycalendar.getRecurrenceSet()
 
-    def setRRuleSet(self, rruleset):
-        #self.transformAllToNative()
-        return self._vobject.setrruleset(rruleset)
-
     def getEffectiveStartEnd(self):
         # Get the start/end range needed for instance comparisons
 
@@ -808,14 +756,16 @@
         Adds a property to this component.
         @param property: the L{Property} to add to this component.
         """
-        self._vobject.add(property._vobject)
+        self._pycalendar.addProperty(property._pycalendar)
+        self._pycalendar.finalise()
 
     def removeProperty(self, property):
         """
         Remove a property from this component.
         @param property: the L{Property} to remove from this component.
         """
-        self._vobject.remove(property._vobject)
+        self._pycalendar.removeProperty(property._pycalendar)
+        self._pycalendar.finalise()
 
     def replaceProperty(self, property):
         """
@@ -824,8 +774,7 @@
         """
         
         # Remove all existing ones first
-        for removeit in tuple(self.properties(property.name())):
-            self.removeProperty(removeit)
+        self._pycalendar.removeProperties(property.name())
         self.addProperty(property)
 
     def timezoneIDs(self):
@@ -837,20 +786,10 @@
         result = set()
 
         for property in self.properties():
-            for propertyname in ("TZID", "X-VOBJ-ORIGINAL-TZID"):
-                tzid = property.paramValue(propertyname)
-                if tzid is not None:
-                    result.add(tzid)
-                    break
-            else:
-                items = property.value()
-                if type(items) is not list:
-                    items = [items]
-                for item in items:
-                    tzinfo = getattr(item, 'tzinfo', None)
-                    tzid = TimezoneComponent.pickTzid(tzinfo)
-                    if tzid is not None:
-                        result.add(tzid)
+            tzid = property.parameterValue("TZID")
+            if tzid is not None:
+                result.add(tzid)
+                break
         
         return result
     
@@ -879,47 +818,48 @@
         @type maximumCount: C{int}
         @return: a C{bool} indicating whether a change was made or not
         """
-
+        
         changed = False
         master = self.masterComponent()
         if master and master.isRecurring():
-            rrules = master.getRRuleSet()
+            rrules = master._pycalendar.getRecurrenceSet()
             if rrules:
-                for rrule in rrules._rrule:
-                    if rrule._count is not None:
+                for rrule in rrules.getRules():
+                    if rrule.getUseCount():
                         # Make sure COUNT is less than the limit
-                        if rrule._count > maximumCount:
-                            rrule._count = maximumCount
+                        if rrule.getCount() > maximumCount:
+                            rrule.setCount(maximumCount)
                             changed = True
-                    elif rrule._until is not None:
+                    elif rrule.getUseUntil():
                         # Need to figure out how to determine number of instances
                         # with this UNTIL and truncate if needed
                         start = master.getStartDateUTC()
-                        diff = differenceDateTime(start, rrule._until)
-                        diff = diff.days * 24 * 60 * 60 + diff.seconds
+                        diff = differenceDateTime(start, rrule.getUntil())
+                        diff = diff.getDays() * 24 * 60 * 60 + diff.getSeconds()
                         
                         period = {
-                            0: 365 * 24 * 60 * 60,
-                            1:  30 * 24 * 60 * 60,
-                            2:   7 * 24 * 60 * 60,
-                            3:   1 * 24 * 60 * 60,
-                            4:   60 * 60,
-                            5:   60,
-                            6:   1
-                        }[rrule._freq] * rrule._interval
+                            definitions.eRecurrence_YEARLY:   365 * 24 * 60 * 60,
+                            definitions.eRecurrence_MONTHLY:  30 * 24 * 60 * 60,
+                            definitions.eRecurrence_WEEKLY:   7 * 24 * 60 * 60,
+                            definitions.eRecurrence_DAILY:    1 * 24 * 60 * 60,
+                            definitions.eRecurrence_HOURLY:   60 * 60,
+                            definitions.eRecurrence_MINUTELY: 60,
+                            definitions.eRecurrence_SECONDLY: 1
+                        }[rrule.getFreq()] * rrule.getInterval()
                         
                         if diff / period > maximumCount:
-                            rrule._until = None
-                            rrule._count = maximumCount
+                            rrule.setUseUntil(False)
+                            rrule.setUseCount(True)
+                            rrule.setCount(maximumCount)
+                            rrules.changed()
                             changed = True
                     else:
                         # For frequencies other than yearly we will truncate at our limit
-                        if rrule._freq != 0:
-                            rrule._count = maximumCount
+                        if rrule.getFreq() != definitions.eRecurrence_YEARLY:
+                            rrule.setUseCount(True)
+                            rrule.setCount(maximumCount)
+                            rrules.changed()
                             changed = True
-                
-                if changed:
-                    master.setRRuleSet(rrules)
 
         return changed
 
@@ -928,13 +868,13 @@
         Expand the components into a set of new components, one for each
         instance in the specified range. Date-times are converted to UTC. A
         new calendar object is returned.
-        @param start: the L{datetime.datetime} for the start of the range.
-        @param end: the L{datetime.datetime} for the end of the range.
+        @param start: the L{PyCalendarDateTime} for the start of the range.
+        @param end: the L{PyCalendarDateTime} for the end of the range.
         @param timezone: the L{Component} the VTIMEZONE to use for floating/all-day.
         @return: the L{Component} for the new calendar with expanded instances.
         """
         
-        tzinfo = timezone.gettzinfo() if timezone else None
+        pytz = PyCalendarTimezone(tzid=timezone.propertyValue("TZID")) if timezone else None
 
         # Create new calendar object with same properties as the original, but
         # none of the originals sub-components
@@ -949,7 +889,7 @@
         first = True
         for key in instances:
             instance = instances[key]
-            if timeRangesOverlap(instance.start, instance.end, start, end, tzinfo):
+            if timeRangesOverlap(instance.start, instance.end, start, end, pytz):
                 calendar.addComponent(self.expandComponent(instance, first))
             first = False
         
@@ -974,8 +914,8 @@
         # Convert all datetime properties to UTC unless they are floating
         for property in newcomp.properties():
             value = property.value()
-            if isinstance(value, datetime.datetime) and value.tzinfo is not None:
-                property.setValue(value.astimezone(utc))
+            if isinstance(value, PyCalendarDateTime) and value.local():
+                property.setValue(value.duplicateAsUTC())
         
         # Now reset DTSTART, DTEND/DURATION
         for property in newcomp.properties("DTSTART"):
@@ -988,7 +928,6 @@
         # Add RECURRENCE-ID if not first instance
         if not first:
             newcomp.addProperty(Property("RECURRENCE-ID", instance.rid))
-            newcomp.transformAllToNative()
 
         return newcomp
 
@@ -998,7 +937,7 @@
         so we can return cached results in the future.
  
         @param limit: the max datetime to cache up to.
-        @type limit: L{datetime.datetime} or L{datetime.date}
+        @type limit: L{PyCalendarDateTime}
         """
         
         # Checked for cached values first
@@ -1018,7 +957,7 @@
         contained within this VCALENDAR component. We will assume
         that this component has already been validated as a CalDAV resource
         (i.e. only one type of component, all with the same UID)
-        @param limit: datetime.date value representing the end of the expansion.
+        @param limit: L{PyCalendarDateTime} value representing the end of the expansion.
         @param ignoreInvalidInstances: C{bool} whether to ignore instance errors.
         @return: a set of Instances for each recurrence in the set.
         """
@@ -1032,7 +971,7 @@
         What we do is first expand the master instance into the set of generate
         instances. Then we merge the overridden instances, taking into account
         THISANDFUTURE and THISANDPRIOR.
-        @param limit: datetime.date value representing the end of the expansion.
+        @param limit: L{PyCalendarDateTime} value representing the end of the expansion.
         @param componentSet: the set of components that are to make up the
                 recurrence set. These MUST all be components with the same UID
                 and type, forming a proper recurring set.
@@ -1087,8 +1026,7 @@
         if master:
             rrules = master.properties("RRULE")
             for rrule in rrules:
-                s = str(rrule)
-                if "COUNT" not in s and "UNTIL" not in s:
+                if not rrule.value().getUseCount() and not rrule.value().getUseUntil():
                     return True
         return False
         
@@ -1100,7 +1038,7 @@
         is added as STATUS:CANCELLED and the EXDATE removed.
 
         @param rid: recurrence-id value
-        @type rid: L{datetime.datetime}
+        @type rid: L{PyCalendarDateTime}
         @param allowCancelled: whether to allow a STATUS:CANCELLED override
         @type allowCancelled: C{bool}
         
@@ -1117,7 +1055,7 @@
         didCancel = False
         for exdate in tuple(master.properties("EXDATE")):
             for exdateValue in exdate.value():
-                if exdateValue == rid:
+                if exdateValue.getValue() == rid:
                     if allowCancelled:
                         exdate.value().remove(exdateValue)
                         if len(exdate.value()) == 0:
@@ -1135,13 +1073,13 @@
         # Check whether recurrence-id matches an RDATE - if so it is OK
         rdates = set()
         for rdate in master.properties("RDATE"):
-            rdates.update([asUTC(item) for item in rdate.value()])
+            rdates.update([item.getValue().duplicateAsUTC() for item in rdate.value()])
         if rid not in rdates:
             # Check whether we have a truncated RRULE
             rrules = master.properties("RRULE")
             if len(tuple(rrules)):
-                limit = rid
-                limit += datetime.timedelta(days=365)
+                limit = rid.duplicate()
+                limit += PyCalendarDuration(days=365)
                 instances = self.cacheExpandedTimeRanges(limit)
                 rids = set([instances[key].rid for key in instances])
                 instance_rid = normalizeForIndex(rid)
@@ -1166,28 +1104,26 @@
         if newcomp.hasProperty("DTEND"):
             dtend = newcomp.getProperty("DTEND")
             oldduration = dtend.value() - dtstart.value()
-            
-        if isinstance(dtstart.value(), datetime.datetime):
-            if dtstart.value().tzinfo:
-                newdtstartValue = rid.astimezone(dtstart.value().tzinfo)
-            else:
-                newdtstartValue = rid
+        
+        newdtstartValue = rid.duplicate()
+        if not dtstart.value().isDateOnly():
+            if dtstart.value().local():
+                newdtstartValue.adjustTimezone(dtstart.value().getTimezone())
         else:
-            newdtstartValue = datetime.date.fromordinal(rid.toordinal())
+            newdtstartValue.setDateOnly(True)
             
         dtstart.setValue(newdtstartValue)
         if newcomp.hasProperty("DTEND"):
             dtend.setValue(newdtstartValue + oldduration)
 
-        try:
-            rid_params = {"X-VOBJ-ORIGINAL-TZID":dtstart.params()["X-VOBJ-ORIGINAL-TZID"]}
-        except KeyError:
-            rid_params = {}
-        newcomp.addProperty(Property("RECURRENCE-ID", dtstart.value(), params=rid_params))
+        newcomp.addProperty(Property("RECURRENCE-ID", dtstart.value(), params={}))
         
         if didCancel:
             newcomp.addProperty(Property("STATUS", "CANCELLED"))
 
+        # After creating/changing a component we need to do this to keep PyCalendar happy
+        newcomp._pycalendar.finalise()
+
         return newcomp
         
     def validInstances(self, rids):
@@ -1204,7 +1140,7 @@
         non_master_rids = [rid for rid in rids if rid is not None]
         if non_master_rids:
             highest_rid = max(non_master_rids)
-            self.cacheExpandedTimeRanges(highest_rid + datetime.timedelta(days=1))
+            self.cacheExpandedTimeRanges(highest_rid + PyCalendarDuration(days=1))
         for rid in rids:
             if self.validInstance(rid, clear_cache=False):
                 valid.add(rid)
@@ -1215,7 +1151,7 @@
         Test whether the specified recurrence-id is a valid instance in this event.
 
         @param rid: recurrence-id value
-        @type rid: L{datetime.datetime}
+        @type rid: L{PyCalendarDateTime}
         
         @return: C{bool}
         """
@@ -1231,7 +1167,7 @@
             return False
 
         # Get expansion
-        instances = self.cacheExpandedTimeRanges(rid + datetime.timedelta(days=1))
+        instances = self.cacheExpandedTimeRanges(rid + PyCalendarDuration(days=1))
         new_rids = set([instances[key].rid for key in instances])
         return rid in new_rids
 
@@ -1406,28 +1342,25 @@
                 # If they're not both date or both date-time, raise error
                 if (subcomponent.hasProperty("DTSTART") and
                     subcomponent.hasProperty("RRULE")):
-                    dtValue = subcomponent.propertyNativeValue("DTSTART")
-                    dtType = type(dtValue)
+                    dtValue = subcomponent.propertyValue("DTSTART")
+                    dtutc = dtValue.duplicateAsUTC()
                     # Using properties("RRULE") rather than getRRuleSet() here
                     # because the dateutil rrule's _until values are datetime
                     # even if the UNTIL is a date (and therefore we can't
                     # check validity without doing the following):
-                    for rrule in subcomponent.properties("RRULE"):
-                        indexedTokens = {}
-                        indexedTokens.update([valuePart.split("=")
-                            for valuePart in rrule.value().split(";")])
-                        until = indexedTokens.get("UNTIL", None)
-                        if until:
-                            untilType = datetime.date if len(until) == 8 else datetime.datetime
-                            if untilType is not dtType:
+                    rrules = subcomponent._pycalendar.getRecurrenceSet()
+                    for rrule in rrules.getRules():
+                        if rrule.getUseUntil():
+                            if rrule.getUntil().isDateOnly() ^ dtValue.isDateOnly():
                                 msg = "Calendar resources must have matching type for DTSTART and UNTIL"
                                 log.debug(msg)
                                 if fix:
-                                    rrules = subcomponent.getRRuleSet()
-                                    if rrules:
-                                        log.debug("Fixing mismatch")
-                                        # vobject fixes DTSTART/UNTIL mismatches
-                                        subcomponent.setRRuleSet(rrules)
+                                    log.debug("Fixing mismatch")
+                                    rrule.getUntil().setDateOnly(dtValue.isDateOnly())
+                                    if not dtValue.isDateOnly():
+                                        rrule.getUntil().setHHMMSS(dtutc.getHours(), dtutc.getMinutes(), dtutc.getSeconds())
+                                        rrule.getUntil().setTimezone(PyCalendarTimezone(utc=True))
+                                    rrules.changed()
                                 else:
                                     raise InvalidICalendarDataError(msg)
 
@@ -1492,26 +1425,18 @@
 
         return foundOrganizer
 
-    def transformAllFromNative(self):
-        self._vobject = self._vobject.transformFromNative()
-        self._vobject.transformChildrenFromNative(False)
-        
-    def transformAllToNative(self):
-        self._vobject = self._vobject.transformToNative()
-        self._vobject.transformChildrenToNative()
-
-    def gettzinfo(self):
+    def gettimezone(self):
         """
-        Get the tzinfo for a Timezone component.
+        Get the PyCalendarTimezone for a Timezone component.
 
-        @return: L{datetime.tzinfo} if this is a VTIMEZONE, otherwise None.
+        @return: L{PyCalendarTimezone} if this is a VTIMEZONE, otherwise None.
         """
         if self.name() == "VTIMEZONE":
-            return self._vobject.gettzinfo()
+            return PyCalendarTimezone(tzid=self._pycalendar.getID())
         elif self.name() == "VCALENDAR":
             for component in self.subcomponents():
                 if component.name() == "VTIMEZONE":
-                    return component.gettzinfo()
+                    return component.gettimezone()
             else:
                 return None
         else:
@@ -1639,8 +1564,8 @@
 
         is_server = False
         organizerProp = self.getOrganizerProperty()
-        if "SCHEDULE-AGENT" in organizerProp.params():
-            if organizerProp.paramValue("SCHEDULE-AGENT") == "SERVER":
+        if organizerProp.hasParameter("SCHEDULE-AGENT"):
+            if organizerProp.parameterValue("SCHEDULE-AGENT") == "SERVER":
                 is_server = True
         else:
             is_server = True
@@ -1691,8 +1616,8 @@
             for attendee in tuple(self.properties("ATTENDEE")):
                 
                 if onlyScheduleAgentServer:
-                    if "SCHEDULE-AGENT" in attendee.params():
-                        if attendee.paramValue("SCHEDULE-AGENT") != "SERVER":
+                    if attendee.hasParameter("SCHEDULE-AGENT"):
+                        if attendee.parameterValue("SCHEDULE-AGENT") != "SERVER":
                             continue
 
                 cuaddr = attendee.value()
@@ -1810,7 +1735,7 @@
                 continue
             for property in component.properties(propname):
                 if propvalue is None or property.value() == propvalue:
-                    property.params()[paramname] = [paramvalue]
+                    property.setParameter(paramname, paramvalue)
     
     def hasPropertyInAnyComponent(self, properties):
         """
@@ -1980,8 +1905,8 @@
                     found_all_attendees = False
                     break
                 if onlyScheduleAgentServer:
-                    if "SCHEDULE-AGENT" in foundAttendee.params():
-                        if foundAttendee.paramValue("SCHEDULE-AGENT") != "SERVER":
+                    if foundAttendee.hasParameter("SCHEDULE-AGENT"):
+                        if foundAttendee.parameterValue("SCHEDULE-AGENT") != "SERVER":
                             found_all_attendees = False
                             break
             if not found_all_attendees:
@@ -2019,7 +1944,7 @@
                 remaining -= 1
                 continue
             rid = component.getRecurrenceIDUTC()
-            if (iCalendarString(rid) if rid else "") not in rids:
+            if (rid.getText() if rid else "") not in rids:
                 self.removeComponent(component)
                 remaining -= 1
                 
@@ -2093,9 +2018,9 @@
                 if xpname and p.name() not in keep_properties:
                     self.removeProperty(p)
                 elif not xpname and remove_x_parameters:
-                    for paramname in tuple(p.params()):
+                    for paramname in p.parameterNames():
                         if paramname.startswith("X-"):
-                            del p.params()[paramname]
+                            p.removeParameter(paramname)
             
     def removePropertyParameters(self, property, params):
         """
@@ -2111,10 +2036,7 @@
             props = self.properties(property)
             for prop in props:
                 for param in params:
-                    try:
-                        del prop.params()[param]
-                    except KeyError:
-                        pass
+                    prop.removeParameter(param)
 
     def removePropertyParametersByValue(self, property, paramvalues):
         """
@@ -2130,14 +2052,7 @@
             props = self.properties(property)
             for prop in props:
                 for param, value in paramvalues:
-                    try:
-                        prop.params()[param].remove(value)
-                        if len(prop.params()[param]) == 0:
-                            del prop.params()[param]
-                    except KeyError:
-                        pass
-                    except InvalidICalendarDataError:
-                        pass
+                    prop.removeParameterValue(param, value)
 
     def normalizeAll(self):
         
@@ -2152,13 +2067,14 @@
                 default_params = {"VALUE": "TEXT"}
             
             # Remove any default parameters
-            for name, value in prop.params().items():
-                if value == [default_params.get(name),]:
-                    del prop.params()[name]
+            for name in prop.parameterNames():
+                value = prop.parameterValue(name)
+                if value == default_params.get(name):
+                    prop.removeParameter(name)
             
             # If there are no parameters, remove the property if it has the default value
-            if len(prop.params()) == 0:
-                if prop.value() == default_value:
+            if len(prop.parameterNames()) == 0:
+                if default_value is not None and prop.value() == default_value:
                     self.removeProperty(prop)
                     continue
 
@@ -2193,48 +2109,54 @@
             dtend = self.getProperty("DTEND")
             duration = self.getProperty("DURATION")
             
-            timeRange = timerange(
+            timeRange = PyCalendarPeriod(
                 start    = dtstart.value(),
                 end      = dtend.value()    if dtend is not None else None,
                 duration = duration.value() if duration is not None else None,
             )
 
-            dtstart.setValue(timeRange.start().asUTC().dateOrDatetime())
-            if "X-VOBJ-ORIGINAL-TZID" in dtstart.params():
-                dtstart.params()["ORIGINAL-TZID"] = dtstart.params()["X-VOBJ-ORIGINAL-TZID"]
-                del dtstart.params()["X-VOBJ-ORIGINAL-TZID"]
+            # Have to fake the TZID value here when we convert date-times to UTC
+            # as we need to know what the original one was
+            if dtstart.hasParameter("TZID"):
+                dtstart.setParameter("_TZID", dtstart.parameterValue("TZID"))
+                dtstart.removeParameter("TZID")
+            dtstart.value().adjustToUTC()
             if dtend is not None:
-                dtend.setValue(timeRange.end().asUTC().dateOrDatetime())
-                if "X-VOBJ-ORIGINAL-TZID" in dtend.params():
-                    dtend.params()["ORIGINAL-TZID"] = dtend.params()["X-VOBJ-ORIGINAL-TZID"]
-                    del dtend.params()["X-VOBJ-ORIGINAL-TZID"]
+                if dtend.hasParameter("TZID"):
+                    dtend.setParameter("_TZID", dtend.parameterValue("TZID"))
+                    dtend.removeParameter("TZID")
+                dtend.value().adjustToUTC()
             elif duration is not None:
                 self.removeProperty(duration)
-                self.addProperty(Property("DTEND", timeRange.end().asUTC().dateOrDatetime()))
+                self.addProperty(Property("DTEND", timeRange.getEnd().duplicateAsUTC()))
 
+            rdates = self.properties("RDATE")
+            for rdate in rdates:
+                if rdate.hasParameter("TZID"):
+                    rdate.setParameter("_TZID", rdate.parameterValue("TZID"))
+                    rdate.removeParameter("TZID")
+                for value in rdate.value():
+                    value.getValue().adjustToUTC()
+
             exdates = self.properties("EXDATE")
             for exdate in exdates:
-                exdate.setValue([asUTC(value) for value in exdate.value()])
-                try:
-                    del exdate.params()["TZID"]
-                except KeyError:
-                    pass
+                if exdate.hasParameter("TZID"):
+                    exdate.setParameter("_TZID", exdate.parameterValue("TZID"))
+                    exdate.removeParameter("TZID")
+                for value in exdate.value():
+                    value.getValue().adjustToUTC()
 
             rid = self.getProperty("RECURRENCE-ID")
             if rid is not None:
-                rid.setValue(asUTC(rid.value()))
-                try:
-                    del rid.params()["TZID"]
-                except KeyError:
-                    pass
+                rid.removeParameter("TZID")
+                rid.setValue(rid.value().duplicateAsUTC())
 
             # Recurrence rules - we need to normalize the order of the value parts
-            rrules = self.properties("RRULE")
-            for rrule in rrules:
-                indexedTokens = {}
-                indexedTokens.update([valuePart.split("=") for valuePart in rrule.value().split(";")])
-                sortedValue = ";".join(["%s=%s" % (key, value,) for key, value in sorted(indexedTokens.iteritems(), key=lambda x:x[0])])
-                rrule.setValue(sortedValue)
+#            for rrule in self._pycalendar.getRecurrenceSet().getRules():
+#                indexedTokens = {}
+#                indexedTokens.update([valuePart.split("=") for valuePart in rrule.value().split(";")])
+#                sortedValue = ";".join(["%s=%s" % (key, value,) for key, value in sorted(indexedTokens.iteritems(), key=lambda x:x[0])])
+#                rrule.setValue(sortedValue)
 
     def normalizePropertyValueLists(self, propname):
         """
@@ -2252,7 +2174,7 @@
                 if type(prop.value()) is list and len(prop.value()) > 1:
                     self.removeProperty(prop)
                     for value in prop.value():
-                        self.addProperty(Property(propname, [value,]))
+                        self.addProperty(Property(propname, [value.getValue(),]))
 
     def normalizeAttachments(self):
         """
@@ -2269,7 +2191,7 @@
             if dropboxPrefix is None:
                 return
             for attachment in tuple(self.properties("ATTACH")):
-                valueType = attachment.paramValue("VALUE")
+                valueType = attachment.parameterValue("VALUE")
                 if valueType in (None, "URI"):
                     dataValue = attachment.value()
                     if dataValue.find(dropboxPrefix) != -1:
@@ -2298,7 +2220,7 @@
                     continue
 
                 # Get any EMAIL parameter
-                oldemail = prop.params().get("EMAIL", (None,))[0]
+                oldemail = prop.parameterValue("EMAIL")
                 if oldemail:
                     oldemail = "mailto:%s" % (oldemail,)
 
@@ -2341,12 +2263,9 @@
 
                 # Always re-write the CN parameter
                 if name:
-                    prop.params()["CN"] = [name,]
+                    prop.setParameter("CN", name)
                 else:
-                    try:
-                        del prop.params()["CN"]
-                    except KeyError:
-                        pass
+                    prop.removeParameter("CN")
 
                 # Re-write the EMAIL if its value no longer matches
                 if oldemail and oldemail not in cuaddrs or oldemail is None and toUUID:
@@ -2361,12 +2280,9 @@
                             email = None
 
                     if email:
-                        prop.params()["EMAIL"] = [email,]
+                        prop.setParameter("EMAIL", email)
                     else:
-                        try:
-                            del prop.params()["EMAIL"]
-                        except KeyError:
-                            pass
+                        prop.removeParameter("EMAIL")
 
 
     def allPerUserUIDs(self):
@@ -2412,86 +2328,6 @@
         return tuple(results)
 
 ##
-# Dates and date-times
-##
-
-class FixedOffset (datetime.tzinfo):
-    """
-    Fixed offset in minutes east from UTC.
-    """
-    def __init__(self, offset, name=None):
-        self._offset = datetime.timedelta(minutes=offset)
-        self._name   = name
-
-    def utcoffset(self, dt): return self._offset
-    def tzname   (self, dt): return self._name
-    def dst      (self, dt): return datetime.timedelta(0)
-
-def parse_date(date_string):
-    """
-    Parse an iCalendar-format DATE string.  (RFC 2445, section 4.3.4)
-    @param date_string: an iCalendar-format DATE string.
-    @return: a L{datetime.date} object for the given C{date_string}.
-    """
-    try:
-        return stringToDate(date_string)
-    except (vParseError, InvalidICalendarDataError):
-        raise InvalidICalendarDataError("Invalid iCalendar DATE: %r" % (date_string,))
-
-def parse_time(time_string):
-    """
-    Parse iCalendar-format TIME string.  (RFC 2445, section 4.3.12)
-    @param time_string: an iCalendar-format TIME string.
-    @return: a L{datetime.time} object for the given C{time_string}.
-    """
-    try:
-        # Parse this as a fake date-time string by prepending date
-        with_date = "20000101T" + time_string
-        return stringToDateTime(with_date).time()
-    except (vParseError, InvalidICalendarDataError):
-        raise InvalidICalendarDataError("Invalid iCalendar TIME: %r" % (time_string,))
-
-def parse_datetime(datetime_string):
-    """
-    Parse iCalendar-format DATE-TIME string.  (RFC 2445, section 4.3.5)
-    @param datetime_string: an iCalendar-format DATE-TIME string.
-    @return: a L{datetime.datetime} object for the given C{datetime_string}.
-    """
-    try:
-        return stringToDateTime(datetime_string)
-    except (vParseError, InvalidICalendarDataError):
-        raise InvalidICalendarDataError("Invalid iCalendar DATE-TIME: %r" % (datetime_string,))
-
-def parse_date_or_datetime(date_string):
-    """
-    Parse iCalendar-format DATE or DATE-TIME string.  (RFC 2445, sections 4.3.4
-    and 4.3.5)
-    @param date_string: an iCalendar-format DATE or DATE-TIME string.
-    @return: a L{datetime.date} or L{datetime.datetime} object for the given
-        C{date_string}.
-    """
-    try:
-        if len(date_string) == 8:
-            return parse_date(date_string)
-        else:
-            return parse_datetime(date_string)
-    except InvalidICalendarDataError:
-        raise InvalidICalendarDataError("Invalid iCalendar DATE or DATE-TIME: %r" % (date_string,))
-
-def parse_duration(duration_string):
-    """
-    Parse iCalendar-format DURATION string.  (RFC 2445, sections 4.3.6)
-    @param duration_string: an iCalendar-format DURATION string.
-    @return: a L{datetime.timedelta} object for the given C{duration_string}.
-    """
-    try:
-        return stringToDurations(duration_string)[0]
-    except (vParseError, InvalidICalendarDataError):
-        raise InvalidICalendarDataError("Invalid iCalendar DURATION: %r" % (duration_string,))
-
-_regex_duration = None
-
-##
 # Timezones
 ##
 
@@ -2510,8 +2346,6 @@
     @return: a C{list} of tuples of (C{datetime}, C{str})
     """
     
-    start = datetime.datetime.fromordinal(start.toordinal())
-    end = datetime.datetime.fromordinal(end.toordinal())
     icalobj = Component.fromString(tzdata)
     tzcomp = None
     for comp in icalobj.subcomponents():
@@ -2521,52 +2355,23 @@
     else:
         raise InvalidICalendarDataError("No VTIMEZONE component in %s" % (tzdata,))
 
-    tzinfo = tzcomp.gettzinfo()
+    tzexpanded = tzcomp._pycalendar.expandAll(start, end)
     
     results = []
     
-    # Get the start utc-offset - that is our first value
-    results.append((dateTimeToString(start), deltaToOffset(tzinfo.utcoffset(start)),))
-    last_dt = start
+    # Always need to ensure the start appears in the result
+    start.setDateOnly(False)
+    if tzexpanded:
+        if start != tzexpanded[0][0]:
+            results.append((str(start), PyCalendarUTCOffsetValue(tzexpanded[0][1]).getText(),))
+    else:
+        results.append((str(start), PyCalendarUTCOffsetValue(tzcomp._pycalendar.getTimezoneOffsetSeconds(start)).getText(),))
+    for tzstart, _ignore_tzoffsetfrom, tzoffsetto in tzexpanded:
+        results.append((
+            tzstart.getText(),
+            PyCalendarUTCOffsetValue(tzoffsetto).getText(),
+        ))
     
-    while last_dt < end:
-        # Get the transitions for the current year
-        standard = getTransition("standard", last_dt.year, tzinfo)
-        daylight = getTransition("daylight", last_dt.year, tzinfo)
-        
-        # Order the transitions
-        if standard and daylight:
-            if standard < daylight:
-                first = standard
-                second = daylight
-            else:
-                first = daylight
-                second = standard
-        elif standard:
-            first = standard
-            second = None
-        else:
-            first = daylight
-            second = None
-        
-        for transition in (first, second):
-            # Terminate if the next transition is outside the time range
-            if transition and transition > end:
-                break
-            
-            # If the next transition is after the last one, then add its info if
-            # the utc-offset actually changed.
-            if transition and transition > last_dt:
-                utcoffset = deltaToOffset(tzinfo.utcoffset(transition + datetime.timedelta(days=1)))
-                if utcoffset != results[-1][1]:
-                    results.append((dateTimeToString(transition), utcoffset,))
-                last_dt = transition
-            
-        # Bump last transition up to the start of the next year
-        last_dt = datetime.datetime(last_dt.year + 1, 1, 1, 0, 0, 0)
-        if last_dt >= end:
-            break
-    
     return results
 
 ##

Modified: CalendarServer/trunk/twistedcaldav/instance.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/instance.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/instance.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -18,14 +18,13 @@
 iCalendar Recurrence Expansion Utilities
 """
 
-import datetime
+from twistedcaldav.dateops import normalizeForIndex, differenceDateTime
 
-from vobject.icalendar import utc
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.duration import PyCalendarDuration
+from pycalendar.period import PyCalendarPeriod
+from pycalendar.timezone import PyCalendarTimezone
 
-from twext.python.datetime import dateordatetime
-
-from twistedcaldav.dateops import normalizeForIndex, differenceDateTime, periodEnd
-
 # The maximum number of instances we will expand out to.
 # Raise a TooManyInstancesError exception if we exceed this.
 max_allowed_instances = 1000
@@ -48,22 +47,11 @@
 
 class Instance(object):
     
-    __slots__ = ["component", "start", "end", "rid", "overridden", "future"]
-    
     def __init__(self, component, start = None, end = None, rid = None, overridden = False, future = False):
         self.component = component
-        if start is None:
-            self.start = component.getStartDateUTC()
-        else:
-            self.start = start
-        if end is None:
-            self.end = component.getEndDateUTC()
-        else:
-            self.end = end
-        if rid is None:
-            self.rid = self.start
-        else:
-            self.rid = rid
+        self.start = component.getStartDateUTC() if start is None else start
+        self.end = component.getEndDateUTC() if end is None else end
+        self.rid = self.start if rid is None else rid
         self.overridden = overridden
         self.future = future
         
@@ -78,28 +66,25 @@
             (trigger, related, repeat, duration)  = alarm.getTriggerDetails()
             
             # Handle relative vs absolute triggers
-            if isinstance(trigger, datetime.date):
+            if isinstance(trigger, PyCalendarDateTime):
                 # Absolute trigger
                 start = trigger
             else:
                 # Relative trigger
-                if related:
-                    start = self.start + trigger
-                else:
-                    start = self.end + trigger
+                start = (self.start if related else self.end) + trigger
             triggers.add(start)
             
             # Handle repeats
             if repeat > 0:
-                for i in xrange(1, repeat+1):
-                    triggers.add(start + (duration * i))
+                tstart = start.duplicate()
+                for _ignore in xrange(1, repeat+1):
+                    tstart += duration
+                    triggers.add(tstart)
         
         return triggers
     
 class InstanceList(object):
     
-    __slots__ = ["instances", "limit", "ignoreInvalidInstances",]
-    
     def __init__(self, ignoreInvalidInstances=False):
         self.instances = {}
         self.limit = None
@@ -122,7 +107,7 @@
         @param componentSet: the set of components that are to make up the
                 recurrence set. These MUST all be components with the same UID
                 and type, forming a proper recurring set.
-        @param limit: datetime.date value representing the end of the expansion.
+        @param limit: L{PyCalendarDateTime} value representing the end of the expansion.
         """
         
         # Look at each component type
@@ -185,26 +170,27 @@
         Add the specified master VEVENT Component to the instance list, expanding it
         within the supplied time range.
         @param component: the Component to expand
-        @param limit: the end datetime.datetime for expansion
+        @param limit: the end L{PyCalendarDateTime} for expansion
         """
         start = component.getStartDateUTC()
         if start is None:
             return
+        rulestart = component.propertyValue("DTSTART")
 
         end = component.getEndDateUTC()
         duration = None
         if end is None:
-            if isinstance(start, datetime.datetime):
+            if not start.isDateOnly():
                 # Timed event with zero duration
-                duration = datetime.timedelta(days=0)
+                duration = PyCalendarDuration(days=0)
             else:
                 # All day event default duration is one day
-                duration = datetime.timedelta(days=1)
+                duration = PyCalendarDuration(days=1)
             end = start + duration
         else:
             duration = differenceDateTime(start, end)
 
-        self._addMasterComponent(component, limit, start, end, duration)
+        self._addMasterComponent(component, limit, rulestart, start, end, duration)
 
     def _addOverrideEventComponent(self, component, limit, got_master):
         """
@@ -223,15 +209,13 @@
         end = component.getEndDateUTC()
         duration = None
         if end is None:
-            if isinstance(start, datetime.datetime):
+            if not start.isDateOnly():
                 # Timed event with zero duration
-                duration = datetime.timedelta(days=0)
+                duration = PyCalendarDuration(days=0)
             else:
                 # All day event default duration is one day
-                duration = datetime.timedelta(days=1)
+                duration = PyCalendarDuration(days=1)
             end = start + duration
-        else:
-            duration = differenceDateTime(start, end)
 
         self._addOverrideComponent(component, limit, start, end, got_master)
 
@@ -240,7 +224,7 @@
         Add the specified master VTODO Component to the instance list, expanding it
         within the supplied time range.
         @param component: the Component to expand
-        @param limit: the end datetime.datetime for expansion
+        @param limit: the end L{PyCalendarDateTime} for expansion
         """
         start = component.getStartDateUTC()
         due = component.getDueDateUTC()
@@ -248,13 +232,15 @@
         if start is None and due is None:
             return
 
+        rulestart = component.propertyValue("DTSTART")
         if start is None:
             start = due
+            rulestart = component.propertyValue("DUE")
         elif due is None:
             due = start
         duration = differenceDateTime(start, due)
 
-        self._addMasterComponent(component, limit, start, due, duration)
+        self._addMasterComponent(component, limit, rulestart, start, due, duration)
 
     def _addOverrideToDoComponent(self, component, limit, got_master):
         """
@@ -279,35 +265,27 @@
 
         self._addOverrideComponent(component, limit, start, due, got_master)
 
-    def _addMasterComponent(self, component, limit, start, end, duration):
-        # Always add first instance if included in range.
-        if dateordatetime(start) < limit:
-            # dateutils does not do all-day - so convert to datetime.datetime
-            start = normalizeForIndex(start)
-            end = normalizeForIndex(end)
-            
-            # Do not add if in EXDATEs
-            exdates = []
-            for prop in component.properties("EXDATE"):
-                exdates.extend(prop.value())
-            exdates = [normalizeForIndex(exdate) for exdate in exdates]
-            if start not in exdates:
-                self.addInstance(Instance(component, start, end))
-        else:
-            self.limit = limit
+    def _addMasterComponent(self, component, limit, rulestart, start, end, duration):
         
-        # Now expand recurrence
-        # FIXME: Current Python implementation fails when RDATEs are PERIODs
-        recur = component.getRRuleSet(True)
-        if recur is not None:
-            for startDate in recur:
-                if dateordatetime(startDate) >= limit:
-                    self.limit = limit
-                    break
-                endDate = startDate + duration
+        rrules = component.getRecurrenceSet()
+        if rrules is not None:
+            # Do recurrence set expansion
+            expanded = []
+            limited = rrules.expand(rulestart, PyCalendarPeriod(start, limit), expanded)
+            for startDate in expanded:
                 startDate = normalizeForIndex(startDate)
-                endDate = normalizeForIndex(endDate)
+                endDate = startDate + duration
                 self.addInstance(Instance(component, startDate, endDate))
+            if limited:
+                self.limit = limit
+        else:
+            # Always add main instance if included in range.
+            if start < limit:
+                start = normalizeForIndex(start)
+                end = normalizeForIndex(end)
+                self.addInstance(Instance(component, start, end))
+            else:
+                self.limit = limit
     
     def _addOverrideComponent(self, component, limit, start, end, got_master):
 
@@ -321,12 +299,12 @@
         rid = normalizeForIndex(rid)
 
         # Make sure start is within the limit
-        if dateordatetime(start) > limit and dateordatetime(rid) > limit:
+        if start > limit and rid > limit:
             return
 
         # Make sure override RECURRENCE-ID is a valid instance of the master
         if got_master:
-            if str(rid) not in self.instances and dateordatetime(rid) < limit:
+            if str(rid) not in self.instances and rid < limit:
                 if self.ignoreInvalidInstances:
                     return
                 else:
@@ -371,11 +349,11 @@
         Add the specified master VFREEBUSY Component to the instance list, expanding it
         within the supplied time range.
         @param component: the Component to expand
-        @param limit: the end datetime.datetime for expansion
+        @param limit: the end L{PyCalendarDateTime} for expansion
         """
 
         start = component.getStartDateUTC()
-        if start is not None and dateordatetime(start) >= limit:
+        if start is not None and start >= limit:
             # If the free busy is beyond the end of the range we want, ignore it
             return
 
@@ -389,10 +367,11 @@
             assert isinstance(fb.value(), list), "FREEBUSY property does not contain a list of values: %r" % (fb,)
             for period in fb.value():
                 # Ignore if period starts after limit
-                if dateordatetime(period[0]) >= limit:
+                period = period.getValue()
+                if period.getStart() >= limit:
                     continue
-                start = normalizeForIndex(period[0])
-                end = normalizeForIndex(periodEnd(period))
+                start = normalizeForIndex(period.getStart())
+                end = normalizeForIndex(period.getEnd())
                 self.addInstance(Instance(component, start, end))
 
     def _addAvailabilityComponent(self, component, limit):
@@ -403,20 +382,20 @@
         depending on the presence of the properties. If unbounded at one or both ends, we will
         set the time to 1/1/1900 in the past and 1/1/3000 in the future.
         @param component: the Component to expand
-        @param limit: the end datetime.datetime for expansion
+        @param limit: the end L{PyCalendarDateTime} for expansion
         """
 
         start = component.getStartDateUTC()
-        if start is not None and dateordatetime(start) >= limit:
+        if start is not None and start >= limit:
             # If the free busy is beyond the end of the range we want, ignore it
             return
         if start is None:
-            start = datetime.datetime(1900, 1, 1, 0, 0, 0, tzinfo=utc)
+            start = PyCalendarDateTime(1900, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
         start = normalizeForIndex(start)
 
         end = component.getEndDateUTC()
         if end is None:
-            end = datetime.datetime(3000, 1, 1, 0, 0, 0, tzinfo=utc)
+            end = PyCalendarDateTime(2100, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
         end = normalizeForIndex(end)
 
         self.addInstance(Instance(component, start, end))

Modified: CalendarServer/trunk/twistedcaldav/localization.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/localization.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/localization.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -24,6 +24,7 @@
 from locale import normalize
 
 from twext.python.log import Logger
+from pycalendar.duration import PyCalendarDuration
 
 try:
     from Foundation import (
@@ -90,12 +91,12 @@
 helper methods for date formatting:
 
     with translationTo('en') as trans:
-        print trans.dtDate(datetime.today())
+        print trans.dtDate(PyCalendarDateTime.getToday())
 
     ... Thursday, October 23, 2008
 
     with translationTo('fr') as trans:
-        print trans.dtDate(datetime.today())
+        print trans.dtDate(PyCalendarDateTime.getToday())
 
     ... Jeudi, Octobre 23, 2008
 
@@ -151,7 +152,7 @@
         return self.translation.ugettext(monthsAbbrev[monthNumber])
 
     def date(self, component):
-        dtStart = component.propertyNativeValue("DTSTART")
+        dtStart = component.propertyValue("DTSTART")
         return self.dtDate(dtStart)
 
     def time(self, component):
@@ -173,32 +174,29 @@
         _ = self.translation.ugettext
 
         tzStart = tzEnd = None
-        dtStart = component.propertyNativeValue("DTSTART")
-        if isinstance(dtStart, datetime.datetime):
-            tzStart = dtStart.tzname()
+        dtStart = component.propertyValue("DTSTART")
+        if dtStart.isDateOnly():
+            return ("", _("All day"))
         else:
-            return ("", _("All day"))
+            tzStart = dtStart.timeZoneDescriptor()
 
-        # tzStart = component.getProperty("DTSTART").params().get("TZID", "UTC")
-
-        dtEnd = component.propertyNativeValue("DTEND")
+        dtEnd = component.propertyValue("DTEND")
         if dtEnd:
-            if isinstance(dtEnd, datetime.datetime):
-                tzEnd = dtEnd.tzname()
-            # tzEnd = component.getProperty("DTEND").params().get("TZID", "UTC")
+            if not dtEnd.isDateOnly():
+                tzEnd = dtEnd.timeZoneDescriptor()
             duration = dtEnd - dtStart
         else:
             tzEnd = tzStart
-            duration = component.propertyNativeValue("DURATION")
+            duration = component.propertyValue("DURATION")
             if duration:
                 dtEnd = dtStart + duration
             else:
-                if isinstance(dtStart, datetime.date):
+                if dtStart.isDateOnly():
                     dtEnd = None
-                    duration = datetime.timedelta(days=1)
+                    duration = PyCalendarDuration(days=1)
                 else:
-                    dtEnd = dtStart + datetime.timedelta(days=1)
-                    dtEnd.hour = dtEnd.minute = dtEnd.second = 0
+                    dtEnd = dtStart + PyCalendarDuration(days=1)
+                    dtEnd.setHHMMSS(0, 0, 0)
                     duration = dtEnd - dtStart
 
         if dtStart == dtEnd:
@@ -222,37 +220,37 @@
         return (
             _("%(dayName)s, %(monthName)s %(dayNumber)d, %(yearNumber)d")
             % {
-                'dayName'    : _(daysFull[val.weekday()]),
-                'monthName'  : _(monthsFull[val.month]),
-                'dayNumber'  : val.day,
-                'yearNumber' : val.year,
+                'dayName'    : _(daysFull[(val.getDayOfWeek() + 6) % 7]),
+                'monthName'  : _(monthsFull[val.getMonth()]),
+                'dayNumber'  : val.getDay(),
+                'yearNumber' : val.getYear(),
             }
         )
 
     def dtTime(self, val, includeTimezone=True):
-        if not isinstance(val, (datetime.datetime, datetime.time)):
+        if val.isDateOnly():
             return ""
 
         # Bind to '_' so pygettext.py will pick this up for translation
         _ = self.translation.ugettext
 
-        ampm = _("AM") if val.hour < 12 else _("PM")
-        hour12 = val.hour % 12
+        ampm = _("AM") if val.getHours() < 12 else _("PM")
+        hour12 = val.getHours() % 12
         if hour12 == 0:
             hour12 = 12
 
         result = (
             _("%(hour12Number)d:%(minuteNumber)02d %(ampm)s")
             % {
-                'hour24Number' : val.hour, # 0-23
+                'hour24Number' : val.getHours(), # 0-23
                 'hour12Number' : hour12, # 1-12
-                'minuteNumber' : val.minute, # 0-59
+                'minuteNumber' : val.getMinutes(), # 0-59
                 'ampm'         : _(ampm),
             }
         )
 
-        if includeTimezone and val.tzname():
-            result += " %s" % (val.tzname())
+        if includeTimezone and val.local():
+            result += " %s" % (val.timeZoneDescriptor(),)
 
         return result
 
@@ -263,15 +261,17 @@
 
         parts = []
 
-        if val.days == 1:
+        total = val.getTotalSeconds()
+        days = total / (24 * 60 * 60)
+        if days == 1:
             parts.append(_("1 day"))
-        elif val.days > 1:
+        elif days > 1:
             parts.append(_("%(dayCount)d days" %
-                { 'dayCount' : val.days }))
+                { 'dayCount' : days }))
 
-        hours = val.seconds / 3600
-        minutes = divmod(val.seconds / 60, 60)[1]
-        seconds = divmod(val.seconds, 60)[1]
+        hours = divmod(total / 3600, 24)[1]
+        minutes = divmod(total / 60, 60)[1]
+        seconds = divmod(total, 60)[1]
 
         if hours == 1:
             parts.append(_("1 hour"))
@@ -320,7 +320,7 @@
 ]
 
 monthsFull = [
-    "datetime.month is 1-based",
+    "month is 1-based",
     _("January"),
     _("February"),
     _("March"),
@@ -336,7 +336,7 @@
 ]
 
 monthsAbbrev = [
-    "datetime.month is 1-based",
+    "month is 1-based",
     _("JAN"),
     _("FEB"),
     _("MAR"),

Modified: CalendarServer/trunk/twistedcaldav/mail.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/mail.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/mail.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -781,7 +781,7 @@
             # The organizer will then see that the reply was not successful.
             attendeeProp = Property("ATTENDEE", attendee,
                 params = {
-                    "SCHEDULE-STATUS": [iTIPRequestStatus.SERVICE_UNAVAILABLE],
+                    "SCHEDULE-STATUS": iTIPRequestStatus.SERVICE_UNAVAILABLE,
                 }
             )
             event.addProperty(attendeeProp)
@@ -833,17 +833,16 @@
         # readable email message (not modifying the calendar body)
         attendees = []
         for attendeeProp in calendar.getAllAttendeeProperties():
-            params = attendeeProp.params()
-            cutype = params.get('CUTYPE', (None,))[0]
+            cutype = attendeeProp.parameterValue('CUTYPE', None)
             if cutype == "INDIVIDUAL":
-                cn = params.get("CN", (None,))[0]
+                cn = attendeeProp.parameterValue("CN", None)
                 cuaddr = normalizeCUAddr(attendeeProp.value())
                 if cuaddr.startswith("mailto:"):
                     mailto = cuaddr[7:]
                     if not cn:
                         cn = mailto
                 else:
-                    emailAddress = params.get("EMAIL", (None,))[0]
+                    emailAddress = attendeeProp.parameterValue("EMAIL", None)
                     if emailAddress:
                         mailto = emailAddress
                     else:
@@ -898,7 +897,7 @@
             else:
                 fromAddr = serverAddress
                 orgEmail = None
-            cn = calendar.getOrganizerProperty().params().get('CN', (None,))[0]
+            cn = calendar.getOrganizerProperty().parameterValue('CN', None)
             if cn is None:
                 cn = 'Calendar Server'
                 orgCN = orgEmail
@@ -920,19 +919,18 @@
                 raise ValueError("ORGANIZER address '%s' must be mailto: for REPLY." % (organizerMailto,))
             orgEmail = organizerMailto[7:]
 
-            orgCN = calendar.getOrganizerProperty().params().get('CN', (None,))[0]
+            orgCN = calendar.getOrganizerProperty().parameterValue('CN', None)
             addressWithToken = formattedFrom
 
 
         # Now prevent any "internal" CUAs from being exposed by converting
         # to mailto: if we have one
         for attendeeProp in calendar.getAllAttendeeProperties():
-            params = attendeeProp.params()
-            cutype = params.get('CUTYPE', (None,))[0]
+            cutype = attendeeProp.parameterValue('CUTYPE', None)
             if cutype == "INDIVIDUAL":
                 cuaddr = normalizeCUAddr(attendeeProp.value())
                 if not cuaddr.startswith("mailto:"):
-                    emailAddress = params.get("EMAIL", (None,))[0]
+                    emailAddress = attendeeProp.parameterValue("EMAIL", None)
                     if emailAddress:
                         attendeeProp.setValue("mailto:%s" % (emailAddress,))
 
@@ -1221,9 +1219,9 @@
 
         results = { }
 
-        dtStart = component.propertyNativeValue("DTSTART")
-        results['month'] = dtStart.month
-        results['day'] = dtStart.day
+        dtStart = component.propertyValue("DTSTART")
+        results['month'] = dtStart.getMonth()
+        results['day'] = dtStart.getDay()
 
         summary = component.propertyValue("SUMMARY")
         if summary is None:

Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -628,7 +628,7 @@
             )
             attachments = [
                 attachment for attachment in attachments
-                if attachment.params().get("VALUE", ("TEXT",))[0] == "URI" and attachment.value().startswith("http")
+                if attachment.paramsValue("VALUE", "TEXT") == "URI" and attachment.value().startswith("http")
             ]
 
             if len(xdropboxes) or len(attachments):

Modified: CalendarServer/trunk/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_common.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/method/report_common.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -29,7 +29,6 @@
     "buildFreeBusyResult",
 ]
 
-import datetime
 import time
 
 try:
@@ -37,8 +36,6 @@
 except ImportError:
     from md5 import new as md5
 
-from vobject.icalendar import utc, dateTimeToString
-
 from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred
 from twisted.python.failure import Failure
 from twext.web2 import responsecode
@@ -62,7 +59,7 @@
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
 from twistedcaldav.datafilters.addressdata import AddressDataFilter
 from twistedcaldav.dateops import clipPeriod, normalizePeriodList, timeRangesOverlap,\
-    compareDateTime, normalizeToUTC
+    compareDateTime, normalizeToUTC, parseSQLTimestampToPyCalendar
 from twistedcaldav.ical import Component, Property, iCalendarProductID
 from twistedcaldav.instance import InstanceList
 from twistedcaldav.memcacher import Memcacher
@@ -71,6 +68,11 @@
 
 from txdav.common.icommondatastore import IndexedSearchException
 
+from pycalendar.duration import PyCalendarDuration
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.timezone import PyCalendarTimezone
+from pycalendar.period import PyCalendarPeriod
+
 log = Logger()
 
 COLLECTION_TYPE_REGULAR     = "collection"
@@ -403,8 +405,8 @@
         if entry:
     
             # Offset one day at either end to account for floating
-            cached_start = entry.timerange.start + datetime.timedelta(days=FBCacheEntry.CACHE_DAYS_FLOATING_ADJUST)
-            cached_end = entry.timerange.end - datetime.timedelta(days=FBCacheEntry.CACHE_DAYS_FLOATING_ADJUST)
+            cached_start = entry.timerange.start + PyCalendarDuration(days=FBCacheEntry.CACHE_DAYS_FLOATING_ADJUST)
+            cached_end = entry.timerange.end - PyCalendarDuration(days=FBCacheEntry.CACHE_DAYS_FLOATING_ADJUST)
 
             # Verify that the requested timerange lies within the cache timerange
             if compareDateTime(timerange.end, cached_end) <= 0 and compareDateTime(timerange.start, cached_start) >= 0:
@@ -481,12 +483,12 @@
             request.extendedLogItems["fb-uncached"] = request.extendedLogItems.get("fb-uncached", 0) + 1
 
             # We want to cache a large range of time based on the current date
-            cache_start = normalizeToUTC(datetime.date.today() - datetime.timedelta(days=config.FreeBusyCacheDaysBack))
-            cache_end = normalizeToUTC(datetime.date.today() + datetime.timedelta(days=config.FreeBusyCacheDaysForward))
+            cache_start = normalizeToUTC(PyCalendarDateTime.getToday() + PyCalendarDuration(days=-config.FreeBusyCacheDaysBack))
+            cache_end = normalizeToUTC(PyCalendarDateTime.getToday() + PyCalendarDuration(days=config.FreeBusyCacheDaysForward))
             
             # If the requested timerange would fit in our allowed cache range, trigger the cache creation
             if compareDateTime(timerange.start, cache_start) >= 0 and compareDateTime(timerange.end, cache_end) <= 0:
-                cache_timerange = TimeRange(start=dateTimeToString(cache_start), end=dateTimeToString(cache_end))
+                cache_timerange = TimeRange(start=cache_start.getText(), end=cache_end.getText())
                 caching = True
  
         #
@@ -568,19 +570,19 @@
                             continue
                         
                 # Apply a timezone to any floating times
-                fbstart = datetime.datetime.strptime(start[:19], "%Y-%m-%d %H:%M:%S")
+                fbstart = parseSQLTimestampToPyCalendar(start)
                 if float == 'Y':
-                    fbstart = fbstart.replace(tzinfo=tzinfo)
+                    fbstart.setTimezone(tzinfo)
                 else:
-                    fbstart = fbstart.replace(tzinfo=utc)
-                fbend =datetime.datetime.strptime(end[:19], "%Y-%m-%d %H:%M:%S")
+                    fbstart.setTimezone(PyCalendarTimezone(utc=True))
+                fbend = parseSQLTimestampToPyCalendar(end)
                 if float == 'Y':
-                    fbend = fbend.replace(tzinfo=tzinfo)
+                    fbend.setTimezone(tzinfo)
                 else:
-                    fbend = fbend.replace(tzinfo=utc)
+                    fbend.setTimezone(PyCalendarTimezone(utc=True))
                 
-                # Click instance to time range
-                clipped = clipPeriod((fbstart, fbend - fbstart), (timerange.start, timerange.end))
+                # Clip instance to time range
+                clipped = clipPeriod(PyCalendarPeriod(fbstart, duration=fbend-fbstart), PyCalendarPeriod(timerange.start, timerange.end))
 
                 # Double check for overlap
                 if clipped:
@@ -642,7 +644,7 @@
     @param calendar: the L{Component} that is the VCALENDAR containing the VEVENT's.
     @param fbinfo: the tuple used to store the three types of fb data.
     @param timerange: the time range to restrict free busy data to.
-    @param tzinfo: the L{datetime.tzinfo} for the timezone to use for floating/all-day events.
+    @param tzinfo: the L{PyCalendarTimezone} for the timezone to use for floating/all-day events.
     """
     
     # Expand out the set of instances for the event with in the required range
@@ -651,7 +653,7 @@
     # Can only do timed events
     for key in instances:
         instance = instances[key]
-        if not isinstance(instance.start, datetime.datetime):
+        if instance.start.isDateOnly():
             return
         break
     else:
@@ -662,11 +664,11 @@
 
         # Apply a timezone to any floating times
         fbstart = instance.start
-        if fbstart.tzinfo is None:
-            fbstart = fbstart.replace(tzinfo=tzinfo)
+        if fbstart.floating():
+            fbstart.setTimezone(tzinfo)
         fbend = instance.end
-        if fbend.tzinfo is None:
-            fbend = fbend.replace(tzinfo=tzinfo)
+        if fbend.floating():
+            fbend.setTimezone(tzinfo)
         
         # Check TRANSP property of underlying component
         if instance.component.hasProperty("TRANSP"):
@@ -687,10 +689,10 @@
         # Clip period for this instance - use duration for period end if that
         # is what original component used
         if instance.component.hasProperty("DURATION"):
-            period = (fbstart, fbend - fbstart)
+            period = PyCalendarPeriod(fbstart, duration=fbend-fbstart)
         else:
-            period = (fbstart, fbend)
-        clipped = clipPeriod(period, (timerange.start, timerange.end))
+            period = PyCalendarPeriod(fbstart, fbend)
+        clipped = clipPeriod(period, PyCalendarPeriod(timerange.start, timerange.end))
         
         # Double check for overlap
         if clipped:
@@ -706,7 +708,7 @@
     @param fbinfo: the tuple used to store the three types of fb data.
     @param timerange: the time range to restrict free busy data to.
     """
-    
+
     for vfb in [x for x in calendar.subcomponents() if x.name() == "VFREEBUSY"]:
         # First check any start/end in the actual component
         start = vfb.getStartDateUTC()
@@ -718,10 +720,7 @@
         # Now look at each FREEBUSY property
         for fb in vfb.properties("FREEBUSY"):
             # Check the type
-            if "FBTYPE" in fb.params():
-                fbtype = fb.params()["FBTYPE"][0]
-            else:
-                fbtype = "BUSY"
+            fbtype = fb.parameterValue("FBTYPE", default="BUSY")
             if fbtype == "FREE":
                 continue
             
@@ -729,7 +728,7 @@
             assert isinstance(fb.value(), list), "FREEBUSY property does not contain a list of values: %r" % (fb,)
             for period in fb.value():
                 # Clip period for this instance
-                clipped = clipPeriod(period, (timerange.start, timerange.end))
+                clipped = clipPeriod(period.getValue(), PyCalendarPeriod(timerange.start, timerange.end))
                 if clipped:
                     fbinfo[fbtype_mapper.get(fbtype, 0)].append(clipped)
 
@@ -746,12 +745,12 @@
         # Get overall start/end
         start = vav.getStartDateUTC()
         if start is None:
-            start = datetime.datetime(1900, 1, 1, 0, 0, 0, tzinfo=utc)
+            start = PyCalendarDateTime(1900, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
         end = vav.getEndDateUTC()
         if end is None:
-            end = datetime.datetime(3000, 1, 1, 0, 0, 0, tzinfo=utc)
-        period = (start, end)
-        overall = clipPeriod(period, (timerange.start, timerange.end))
+            end = PyCalendarDateTime(2100, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
+        period = PyCalendarPeriod(start, end)
+        overall = clipPeriod(period, PyCalendarPeriod(timerange.start, timerange.end))
         if overall is None:
             continue
         
@@ -762,11 +761,11 @@
         busyperiods = []
         last_end = timerange.start
         for period in periods:
-            if last_end < period[0]:
-                busyperiods.append((last_end, period[0]))
-            last_end = period[1]
+            if last_end < period.getStart():
+                busyperiods.append(PyCalendarPeriod(last_end, period.getStart()))
+            last_end = period.getEnd()
         if last_end < timerange.end:
-            busyperiods.append((last_end, timerange.end))
+            busyperiods.append(PyCalendarPeriod(last_end, timerange.end))
 
         # Add to actual results mapped by busy type
         fbtype = vav.propertyValue("BUSYTYPE")
@@ -803,19 +802,19 @@
             # Ignore any with floating times (which should not happen as the spec requires UTC or local
             # but we will try and be safe here).
             start = instance.start
-            if start.tzinfo is None:
+            if start.floating():
                 continue
             end = instance.end
-            if end.tzinfo is None:
+            if end.floating():
                 continue
 
             # Clip period for this instance - use duration for period end if that
             # is what original component used
             if instance.component.hasProperty("DURATION"):
-                period = (start, end - start)
+                period = PyCalendarPeriod(start, duration=end-start)
             else:
-                period = (start, end)
-            clipped = clipPeriod(period, (timerange.start, timerange.end))
+                period = PyCalendarPeriod(start, end)
+            clipped = clipPeriod(period, PyCalendarPeriod(timerange.start, timerange.end))
             if clipped:
                 periods.append(clipped)
             
@@ -842,6 +841,7 @@
     
     # Now build a new calendar object with the free busy info we have
     fbcalendar = Component("VCALENDAR")
+    fbcalendar.addProperty(Property("VERSION", "2.0"))
     fbcalendar.addProperty(Property("PRODID", iCalendarProductID))
     if method:
         fbcalendar.addProperty(Property("METHOD", method))
@@ -853,13 +853,13 @@
         fb.addProperty(attendee)
     fb.addProperty(Property("DTSTART", timerange.start))
     fb.addProperty(Property("DTEND", timerange.end))
-    fb.addProperty(Property("DTSTAMP", datetime.datetime.now(tz=utc)))
+    fb.addProperty(Property("DTSTAMP", PyCalendarDateTime.getNowUTC()))
     if len(fbinfo[0]) != 0:
-        fb.addProperty(Property("FREEBUSY", fbinfo[0], {"FBTYPE": ["BUSY"]}))
+        fb.addProperty(Property("FREEBUSY", fbinfo[0], {"FBTYPE": "BUSY"}))
     if len(fbinfo[1]) != 0:
-        fb.addProperty(Property("FREEBUSY", fbinfo[1], {"FBTYPE": ["BUSY-TENTATIVE"]}))
+        fb.addProperty(Property("FREEBUSY", fbinfo[1], {"FBTYPE": "BUSY-TENTATIVE"}))
     if len(fbinfo[2]) != 0:
-        fb.addProperty(Property("FREEBUSY", fbinfo[2], {"FBTYPE": ["BUSY-UNAVAILABLE"]}))
+        fb.addProperty(Property("FREEBUSY", fbinfo[2], {"FBTYPE": "BUSY-UNAVAILABLE"}))
     if uid is not None:
         fb.addProperty(Property("UID", uid))
     else:

Modified: CalendarServer/trunk/twistedcaldav/query/calendarquery.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/calendarquery.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/query/calendarquery.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -26,7 +26,7 @@
     "sqlcalendarquery",
 ]
 
-from twistedcaldav.dateops import floatoffset
+from twistedcaldav.dateops import floatoffset, pyCalendarTodatetime
 from twistedcaldav.query import expression, sqlgenerator, calendarqueryfilter
 
 # SQL Index column (field) names
@@ -197,10 +197,10 @@
     endfloat = floatoffset(end, tzinfo) if end else None
 
     return (
-        (start) if start else None,
-        (end) if end else None,
-        (startfloat) if startfloat else None,
-        (endfloat) if endfloat else None,
+        pyCalendarTodatetime(start) if start else None,
+        pyCalendarTodatetime(end) if end else None,
+        pyCalendarTodatetime(startfloat) if startfloat else None,
+        pyCalendarTodatetime(endfloat) if endfloat else None,
     )
 
 def sqlcalendarquery(filter, calendarid=None, userid=None, generator=sqlgenerator.sqlgenerator):

Modified: CalendarServer/trunk/twistedcaldav/query/calendarqueryfilter.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/calendarqueryfilter.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/query/calendarqueryfilter.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -26,10 +26,11 @@
 
 from twistedcaldav.caldavxml import caldav_namespace, CalDAVTimeZoneElement
 from twistedcaldav.dateops import timeRangesOverlap
-from twistedcaldav.ical import Component, Property, parse_date_or_datetime
-from vobject.icalendar import utc
-import datetime
+from twistedcaldav.ical import Component, Property
 
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.timezone import PyCalendarTimezone
+
 log = Logger()
 
 class FilterBase(object):
@@ -82,7 +83,7 @@
                     instances = None
                 else:
                     # Expand the instances up to infinity
-                    instances = component.expandTimeRanges(datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc), ignoreInvalidInstances=True)
+                    instances = component.expandTimeRanges(PyCalendarDateTime(2100, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), ignoreInvalidInstances=True)
             else:
                 instances = component.expandTimeRanges(maxend, ignoreInvalidInstances=True)
         else:
@@ -108,7 +109,7 @@
         Set the default timezone to use with this query.
         @param calendar: a L{Component} for the VCALENDAR containing the one
             VTIMEZONE that we want
-        @return: the L{datetime.tzinfo} derived from the VTIMEZONE or utc.
+        @return: the L{PyCalendarTimezone} derived from the VTIMEZONE or utc.
         """
         assert tzelement is None or isinstance(tzelement, CalDAVTimeZoneElement)
 
@@ -118,11 +119,12 @@
                 for subcomponent in calendar.subcomponents():
                     if subcomponent.name() == "VTIMEZONE":
                         # <filter> contains exactly one <comp-filter>
-                        tzinfo = subcomponent.gettzinfo()
-                        self.child.settzinfo(tzinfo)
-                        return tzinfo
+                        tz = subcomponent.gettimezone()
+                        self.child.settzinfo(tz)
+                        return tz
 
         # Default to using utc tzinfo
+        utc = PyCalendarTimezone(utc=True)
         self.child.settzinfo(utc)
         return utc
 
@@ -319,7 +321,7 @@
     def settzinfo(self, tzinfo):
         """
         Set the default timezone to use with this query.
-        @param tzinfo: a L{datetime.tzinfo} to use.
+        @param tzinfo: a L{PyCalendarTimezone} to use.
         """
         
         # Give tzinfo to any TimeRange we have
@@ -332,10 +334,10 @@
 
     def getmaxtimerange(self, currentMaximum, currentIsStartTime):
         """
-        Get the date furthest into the future in any time-range elements
+        Get the date farthest into the future in any time-range elements
         
         @param currentMaximum: current future value to compare with
-        @type currentMaximum: L{datetime.datetime}
+        @type currentMaximum: L{PyCalendarDateTime}
         """
         
         # Give tzinfo to any TimeRange we have
@@ -405,7 +407,7 @@
     def settzinfo(self, tzinfo):
         """
         Set the default timezone to use with this query.
-        @param tzinfo: a L{datetime.tzinfo} to use.
+        @param tzinfo: a L{PyCalendarTimezone} to use.
         """
         
         # Give tzinfo to any TimeRange we have
@@ -417,7 +419,7 @@
         Get the date furthest into the future in any time-range elements
         
         @param currentMaximum: current future value to compare with
-        @type currentMaximum: L{datetime.datetime}
+        @type currentMaximum: L{PyCalendarDateTime}
         """
         
         # Give tzinfo to any TimeRange we have
@@ -438,24 +440,13 @@
 
     def _match(self, property, access):
 
-        # We have to deal with the problem that the 'Native' form of a property
-        # will be missing the TZID parameter due to the conversion performed. Converting
-        # to non-native for the entire calendar object causes problems elsewhere, so its
-        # best to do it here for this one special case.
-        if self.filter_name == "TZID":
-            transformed = property.transformAllFromNative()
-        else:
-            transformed = False
-
-        # At least one property must match (or is-not-defined is set)
+        # At least one parameter must match (or is-not-defined is set)
         result = not self.defined
-        for parameterName in property.params().keys():
-            if parameterName == self.filter_name and self.match(property.params()[parameterName], access):
+        for parameterName in property.parameterNames():
+            if parameterName == self.filter_name and self.match([property.parameterValue(parameterName)], access):
                 result = self.defined
                 break
 
-        if transformed:
-            property.transformAllToNative()
         return result
 
 class IsNotDefined (FilterBase):
@@ -507,7 +498,7 @@
         if item is None: return False
 
         if isinstance(item, Property):
-            values = [item.value()]
+            values = [item.strvalue()]
         else:
             values = item
 
@@ -526,7 +517,7 @@
 
         for value in values:
             # NB Its possible that we have a text list value which appears as a Python list,
-            # so we need to check for that an iterate over the list.
+            # so we need to check for that and iterate over the list.
             if isinstance(value, list):
                 for subvalue in value:
                     matched, result = _textCompare(subvalue)
@@ -552,14 +543,14 @@
         if "start" not in xml_element.attributes and "end" not in xml_element.attributes:
             raise ValueError("One of 'start' or 'end' must be present in CALDAV:time-range")
         
-        self.start = parse_date_or_datetime(xml_element.attributes["start"]) if "start" in xml_element.attributes else None
-        self.end = parse_date_or_datetime(xml_element.attributes["end"]) if "end" in xml_element.attributes else None
+        self.start = PyCalendarDateTime.parseText(xml_element.attributes["start"]) if "start" in xml_element.attributes else None
+        self.end = PyCalendarDateTime.parseText(xml_element.attributes["end"]) if "end" in xml_element.attributes else None
         self.tzinfo = None
 
     def settzinfo(self, tzinfo):
         """
         Set the default timezone to use with this query.
-        @param tzinfo: a L{datetime.tzinfo} to use.
+        @param tzinfo: a L{PyCalendarTimezone} to use.
         """
         
         # Give tzinfo to any TimeRange we have
@@ -572,16 +563,16 @@
         @return:      True if valid, False otherwise
         """
         
-        if self.start is not None and not isinstance(self.start, datetime.datetime):
+        if self.start is not None and self.start.isDateOnly():
             log.msg("start attribute in <time-range> is not a date-time: %s" % (self.start,))
             return False
-        if self.end is not None and not isinstance(self.end, datetime.datetime):
+        if self.end is not None and self.end.isDateOnly():
             log.msg("end attribute in <time-range> is not a date-time: %s" % (self.end,))
             return False
-        if self.start is not None and self.start.tzinfo != utc:
+        if self.start is not None and not self.start.utc():
             log.msg("start attribute in <time-range> is not UTC: %s" % (self.start,))
             return False
-        if self.end is not None and self.end.tzinfo != utc:
+        if self.end is not None and not self.end.utc():
             log.msg("end attribute in <time-range> is not UTC: %s" % (self.end,))
             return False
 

Modified: CalendarServer/trunk/twistedcaldav/query/expression.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/expression.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/query/expression.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -75,7 +75,7 @@
 
     def __str__(self):
         """
-        Generate a suitable text descriptor of this epxression.
+        Generate a suitable text descriptor of this expression.
         
         @return: a C{str} of the text for this expression.
         """

Modified: CalendarServer/trunk/twistedcaldav/query/sqlgenerator.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/sqlgenerator.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/query/sqlgenerator.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -266,8 +266,8 @@
     e3 = expression.notcontainsExpression("SUMMARY", "help", True)
     e5 = expression.andExpression([e1, e2, e3])
     print e5
-    #sql = sqlgenerator(e5, 'dummy-cal', 'dummy-user')
-    #print sql.generate()
+    sql = sqlgenerator(e5, 'dummy-cal', 'dummy-user')
+    print sql.generate()
     e6 = expression.inExpression("TYPE", ("VEVENT", "VTODO",), False)
     print e6
     sql = sqlgenerator(e6, 'dummy-cal', 'dummy-user')

Modified: CalendarServer/trunk/twistedcaldav/query/test/test_calendarquery.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/test/test_calendarquery.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/query/test/test_calendarquery.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -16,8 +16,8 @@
 
 from twistedcaldav import caldavxml
 from twistedcaldav.query import calendarqueryfilter
-import datetime
 import twistedcaldav.test.util
+from pycalendar.timezone import PyCalendarTimezone
 
 class Tests(twistedcaldav.test.util.TestCase):
 
@@ -33,64 +33,5 @@
             )
         )
         filter = calendarqueryfilter.Filter(filter)
-    
-        # A complete implementation of current DST rules for major US time zones.
-        
-        def first_sunday_on_or_after(dt):
-            days_to_go = 6 - dt.weekday()
-            if days_to_go:
-                dt += datetime.timedelta(days_to_go)
-            return dt
-        
-        # In the US, DST starts at 2am (standard time) on the first Sunday in April.
-        DSTSTART = datetime.datetime(1, 4, 1, 2)
-        # and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct.
-        # which is the first Sunday on or after Oct 25.
-        DSTEND = datetime.datetime(1, 10, 25, 1)
-        
-        ZERO = datetime.timedelta(0)
-        HOUR = datetime.timedelta(hours=1)
-    
-        class USTimeZone(datetime.tzinfo):
-        
-            def __init__(self, hours, reprname, stdname, dstname):
-                self.stdoffset = datetime.timedelta(hours=hours)
-                self.reprname = reprname
-                self.stdname = stdname
-                self.dstname = dstname
-        
-            def __repr__(self):
-                return self.reprname
-        
-            def tzname(self, dt):
-                if self.dst(dt):
-                    return self.dstname
-                else:
-                    return self.stdname
-        
-            def utcoffset(self, dt):
-                return self.stdoffset + self.dst(dt)
-        
-            def dst(self, dt):
-                if dt is None or dt.tzinfo is None:
-                    # An exception may be sensible here, in one or both cases.
-                    # It depends on how you want to treat them.  The default
-                    # fromutc() implementation (called by the default astimezone()
-                    # implementation) passes a datetime with dt.tzinfo is self.
-                    return ZERO
-                assert dt.tzinfo is self
-        
-                # Find first Sunday in April & the last in October.
-                start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
-                end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
-        
-                # Can't compare naive to aware objects, so strip the timezone from
-                # dt first.
-                if start <= dt.replace(tzinfo=None) < end:
-                    return HOUR
-                else:
-                    return ZERO
-    
-        Eastern  = USTimeZone(-5, "Eastern",  "EST", "EDT")
-        filter.child.settzinfo(Eastern)
+        filter.child.settzinfo(PyCalendarTimezone(tzid="America/New_York"))
         
\ No newline at end of file

Modified: CalendarServer/trunk/twistedcaldav/query/test/test_queryfilter.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/test/test_queryfilter.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/query/test/test_queryfilter.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -17,6 +17,8 @@
 from twistedcaldav import caldavxml
 from twistedcaldav.query import calendarqueryfilter
 import twistedcaldav.test.util
+from twistedcaldav.caldavxml import TimeZone
+from pycalendar.timezone import PyCalendarTimezone
 
 class Tests(twistedcaldav.test.util.TestCase):
 
@@ -74,4 +76,151 @@
         )
 
         calendarqueryfilter.Filter(xml_element)
-        
\ No newline at end of file
+
+    def test_queryWithTimezone(self):
+
+        xml_element = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                caldavxml.ComponentFilter(
+                    caldavxml.TimeRange(**{"start":"20060605T160000Z", "end":"20060605T170000Z"}),
+                    **{"name":"VEVENT"}
+                ),
+                **{"name":"VCALENDAR"}
+            )
+        )
+
+        filter = calendarqueryfilter.Filter(xml_element)
+        tz = filter.settimezone(TimeZone.fromString("""BEGIN:VCALENDAR
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:America/New_York
+X-LIC-LOCATION:America/New_York
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+TZNAME:EDT
+DTSTART:19180331T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19200328T070000Z
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+TZNAME:EST
+DTSTART:19181027T020000
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19201031T060000Z
+END:STANDARD
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+TZNAME:EDT
+DTSTART:19210424T020000
+RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19410427T070000Z
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+TZNAME:EST
+DTSTART:19210925T020000
+RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1SU;UNTIL=19410928T060000Z
+END:STANDARD
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+TZNAME:EDT
+DTSTART:19460428T020000
+RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19730429T070000Z
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+TZNAME:EST
+DTSTART:19460929T020000
+RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1SU;UNTIL=19540926T060000Z
+END:STANDARD
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+TZNAME:EST
+DTSTART:19551030T020000
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=20061029T060000Z
+END:STANDARD
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+TZNAME:EDT
+DTSTART:19760425T020000
+RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19860427T070000Z
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+TZNAME:EDT
+DTSTART:19870405T020000
+RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU;UNTIL=20060402T070000Z
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+TZNAME:EDT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+TZNAME:EST
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+END:STANDARD
+BEGIN:STANDARD
+TZOFFSETFROM:-045602
+TZOFFSETTO:-0500
+TZNAME:EST
+DTSTART:18831118T120358
+RDATE:18831118T120358
+END:STANDARD
+BEGIN:STANDARD
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0500
+TZNAME:EST
+DTSTART:19200101T000000
+RDATE:19200101T000000
+RDATE:19420101T000000
+RDATE:19460101T000000
+RDATE:19670101T000000
+END:STANDARD
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+TZNAME:EWT
+DTSTART:19420209T020000
+RDATE:19420209T020000
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0400
+TZNAME:EPT
+DTSTART:19450814T190000
+RDATE:19450814T190000
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+TZNAME:EST
+DTSTART:19450930T020000
+RDATE:19450930T020000
+END:STANDARD
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+TZNAME:EDT
+DTSTART:19740106T020000
+RDATE:19740106T020000
+RDATE:19750223T020000
+END:DAYLIGHT
+END:VTIMEZONE
+END:VCALENDAR
+"""))
+
+        self.assertTrue(isinstance(tz, PyCalendarTimezone))

Modified: CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -15,7 +15,7 @@
 ##
 
 from twext.python.log import Logger
-from twext.python.datetime import timerange, asUTC, iCalendarString
+#from twext.python.datetime import timerange, asUTC, iCalendarString
 
 from twistedcaldav.config import config
 from twistedcaldav.ical import Component, Property
@@ -24,6 +24,8 @@
 from twistedcaldav import accounting
 
 from difflib import unified_diff
+from pycalendar.period import PyCalendarPeriod
+from pycalendar.datetime import PyCalendarDateTime
 
 """
 Class that handles diff'ing two calendar objects.
@@ -209,7 +211,7 @@
             
             # Whenever SCHEDULE-FORCE-SEND is explicitly set by the Organizer we assume the Organizer
             # is deliberately overwriting PARTSTAT
-            if new_attendee.params().get("SCHEDULE-FORCE-SEND", ["",])[0] == "REQUEST":
+            if new_attendee.parameterValue("SCHEDULE-FORCE-SEND", "") == "REQUEST":
                 continue
 
             # Transfer parameters from any old Attendees found
@@ -221,14 +223,14 @@
                 self._transferParameter(old_attendee, new_attendee, "SCHEDULE-STATUS")
     
     def _transferParameter(self, old_property, new_property, parameter):
-        paramvalue = old_property.params().get(parameter)
+        paramvalue = old_property.parameterValue(parameter)
         if paramvalue is None:
             try:
-                del new_property.params()[parameter]
+                new_property.removeParameter(parameter)
             except KeyError:
                 pass
         else:
-            new_property.params()[parameter] = paramvalue
+            new_property.setParameter(parameter, paramvalue)
 
     def attendeeMerge(self, attendee):
         """
@@ -282,7 +284,7 @@
                 # Get all EXDATEs in UTC
                 exdates = set()
                 for exdate in master.properties("EXDATE"):
-                    exdates.update([asUTC(value) for value in exdate.value()])
+                    exdates.update([value.getValue().adjustToUTC() for value in exdate.value()])
                
             return exdates, map, master
         
@@ -331,7 +333,7 @@
                     # Mark Attendee as DECLINED in the server instance
                     if self._attendeeDecline(self.newCalendar.overriddenComponent(rid)):
                         changeCausesReply = True
-                        changedRids.append(iCalendarString(rid) if rid else "")
+                        changedRids.append(rid.getText() if rid else "")
                 else:
                     # We used to generate a 403 here - but instead we now ignore this error and let the server data
                     # override the client
@@ -407,7 +409,7 @@
                 #return False, False, (), None
             changeCausesReply |= reply
             if reply:
-                changedRids.append(iCalendarString(rid) if rid else "")
+                changedRids.append(rid.getText() if rid else "")
 
         # We need to derive instances for any declined using an EXDATE
         for decline in sorted(declines):
@@ -418,7 +420,7 @@
                     self.newCalendar.addComponent(overridden)
                     if self._attendeeDecline(overridden):
                         changeCausesReply = True
-                        changedRids.append(iCalendarString(decline) if decline else "")
+                        changedRids.append(decline.getText() if decline else "")
                 else:
                     self._logDiffError("attendeeMerge: Unable to override an instance to mark as DECLINED: %s" % (decline,))
                     return False, False, (), None
@@ -456,17 +458,17 @@
         # ATTENDEE/PARTSTAT/RSVP
         serverAttendee = serverComponent.getAttendeeProperty((self.attendee,))
         clientAttendee = clientComponent.getAttendeeProperty((self.attendee,))
-        if serverAttendee.params().get("PARTSTAT", ("NEEDS-ACTION",))[0] != clientAttendee.params().get("PARTSTAT", ("NEEDS-ACTION",))[0]:
-            serverAttendee.params()["PARTSTAT"] = clientAttendee.params().get("PARTSTAT", "NEEDS-ACTION")
+        if serverAttendee.parameterValue("PARTSTAT", "NEEDS-ACTION") != clientAttendee.parameterValue("PARTSTAT", "NEEDS-ACTION"):
+            serverAttendee.setParameter("PARTSTAT", clientAttendee.parameterValue("PARTSTAT", "NEEDS-ACTION"))
             replyNeeded = True
-        if serverAttendee.params().get("RSVP", ("FALSE",))[0] != clientAttendee.params().get("RSVP", ("FALSE",))[0]:
-            if clientAttendee.params().get("RSVP", ("FALSE",))[0] == "FALSE":
+        if serverAttendee.parameterValue("RSVP", "FALSE") != clientAttendee.parameterValue("RSVP", "FALSE"):
+            if clientAttendee.parameterValue("RSVP", "FALSE") == "FALSE":
                 try:
-                    del serverAttendee.params()["RSVP"]
+                    serverAttendee.removeParameter("RSVP")
                 except KeyError:
                     pass
             else:
-                serverAttendee.params()["RSVP"] = ["TRUE",]
+                serverAttendee.setParameter("RSVP", "TRUE")
 
         # Transfer these properties from the client data
         replyNeeded |= self._transferProperty("X-CALENDARSERVER-PRIVATE-COMMENT", serverComponent, clientComponent)
@@ -506,7 +508,7 @@
 
             # Remove existing ATTACH's from server
             for attachment in tuple(serverComponent.properties("ATTACH")):
-                valueType = attachment.paramValue("VALUE")
+                valueType = attachment.parameterValue("VALUE")
                 if valueType in (None, "URI"):
                     dataValue = attachment.value()
                     if dataValue.find(serverDropbox) != -1:
@@ -514,7 +516,7 @@
         
             # Copy new ATTACH's to server
             for attachment in tuple(clientComponent.properties("ATTACH")):
-                valueType = attachment.paramValue("VALUE")
+                valueType = attachment.parameterValue("VALUE")
                 if valueType in (None, "URI"):
                     dataValue = attachment.value()
                     if dataValue.find(serverDropbox) != -1:
@@ -540,7 +542,7 @@
             # Bad if EXDATEs have been removed
             missing = serverProps[-1] - clientProps[-1]
             if missing:
-                log.debug("EXDATEs missing: %s" % (", ".join([iCalendarString(exdate) for exdate in missing]),))
+                log.debug("EXDATEs missing: %s" % (", ".join([exdate.getText() for exdate in missing]),))
                 return False
             declines.extend(clientProps[-1] - serverProps[-1])
             return True
@@ -555,7 +557,7 @@
             dtend = component.getProperty("DTEND")
             duration = component.getProperty("DURATION")
             
-            timeRange = timerange(
+            timeRange = PyCalendarPeriod(
                 start    = dtstart.value()  if dtstart  is not None else None,
                 end      = dtend.value()    if dtend    is not None else None,
                 duration = duration.value() if duration is not None else None,
@@ -567,23 +569,23 @@
             duration = component.getProperty("DURATION")
             
             if dtstart or duration:
-                timeRange = timerange(
+                timeRange = PyCalendarPeriod(
                     start    = dtstart.value()  if dtstart  is not None else None,
                     duration = duration.value() if duration is not None else None,
                 )
             else:
-                timeRange = timerange()
+                timeRange = PyCalendarPeriod()
 
             newdue = component.getProperty("DUE")
             if newdue is not None:
-                newdue = asUTC(newdue.value())
+                newdue.value().adjustToUTC()
             
         # Recurrence rules - we need to normalize the order of the value parts
         newrrules = set()
         rrules = component.properties("RRULE")
         for rrule in rrules:
             indexedTokens = {}
-            indexedTokens.update([valuePart.split("=") for valuePart in rrule.value().split(";")])
+            indexedTokens.update([valuePart.split("=") for valuePart in rrule.value().getText().split(";")])
             sortedValue = ";".join(["%s=%s" % (key, value,) for key, value in sorted(indexedTokens.iteritems(), key=lambda x:x[0])])
             newrrules.add(sortedValue)
         
@@ -591,15 +593,18 @@
         newrdates = set()
         rdates = component.properties("RDATE")
         for rdate in rdates:
-            newrdates.update([asUTC(value) for value in rdate.value()])
+            for value in rdate.value():
+                if isinstance(PyCalendarDateTime()):
+                    value.adjustToUTC()
+                newrdates.add(value)
         
         # EXDATEs
         newexdates = set()
         exdates = component.properties("EXDATE")
         for exdate in exdates:
-            newexdates.update([asUTC(value) for value in exdate.value()])
+            newexdates.update([value.getValue().adjustToUTC() for value in exdate.value()])
 
-        return timeRange.start(), timeRange.end(), newdue, newrrules, newrdates, newexdates
+        return timeRange.getStart(), timeRange.getEnd(), newdue, newrrules, newrdates, newexdates
 
     def _transferProperty(self, propName, serverComponent, clientComponent):
 
@@ -625,8 +630,8 @@
         @return: C{bool} indicating whether the PARTSTAT value was in fact changed
         """
         attendee = component.getAttendeeProperty((self.attendee,))
-        partstatChanged = attendee.params().get("PARTSTAT", ("NEEDS-ACTION",))[0] != "DECLINED"
-        attendee.params()["PARTSTAT"] = ["DECLINED",]
+        partstatChanged = attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") != "DECLINED"
+        attendee.setParameter("PARTSTAT", "DECLINED")
         prop = component.getProperty("X-APPLE-NEEDS-REPLY")
         if prop:
             component.removeProperty(prop)
@@ -705,11 +710,7 @@
         comp2 = self._componentDuplicateAndNormalize(comp2)
 
         # Diff all the properties
-        comp1.transformAllFromNative()
-        comp2.transformAllFromNative()
         propdiff = set(comp1.properties()) ^ set(comp2.properties())
-        comp1.transformAllToNative()
-        comp2.transformAllToNative()
         addedChanges = False
         
         propsChanged = {}
@@ -728,17 +729,17 @@
             prop1s = tuple(comp1.properties(prop.name()))
             prop2s = tuple(comp2.properties(prop.name()))
             if len(prop1s) == 1 and len(prop2s) == 1:
-                param1s = set(["%s=%s" % (name, value) for name, value in prop1s[0].params().iteritems()])
-                param2s = set(["%s=%s" % (name, value) for name, value in prop2s[0].params().iteritems()])
+                param1s = set(["%s=%s" % (name, prop1s[0].parameterValue(name)) for name in prop1s[0].parameterNames()])
+                param2s = set(["%s=%s" % (name, prop2s[0].parameterValue(name)) for name in prop2s[0].parameterNames()])
                 paramDiffs = param1s ^ param2s
                 propsChanged[prop.name()].update([param.split("=")[0] for param in paramDiffs])
-            if "ORIGINAL-TZID" in propsChanged[prop.name()]:
-                propsChanged[prop.name()].remove("ORIGINAL-TZID")
+            if "_TZID" in propsChanged[prop.name()]:
+                propsChanged[prop.name()].remove("_TZID")
                 propsChanged[prop.name()].add("TZID")
         
         if addedChanges:
             rid = comp1.getRecurrenceIDUTC()
-            rids[iCalendarString(rid) if rid is not None else ""] = propsChanged
+            rids[rid.getText() if rid is not None else ""] = propsChanged
 
     def _logDiffError(self, title):
 

Modified: CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/implicit.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/scheduling/implicit.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -279,7 +279,7 @@
         self.request.suppressRefresh = False
 
         for attendee in self.calendar.getAllAttendeeProperties():
-            if attendee.params().get("PARTSTAT", ["NEEDS-ACTION"])[0] == "NEEDS-ACTION":
+            if attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "NEEDS-ACTION":
                 self.request.suppressRefresh = True
         
         if hasattr(self.request, "doing_attendee_refresh"):
@@ -491,7 +491,7 @@
                     comp = self.calendar.overriddenComponent(rid)
             
                     for attendee in comp.getAllAttendeeProperties():
-                        if attendee.params().has_key("PARTSTAT"):
+                        if attendee.hasParameter("PARTSTAT"):
                             cuaddr = attendee.value()
                             
                             if cuaddr in self.organizerPrincipal.calendarUserAddresses():
@@ -500,7 +500,7 @@
                                 # The organizer is automatically ACCEPTED to the event.
                                 continue
 
-                            attendee.params()["PARTSTAT"] = ["NEEDS-ACTION",]
+                            attendee.setParameter("PARTSTAT", "NEEDS-ACTION")
 
                 # Check for removed attendees
                 if not recurrence_reschedule:
@@ -511,15 +511,15 @@
             
         # Always set RSVP=TRUE for any NEEDS-ACTION
         for attendee in self.calendar.getAllAttendeeProperties():
-            if attendee.params().get("PARTSTAT", ["NEEDS-ACTION"])[0] == "NEEDS-ACTION":
-                attendee.params()["RSVP"] = ["TRUE",]
+            if attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "NEEDS-ACTION":
+                attendee.setParameter("RSVP", "TRUE")
 
         yield self.scheduleWithAttendees()
         
         # Always clear SCHEDULE-FORCE-SEND from all attendees after scheduling
         for attendee in self.calendar.getAllAttendeeProperties():
             try:
-                del attendee.params()["SCHEDULE-FORCE-SEND"]
+                attendee.removeParameter("SCHEDULE-FORCE-SEND")
             except KeyError:
                 pass
 
@@ -571,7 +571,7 @@
             reinvites = set()
             for attendee in self.calendar.getAllAttendeeProperties():
                 try:
-                    if attendee.params()["SCHEDULE-FORCE-SEND"][0] == "REQUEST":
+                    if attendee.parameterValue("SCHEDULE-FORCE-SEND") == "REQUEST":
                         reinvites.add(attendee.value())
                 except KeyError:
                     pass
@@ -609,10 +609,10 @@
         # Also look for new EXDATEs
         oldexdates = set()
         for property in self.oldcalendar.masterComponent().properties("EXDATE"):
-            oldexdates.update(property.value())
+            oldexdates.update([value.getValue() for value in property.value()])
         newexdates = set()
         for property in self.calendar.masterComponent().properties("EXDATE"):
-            newexdates.update(property.value())
+            newexdates.update([value.getValue() for value in property.value()])
 
         addedexdates = newexdates - oldexdates
 

Modified: CalendarServer/trunk/twistedcaldav/scheduling/itip.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/itip.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/scheduling/itip.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -30,17 +30,13 @@
 # know how to deal with overridden instances.
 #
 
-import datetime
-
-from vobject.icalendar import utc
-from vobject.icalendar import dateTimeToString
-
 from twext.python.log import Logger
-from twext.python.datetime import asUTC, iCalendarString
 
 from twistedcaldav.config import config
 from twistedcaldav.ical import Property, iCalendarProductID, Component
 
+from pycalendar.datetime import PyCalendarDateTime
+
 log = Logger()
 
 __version__ = "0.0"
@@ -101,7 +97,7 @@
             private_comments = current_master.properties("X-CALENDARSERVER-PRIVATE-COMMENT")
             transps = current_master.properties("TRANSP")
             organizer = current_master.getProperty("ORGANIZER")
-            organizer_schedule_status = organizer.params().get("SCHEDULE-STATUS", None) if organizer else None
+            organizer_schedule_status = organizer.parameterValue("SCHEDULE-STATUS", None) if organizer else None
         else:
             master_valarms = ()
             private_comments = ()
@@ -124,7 +120,7 @@
             if organizer_schedule_status: 
                 organizer = master_component.getProperty("ORGANIZER")
                 if organizer:
-                    organizer.params()["SCHEDULE-STATUS"] = organizer_schedule_status
+                    organizer.setParameter("SCHEDULE-STATUS", organizer_schedule_status)
                 
             # Now try to match recurrences
             for component in new_calendar.subcomponents():
@@ -321,7 +317,7 @@
             if attendee:
                 attendees.add(attendee)
                 if rids is not None and (partstat or private_comment):
-                    rids.add((iCalendarString(rid), partstat, private_comment,))
+                    rids.add((rid.getText(), partstat, private_comment,))
 
         # Check for an invalid instance by itself
         len_attendees = len(attendees)
@@ -363,20 +359,20 @@
             return None, False, False
 
         attendee = attendees[0]
-        partstat = attendee.params().get("PARTSTAT", ("NEEDS-ACTION",))[0]
+        partstat = attendee.parameterValue("PARTSTAT", "NEEDS-ACTION")
         
         # Now find matching ATTENDEE in to_component
         existing_attendee = to_component.getAttendeeProperty((attendee.value(),))
         if existing_attendee:
-            oldpartstat = existing_attendee.params().get("PARTSTAT", ("NEEDS-ACTION",))[0]
-            existing_attendee.params()["PARTSTAT"] = [partstat]
-            existing_attendee.params()["SCHEDULE-STATUS"] = [reqstatus]
+            oldpartstat = existing_attendee.parameterValue("PARTSTAT", "NEEDS-ACTION")
+            existing_attendee.setParameter("PARTSTAT", partstat)
+            existing_attendee.setParameter("SCHEDULE-STATUS", reqstatus)
             partstat_changed = (oldpartstat != partstat)
             
             # Always delete RSVP on PARTSTAT change
             if partstat_changed:
                 try:
-                    del existing_attendee.params()["RSVP"]
+                    existing_attendee.removeParameter("RSVP")
                 except KeyError:
                     pass
 
@@ -389,12 +385,8 @@
                 # Look for matching X-CALENDARSERVER-ATTENDEE-COMMENT property in existing data (State 2 in spec)
                 private_comments = tuple(to_component.properties("X-CALENDARSERVER-ATTENDEE-COMMENT"))
                 for comment in private_comments:
-                    params = comment.params()["X-CALENDARSERVER-ATTENDEE-REF"]
-                    if len(params) != 1:
-                        log.error("Must be one and only one X-CALENDARSERVER-ATTENDEE-REF parameter in X-CALENDARSERVER-ATTENDEE-COMMENT")
-                        params = (None,)
-                    param = params[0]
-                    if param == attendee.value():
+                    attendeeref = comment.parameterValue("X-CALENDARSERVER-ATTENDEE-REF")
+                    if attendeeref == attendee.value():
                         private_comment = comment
                         break
                 else:
@@ -410,11 +402,11 @@
  
             elif attendee_comment is None and private_comment is not None:
                 # Remove all property parameters
-                private_comment.params().clear()
+                private_comment.removeAllParameters()
                 
                 # Add default parameters
-                private_comment.params()["X-CALENDARSERVER-ATTENDEE-REF"] = [attendee.value()]
-                private_comment.params()["X-CALENDARSERVER-DTSTAMP"] = [dateTimeToString(datetime.datetime.now(tz=utc))]
+                private_comment.setParameter("X-CALENDARSERVER-ATTENDEE-REF", attendee.value())
+                private_comment.setParameter("X-CALENDARSERVER-DTSTAMP", PyCalendarDateTime.getNowUTC().getText())
                 
                 # Set value empty
                 private_comment.setValue("")
@@ -428,8 +420,8 @@
                     "X-CALENDARSERVER-ATTENDEE-COMMENT",
                     attendee_comment.value(),
                     params = {
-                        "X-CALENDARSERVER-ATTENDEE-REF":     [attendee.value()],
-                        "X-CALENDARSERVER-DTSTAMP": [dateTimeToString(datetime.datetime.now(tz=utc))],
+                        "X-CALENDARSERVER-ATTENDEE-REF": attendee.value(),
+                        "X-CALENDARSERVER-DTSTAMP":      PyCalendarDateTime.getNowUTC().getText(),
                     }
                 )
                 to_component.addProperty(private_comment)
@@ -440,11 +432,11 @@
                 # Only change if different
                 if private_comment.value() != attendee_comment.value():
                     # Remove all property parameters
-                    private_comment.params().clear()
+                    private_comment.removeAllParameters()
                     
                     # Add default parameters
-                    private_comment.params()["X-CALENDARSERVER-ATTENDEE-REF"] = [attendee.value()]
-                    private_comment.params()["X-CALENDARSERVER-DTSTAMP"] = [dateTimeToString(datetime.datetime.now(tz=utc))]
+                    private_comment.setParameter("X-CALENDARSERVER-ATTENDEE-REF", attendee.value())
+                    private_comment.setParameter("X-CALENDARSERVER-DTSTAMP", PyCalendarDateTime.getNowUTC().getText())
                     
                     # Set new value
                     private_comment.setValue(attendee_comment.value())
@@ -467,11 +459,11 @@
             [to_component.replaceProperty(prop) for prop in matched.properties("TRANSP")]
 
             organizer = matched.getProperty("ORGANIZER")
-            organizer_schedule_status = organizer.params().get("SCHEDULE-STATUS", None) if organizer else None
+            organizer_schedule_status = organizer.parameterValue("SCHEDULE-STATUS", None) if organizer else None
             if organizer_schedule_status: 
                 organizer = to_component.getProperty("ORGANIZER")
                 if organizer:
-                    organizer.params()["SCHEDULE-STATUS"] = organizer_schedule_status
+                    organizer.setParameter("SCHEDULE-STATUS", organizer_schedule_status)
 
             # Remove the old one
             if remove_matched:
@@ -486,7 +478,7 @@
             if organizer_schedule_status: 
                 organizer = to_component.getProperty("ORGANIZER")
                 if organizer:
-                    organizer.params()["SCHEDULE-STATUS"] = organizer_schedule_status
+                    organizer.setParameter("SCHEDULE-STATUS", organizer_schedule_status)
     
     @staticmethod
     def fixForiCal3(components, recipient, compatibilityMode):
@@ -498,7 +490,7 @@
                 continue
             attendee = component.getAttendeeProperty((recipient,))
             if attendee:
-                partstat = attendee.params().get("PARTSTAT", ("NEEDS-ACTION",))[0]
+                partstat = attendee.parameterValue("PARTSTAT", "NEEDS-ACTION")
                 if partstat == "NEEDS-ACTION":
                     if compatibilityMode:
                         component.addProperty(Property("X-APPLE-NEEDS-REPLY", "TRUE"))
@@ -523,7 +515,6 @@
             
             # Create a new component matching the type of the original
             comp = Component(original.mainType())
-            itip.addComponent(comp)
 
             # Use the master component when the instance is None
             if not instance_rid:
@@ -535,14 +526,14 @@
             assert instance is not None, "Need a master component"
 
             # Add some required properties extracted from the original
-            comp.addProperty(Property("DTSTAMP", datetime.datetime.now(tz=utc)))
+            comp.addProperty(Property("DTSTAMP", PyCalendarDateTime.getNowUTC()))
             comp.addProperty(Property("UID", instance.propertyValue("UID")))
             seq = instance.propertyValue("SEQUENCE")
-            seq = str(int(seq) + 1) if seq else "1"
+            seq = int(seq) + 1 if seq else 1
             comp.addProperty(Property("SEQUENCE", seq))
             comp.addProperty(instance.getOrganizerProperty())
             if instance_rid:
-                comp.addProperty(Property("RECURRENCE-ID", asUTC(instance_rid)))
+                comp.addProperty(Property("RECURRENCE-ID", instance_rid.duplicate().adjustToUTC()))
             
             def addProperties(propname):
                 for property in instance.properties(propname):
@@ -567,6 +558,8 @@
                 comp.addProperty(attendeeProp)
 
             tzids.update(comp.timezoneIDs())
+
+            itip.addComponent(comp)
             
         # Now include any referenced tzids
         for comp in original.subcomponents():
@@ -589,7 +582,7 @@
         itip.addProperty(Property("METHOD", "REQUEST"))
         
         # Force update to DTSTAMP everywhere
-        itip.replacePropertyInAllComponents(Property("DTSTAMP", datetime.datetime.now(tz=utc)))
+        itip.replacePropertyInAllComponents(Property("DTSTAMP", PyCalendarDateTime.getNowUTC()))
 
         # Now filter out components that do not contain every attendee
         itip.attendeesView(attendees, onlyScheduleAgentServer=True)
@@ -617,7 +610,7 @@
         itip.filterComponents(changedRids)
 
         # Force update to DTSTAMP everywhere
-        itip.replacePropertyInAllComponents(Property("DTSTAMP", datetime.datetime.now(tz=utc)))
+        itip.replacePropertyInAllComponents(Property("DTSTAMP", PyCalendarDateTime.getNowUTC()))
 
         # Remove all attendees except the one we want
         itip.removeAllButOneAttendee(attendee)
@@ -651,11 +644,11 @@
             attendeeProps = itip.getAttendeeProperties((attendee,))
             assert attendeeProps, "Must have some matching ATTENDEEs"
             for attendeeProp in attendeeProps:
-                attendeeProp.params().setdefault("PARTSTAT", ["DECLINED",])[0] = "DECLINED"
+                attendeeProp.setParameter("PARTSTAT", "DECLINED")
         
         # Add REQUEST-STATUS to each top-level component
         itip.addPropertyToAllComponents(Property("REQUEST-STATUS", ["2.0", "Success",]))
-        
+
         # Strip out unwanted bits
         iTipGenerator.prepareSchedulingMessage(itip, reply=True)
 

Modified: CalendarServer/trunk/twistedcaldav/scheduling/processing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/processing.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/scheduling/processing.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -14,12 +14,9 @@
 # limitations under the License.
 ##
 
-import datetime
 import time
 from hashlib import md5
 
-from vobject.icalendar import dateTimeToString, utc
-
 from twisted.python.log import err as log_traceback
 from twext.python.log import Logger
 
@@ -37,6 +34,9 @@
 from twistedcaldav.scheduling.itip import iTipProcessing, iTIPRequestStatus
 from twistedcaldav.scheduling.utils import getCalendarObjectForPrincipals
 from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
+from pycalendar.duration import PyCalendarDuration
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.timezone import PyCalendarTimezone
 
 __all__ = [
     "ImplicitProcessor",
@@ -480,8 +480,8 @@
         log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - checking for auto-reply" % (self.recipient.cuaddr, self.uid))
 
         # First expand current one to get instances (only go 1 year into the future)
-        default_future_expansion_duration = datetime.timedelta(days=356*1)
-        expand_max = datetime.date.today() + default_future_expansion_duration
+        default_future_expansion_duration = PyCalendarDuration(days=356*1)
+        expand_max = PyCalendarDateTime.getToday() + default_future_expansion_duration
         instances = calendar.expandTimeRanges(expand_max, ignoreInvalidInstances=True)
         instance_states = dict([(instance, True) for instance in instances.instances.itervalues()])
         
@@ -501,9 +501,9 @@
             has_prop = (yield testcal.hasProperty((caldav_namespace, "calendar-timezone"), self.request))
             if has_prop:
                 tz = (yield testcal.readProperty((caldav_namespace, "calendar-timezone"), self.request))
-                tzinfo = tz.calendar().gettzinfo()
+                tzinfo = tz.calendar().gettimezone()
             else:
-                tzinfo = utc
+                tzinfo = PyCalendarTimezone(utc=True)
 
             # Now do search for overlapping time-range
             for instance in instances.instances.itervalues():
@@ -513,15 +513,18 @@
                         fbinfo = ([], [], [])
                         
                         def makeTimedUTC(dt):
-                            if isinstance(dt, datetime.date) and not isinstance(dt, datetime.datetime):
-                                dt = datetime.datetime.fromordinal(dt.toordinal())
-                            if dt.tzinfo is None:
-                                dt = dt.replace(tzinfo=tzinfo).astimezone(utc)
+                            dt = dt.duplicate()
+                            if dt.isDateOnly():
+                                dt.setDateOnly(False)
+                                dt.setHHMMSS(0, 0, 0)
+                            if dt.floating():
+                                dt.setTimezone(tzinfo)
+                                dt.adjustToUTC()
                             return dt
                         
                         tr = caldavxml.TimeRange(
-                            start=dateTimeToString(makeTimedUTC(instance.start)),
-                            end=dateTimeToString(makeTimedUTC(instance.end)),
+                            start=str(makeTimedUTC(instance.start)),
+                            end=str(makeTimedUTC(instance.end)),
                         )
 
                         yield report_common.generateFreeBusyInfo(self.request, testcal, fbinfo, tr, 0, uid, servertoserver=True)
@@ -692,14 +695,14 @@
 
         madeChanges = False
         for attendee in attendees:
-            if attendee.params().get("PARTSTAT", ("NEEDS-ACTION",))[0] != partstat:
-                attendee.params()["PARTSTAT"] = [partstat]
+            if attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") != partstat:
+                attendee.setParameter("PARTSTAT", partstat)
                 madeChanges = True
 
             # Always remove RSVP - this is only an attendee change so madeChanges
             # does not need to be changed
             try:
-                del attendee.params()["RSVP"]
+                attendee.removeParameter("RSVP")
             except KeyError:
                 pass
 

Modified: CalendarServer/trunk/twistedcaldav/scheduling/scheduler.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/scheduler.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/scheduling/scheduler.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -382,7 +382,7 @@
                     ))
 
                 # Some clients send floating instead of UTC - coerce to UTC
-                if dtstart.tzinfo is None or dtend.tzinfo is None:
+                if not dtstart.utc() or not dtend.utc():
                     log.err("VFREEBUSY start or end not UTC: %s" % (self.calendar,))
                     raise HTTPError(ErrorResponse(
                         responsecode.FORBIDDEN,

Modified: CalendarServer/trunk/twistedcaldav/scheduling/test/test_icaldiff.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/test/test_icaldiff.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/scheduling/test/test_icaldiff.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -855,6 +855,13 @@
 BEGIN:VTIMEZONE
 TZID:US-Eastern
 LAST-MODIFIED:20040110T032845Z
+BEGIN:DAYLIGHT
+DTSTART:19900404T010000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
 BEGIN:STANDARD
 DTSTART:19901026T060000
 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
@@ -862,13 +869,6 @@
 TZOFFSETFROM:-0400
 TZOFFSETTO:-0500
 END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19900404T010000
-RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
 END:VTIMEZONE
 BEGIN:VEVENT
 UID:12345-67890
@@ -952,6 +952,13 @@
 BEGIN:VTIMEZONE
 TZID:US-Eastern
 LAST-MODIFIED:20040110T032845Z
+BEGIN:DAYLIGHT
+DTSTART:19900404T010000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
 BEGIN:STANDARD
 DTSTART:19901026T060000
 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
@@ -959,13 +966,6 @@
 TZOFFSETFROM:-0400
 TZOFFSETTO:-0500
 END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19900404T010000
-RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
 END:VTIMEZONE
 BEGIN:VEVENT
 UID:12345-67890
@@ -1049,6 +1049,13 @@
 BEGIN:VTIMEZONE
 TZID:US-Eastern
 LAST-MODIFIED:20040110T032845Z
+BEGIN:DAYLIGHT
+DTSTART:19900404T010000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
 BEGIN:STANDARD
 DTSTART:19901026T060000
 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
@@ -1056,13 +1063,6 @@
 TZOFFSETFROM:-0400
 TZOFFSETTO:-0500
 END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19900404T010000
-RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
 END:VTIMEZONE
 BEGIN:VEVENT
 UID:12345-67890
@@ -1146,6 +1146,13 @@
 BEGIN:VTIMEZONE
 TZID:US-Eastern
 LAST-MODIFIED:20040110T032845Z
+BEGIN:DAYLIGHT
+DTSTART:19900404T010000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
 BEGIN:STANDARD
 DTSTART:19901026T060000
 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
@@ -1153,13 +1160,6 @@
 TZOFFSETFROM:-0400
 TZOFFSETTO:-0500
 END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19900404T010000
-RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
 END:VTIMEZONE
 BEGIN:VEVENT
 UID:12345-67890
@@ -1245,7 +1245,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:COUNT=400;FREQ=DAILY
+RRULE:FREQ=DAILY;COUNT=400
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890
@@ -1325,7 +1325,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:COUNT=400;FREQ=DAILY
+RRULE:FREQ=DAILY;COUNT=400
 BEGIN:VALARM
 ACTION:DISPLAY
 DESCRIPTION:Test for Attendee
@@ -1392,7 +1392,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:COUNT=400;FREQ=DAILY
+RRULE:FREQ=DAILY;COUNT=400
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890
@@ -1471,7 +1471,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:COUNT=400;FREQ=DAILY
+RRULE:FREQ=DAILY;COUNT=400
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890
@@ -1559,7 +1559,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:COUNT=400;FREQ=DAILY
+RRULE:FREQ=DAILY;COUNT=400
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890
@@ -1647,7 +1647,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:COUNT=400;FREQ=DAILY
+RRULE:FREQ=DAILY;COUNT=400
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890
@@ -1728,7 +1728,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:COUNT=400;FREQ=DAILY
+RRULE:FREQ=DAILY;COUNT=400
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890
@@ -1784,7 +1784,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:COUNT=400;FREQ=DAILY
+RRULE:FREQ=DAILY;COUNT=400
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890
@@ -1858,7 +1858,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:COUNT=400;FREQ=DAILY
+RRULE:FREQ=DAILY;COUNT=400
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890
@@ -1924,7 +1924,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:COUNT=400;FREQ=DAILY
+RRULE:FREQ=DAILY;COUNT=400
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890
@@ -2079,7 +2079,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:COUNT=400;FREQ=DAILY
+RRULE:FREQ=DAILY;COUNT=400
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890
@@ -2147,7 +2147,7 @@
 ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
 EXDATE:20080604T120000Z
 ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:COUNT=400;FREQ=DAILY
+RRULE:FREQ=DAILY;COUNT=400
 END:VEVENT
 END:VCALENDAR
 """)
@@ -2431,8 +2431,8 @@
 UID:12345-67890
 DTSTART:20080601T120000Z
 DTEND:20080601T130000Z
-ATTACH;VALUE=URI:http://localhost/calendars/users/dropbox/6073432E-644B-49
- 65-B6F7-C3F08E70BBF9.dropbox/caldavd.plist
+ATTACH:http://localhost/calendars/users/dropbox/6073432E-644B-4965-B6F7-C3
+ F08E70BBF9.dropbox/caldavd.plist
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
@@ -2533,8 +2533,8 @@
 UID:12345-67890
 DTSTART:20080601T120000Z
 DTEND:20080601T130000Z
-ATTACH;VALUE=URI:http://localhost/calendars/users/dropbox/6073432E-644B-49
- 65-B6F7-C3F08E70BBF9.dropbox/caldavd.plist
+ATTACH:http://localhost/calendars/users/dropbox/6073432E-644B-4965-B6F7-C3
+ F08E70BBF9.dropbox/caldavd.plist
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
@@ -2641,10 +2641,10 @@
 UID:12345-67890
 DTSTART:20080601T120000Z
 DTEND:20080601T130000Z
-ATTACH;VALUE=URI:http://localhost/calendars/users/dropbox/6073432E-644B-49
- 65-B6F7-C3F08E70BBF9.dropbox/caldavd.plist
-ATTACH;VALUE=URI:http://localhost/calendars/users/dropbox/6073432E-644B-49
- 65-B6F7-C3F08E70BBF9.dropbox/caldavd-2.plist
+ATTACH:http://localhost/calendars/users/dropbox/6073432E-644B-4965-B6F7-C3
+ F08E70BBF9.dropbox/caldavd.plist
+ATTACH:http://localhost/calendars/users/dropbox/6073432E-644B-4965-B6F7-C3
+ F08E70BBF9.dropbox/caldavd-2.plist
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
@@ -3595,6 +3595,44 @@
 """,
                 {"":{"ATTENDEE":set(),}},
             ),
+            (
+                "#2.8 Simple recurring component, property order change",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:COUNT=400;FREQ=DAILY
+EXDATE:20080602T120000Z
+EXDATE:20080603T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:COUNT=400;FREQ=DAILY
+EXDATE:20080603T120000Z
+EXDATE:20080602T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                {},
+            ),
         )
         
         data3 = (
@@ -3843,7 +3881,7 @@
 END:VEVENT
 END:VCALENDAR
 """,
-                {"":{"SUMMARY":set()}, "20080602T120000Z":{"DESCRIPTION":set()}},
+                {"":{"SUMMARY":set()}, "20080602T120000Z":{"Description":set()}},
             ),
             (
                 "#3.6 Simple component, instance added no change",

Modified: CalendarServer/trunk/twistedcaldav/scheduling/test/test_implicit.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/test/test_implicit.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/scheduling/test/test_implicit.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -17,8 +17,8 @@
 from twistedcaldav.ical import Component
 import twistedcaldav.test.util
 from twistedcaldav.scheduling.implicit import ImplicitScheduler
-from dateutil.tz import tzutc
-import datetime
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.timezone import PyCalendarTimezone
 
 class Implicit (twistedcaldav.test.util.TestCase):
     """
@@ -28,134 +28,134 @@
     def test_removed_attendees(self):
         
         data = (
+#            (
+#                "#1.1 Simple component, no change",
+#                """BEGIN:VCALENDAR
+#VERSION:2.0
+#PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+#BEGIN:VEVENT
+#UID:12345-67890
+#DTSTART:20080601T120000Z
+#DTEND:20080601T130000Z
+#ORGANIZER;CN="User 01":mailto:user1 at example.com
+#ATTENDEE:mailto:user1 at example.com
+#ATTENDEE:mailto:user2 at example.com
+#END:VEVENT
+#END:VCALENDAR
+#""",
+#                """BEGIN:VCALENDAR
+#VERSION:2.0
+#PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+#BEGIN:VEVENT
+#UID:12345-67890
+#DTSTART:20080601T120000Z
+#DTEND:20080601T130000Z
+#ORGANIZER;CN="User 01":mailto:user1 at example.com
+#ATTENDEE:mailto:user1 at example.com
+#ATTENDEE:mailto:user2 at example.com
+#END:VEVENT
+#END:VCALENDAR
+#""",
+#                (),
+#            ),
+#            (
+#                "#1.2 Simple component, one removal",
+#                """BEGIN:VCALENDAR
+#VERSION:2.0
+#PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+#BEGIN:VEVENT
+#UID:12345-67890
+#DTSTART:20080601T120000Z
+#DTEND:20080601T130000Z
+#ORGANIZER;CN="User 01":mailto:user1 at example.com
+#ATTENDEE:mailto:user1 at example.com
+#ATTENDEE:mailto:user2 at example.com
+#END:VEVENT
+#END:VCALENDAR
+#""",
+#                """BEGIN:VCALENDAR
+#VERSION:2.0
+#PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+#BEGIN:VEVENT
+#UID:12345-67890
+#DTSTART:20080601T120000Z
+#DTEND:20080601T130000Z
+#ORGANIZER;CN="User 01":mailto:user1 at example.com
+#ATTENDEE:mailto:user1 at example.com
+#END:VEVENT
+#END:VCALENDAR
+#""",
+#                (("mailto:user2 at example.com", None),),
+#            ),
+#            (
+#                "#1.3 Simple component, two removals",
+#                """BEGIN:VCALENDAR
+#VERSION:2.0
+#PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+#BEGIN:VEVENT
+#UID:12345-67890
+#DTSTART:20080601T120000Z
+#DTEND:20080601T130000Z
+#ORGANIZER;CN="User 01":mailto:user1 at example.com
+#ATTENDEE:mailto:user1 at example.com
+#ATTENDEE:mailto:user2 at example.com
+#ATTENDEE:mailto:user3 at example.com
+#END:VEVENT
+#END:VCALENDAR
+#""",
+#                """BEGIN:VCALENDAR
+#VERSION:2.0
+#PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+#BEGIN:VEVENT
+#UID:12345-67890
+#DTSTART:20080601T120000Z
+#DTEND:20080601T130000Z
+#ORGANIZER;CN="User 01":mailto:user1 at example.com
+#ATTENDEE:mailto:user1 at example.com
+#END:VEVENT
+#END:VCALENDAR
+#""",
+#                (
+#                    ("mailto:user2 at example.com", None),
+#                    ("mailto:user3 at example.com", None),
+#                ),
+#            ),
+#            (
+#                "#2.1 Simple recurring component, two removals",
+#                """BEGIN:VCALENDAR
+#VERSION:2.0
+#PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+#BEGIN:VEVENT
+#UID:12345-67890
+#DTSTART:20080601T120000Z
+#DTEND:20080601T130000Z
+#ORGANIZER;CN="User 01":mailto:user1 at example.com
+#ATTENDEE:mailto:user1 at example.com
+#ATTENDEE:mailto:user2 at example.com
+#ATTENDEE:mailto:user3 at example.com
+#RRULE:FREQ=MONTHLY
+#END:VEVENT
+#END:VCALENDAR
+#""",
+#                """BEGIN:VCALENDAR
+#VERSION:2.0
+#PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+#BEGIN:VEVENT
+#UID:12345-67890
+#DTSTART:20080601T120000Z
+#DTEND:20080601T130000Z
+#ORGANIZER;CN="User 01":mailto:user1 at example.com
+#ATTENDEE:mailto:user1 at example.com
+#RRULE:FREQ=MONTHLY
+#END:VEVENT
+#END:VCALENDAR
+#""",
+#                (
+#                    ("mailto:user2 at example.com", None),
+#                    ("mailto:user3 at example.com", None),
+#                ),
+#            ),
             (
-                "#1.1 Simple component, no change",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                (),
-            ),
-            (
-                "#1.2 Simple component, one removal",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                (("mailto:user2 at example.com", None),),
-            ),
-            (
-                "#1.3 Simple component, two removals",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                (
-                    ("mailto:user2 at example.com", None),
-                    ("mailto:user3 at example.com", None),
-                ),
-            ),
-            (
-                "#2.1 Simple recurring component, two removals",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-RRULE:FREQ=MONTHLY
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-RRULE:FREQ=MONTHLY
-END:VEVENT
-END:VCALENDAR
-""",
-                (
-                    ("mailto:user2 at example.com", None),
-                    ("mailto:user3 at example.com", None),
-                ),
-            ),
-            (
                 "#2.2 Simple recurring component, add exdate",
                 """BEGIN:VCALENDAR
 VERSION:2.0
@@ -189,9 +189,9 @@
 END:VCALENDAR
 """,
                 (
-                    ("mailto:user1 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user2 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user3 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user1 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user2 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user3 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
                 ),
             ),
             (
@@ -228,12 +228,12 @@
 END:VCALENDAR
 """,
                 (
-                    ("mailto:user1 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user2 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user3 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user1 at example.com", datetime.datetime(2008, 9, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user2 at example.com", datetime.datetime(2008, 9, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user3 at example.com", datetime.datetime(2008, 9, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user1 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user2 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user3 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user1 at example.com", PyCalendarDateTime(2008, 9, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user2 at example.com", PyCalendarDateTime(2008, 9, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user3 at example.com", PyCalendarDateTime(2008, 9, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
                 ),
             ),
             (
@@ -271,15 +271,15 @@
 END:VCALENDAR
 """,
                 (
-                    ("mailto:user1 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user2 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user3 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user1 at example.com", datetime.datetime(2008, 9, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user2 at example.com", datetime.datetime(2008, 9, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user3 at example.com", datetime.datetime(2008, 9, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user1 at example.com", datetime.datetime(2008, 12, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user2 at example.com", datetime.datetime(2008, 12, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user3 at example.com", datetime.datetime(2008, 12, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user1 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user2 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user3 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user1 at example.com", PyCalendarDateTime(2008, 9, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user2 at example.com", PyCalendarDateTime(2008, 9, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user3 at example.com", PyCalendarDateTime(2008, 9, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user1 at example.com", PyCalendarDateTime(2008, 12, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user2 at example.com", PyCalendarDateTime(2008, 12, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user3 at example.com", PyCalendarDateTime(2008, 12, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
                 ),
             ),
             (
@@ -388,7 +388,7 @@
 """,
                 (
                     ("mailto:user3 at example.com", None),
-                    ("mailto:user3 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user3 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
                 ),
             ),
             (
@@ -443,7 +443,7 @@
 END:VCALENDAR
 """,
                 (
-                    ("mailto:user3 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user3 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
                 ),
             ),
             (
@@ -589,9 +589,9 @@
 END:VCALENDAR
 """,
                 (
-                    ("mailto:user1 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user2 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user3 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user1 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user2 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user3 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
                 ),
             ),
             (
@@ -646,7 +646,7 @@
 """,
                 (
                     ("mailto:user3 at example.com", None),
-                    ("mailto:user4 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user4 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
                 ),
             ),
             (
@@ -692,7 +692,7 @@
 END:VCALENDAR
 """,
                 (
-                    ("mailto:user4 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user4 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
                 ),
             ),
             (
@@ -739,9 +739,9 @@
 END:VCALENDAR
 """,
                 (
-                    ("mailto:user1 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user2 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user4 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user1 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user2 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user4 at example.com", PyCalendarDateTime(2008, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True))),
                 ),
             ),
         )

Modified: CalendarServer/trunk/twistedcaldav/scheduling/test/test_itip.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/test/test_itip.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/scheduling/test/test_itip.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -14,10 +14,10 @@
 # limitations under the License.
 ##
 
-from dateutil.tz import tzutc
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.timezone import PyCalendarTimezone
 from twistedcaldav.ical import Component
 from twistedcaldav.scheduling.itip import iTipProcessing, iTipGenerator
-import datetime
 import os
 import twistedcaldav.test.util
 
@@ -1269,7 +1269,7 @@
 END:VCALENDAR
 """,
                 ("mailto:user2 at example.com",),
-                (datetime.datetime(2008, 11, 14, 0, 0, tzinfo=tzutc()), ),
+                (PyCalendarDateTime(2008, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), ),
             ),
 
             # Recurring component with one instance, each with one attendee - cancel instance
@@ -1308,7 +1308,7 @@
 END:VCALENDAR
 """,
                 ("mailto:user2 at example.com",),
-                (datetime.datetime(2008, 11, 14, 0, 0, tzinfo=tzutc()), ),
+                (PyCalendarDateTime(2008, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), ),
             ),
 
             # Recurring component with one instance, each with one attendee - cancel master

Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/sharing.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -41,10 +41,9 @@
 from twistedcaldav.linkresource import LinkFollowerMixIn
 from twistedcaldav.sql import AbstractSQLDatabase, db_prefix
 
-from vobject.icalendar import dateTimeToString, utc
+from pycalendar.datetime import PyCalendarDateTime
 
 from uuid import uuid4
-import datetime
 import os
 import types
 
@@ -564,7 +563,7 @@
         typeAttr = {'shared-type':self.sharedResourceType()}
         xmltype = customxml.InviteNotification(**typeAttr)
         xmldata = customxml.Notification(
-            customxml.DTStamp.fromString(dateTimeToString(datetime.datetime.now(tz=utc))),
+            customxml.DTStamp.fromString(PyCalendarDateTime.getNowUTC().getText()),
             customxml.InviteNotification(
                 customxml.UID.fromString(record.inviteuid),
                 davxml.HRef.fromString(record.userid),
@@ -1123,7 +1122,7 @@
         notificationUID = "%s-reply" % (replytoUID,)
         xmltype = customxml.InviteReply()
         xmldata = customxml.Notification(
-            customxml.DTStamp.fromString(dateTimeToString(datetime.datetime.now(tz=utc))),
+            customxml.DTStamp.fromString(PyCalendarDateTime.getNowUTC().getText()),
             customxml.InviteReply(
                 *(
                     (

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -38,7 +38,7 @@
 from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.config import config
 from twistedcaldav.ical import Component as VCalendar, Property as VProperty,\
-    InvalidICalendarDataError
+    InvalidICalendarDataError, iCalendarProductID
 from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
 from twistedcaldav.method.put_addressbook_common import StoreAddressObjectResource
 from twistedcaldav.method.put_common import StoreCalendarObjectResource
@@ -47,6 +47,7 @@
 from twistedcaldav.schedule import ScheduleInboxResource
 from twistedcaldav.scheduling.implicit import ImplicitScheduler
 from twistedcaldav.vcard import Component as VCard, InvalidVCardDataError
+
 from txdav.base.propertystore.base import PropertyName
 from txdav.common.icommondatastore import NoSuchObjectResourceError
 from urlparse import urlsplit
@@ -915,6 +916,7 @@
         # Generate a monolithic calendar
         calendar = VCalendar("VCALENDAR")
         calendar.addProperty(VProperty("VERSION", "2.0"))
+        calendar.addProperty(VProperty("PRODID", iCalendarProductID))
 
         # Do some optimisation of access control calculation by determining any
         # inherited ACLs outside of the child resource loop and supply those to
@@ -993,6 +995,7 @@
         for components in by_uid.values():
             
             newvcal = VCalendar("VCALENDAR")
+            newvcal.addProperty(VProperty("VERSION", "2.0"))
             newvcal.addProperty(VProperty("PRODID", vcal.propertyValue("PRODID")))
             
             # Get the set of TZIDs and include them
@@ -1002,7 +1005,7 @@
             for tzid in tzids:
                 try:
                     tz = by_tzid[tzid]
-                    newvcal.addComponent(tz)
+                    newvcal.addComponent(tz.duplicate())
                 except KeyError:
                     # We ignore the error and generate invalid ics which someone will
                     # complain about at some point
@@ -1010,7 +1013,7 @@
             
             # Now add each component
             for component in components:
-                newvcal.addComponent(component)
+                newvcal.addComponent(component.duplicate())
  
             results.append(newvcal)
         

Copied: CalendarServer/trunk/twistedcaldav/test/test_caldavxml.py (from rev 7206, CalendarServer/branches/users/cdaboo/pycalendar/twistedcaldav/test/test_caldavxml.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_caldavxml.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/test/test_caldavxml.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -0,0 +1,52 @@
+##
+# Copyright (c) 2011 Apple Inc. 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 twistedcaldav import caldavxml
+import twistedcaldav.test.util
+
+class CustomXML (twistedcaldav.test.util.TestCase):
+
+
+    def test_TimeRange(self):
+        
+        self.assertRaises(ValueError, caldavxml.CalDAVTimeRangeElement)
+
+        tr = caldavxml.CalDAVTimeRangeElement(start="20110201T120000Z")
+        self.assertTrue(tr.valid())
+        
+        tr = caldavxml.CalDAVTimeRangeElement(start="20110201T120000")
+        self.assertFalse(tr.valid())
+        
+        tr = caldavxml.CalDAVTimeRangeElement(start="20110201")
+        self.assertFalse(tr.valid())
+
+        tr = caldavxml.CalDAVTimeRangeElement(end="20110201T120000Z")
+        self.assertTrue(tr.valid())
+        
+        tr = caldavxml.CalDAVTimeRangeElement(end="20110201T120000")
+        self.assertFalse(tr.valid())
+        
+        tr = caldavxml.CalDAVTimeRangeElement(end="20110201")
+        self.assertFalse(tr.valid())
+
+        tr = caldavxml.CalDAVTimeRangeElement(start="20110201T120000Z", end="20110202T120000Z")
+        self.assertTrue(tr.valid())
+        
+        tr = caldavxml.CalDAVTimeRangeElement(start="20110201T120000Z", end="20110202T120000")
+        self.assertFalse(tr.valid())
+        
+        tr = caldavxml.CalDAVTimeRangeElement(start="20110201T120000Z", end="20110202")
+        self.assertFalse(tr.valid())

Copied: CalendarServer/trunk/twistedcaldav/test/test_customxml.py (from rev 7206, CalendarServer/branches/users/cdaboo/pycalendar/twistedcaldav/test/test_customxml.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_customxml.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/test/test_customxml.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -0,0 +1,29 @@
+##
+# Copyright (c) 2011 Apple Inc. 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 twistedcaldav import customxml
+import time
+import twistedcaldav.test.util
+
+class CustomXML (twistedcaldav.test.util.TestCase):
+
+
+    def test_DTStamp(self):
+        
+        dtstamp = customxml.DTStamp()
+        now = time.time()
+        now_tm = time.gmtime( now )
+        self.assertEqual(str(dtstamp)[:4], "%s" % (now_tm.tm_year,))

Modified: CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -15,19 +15,18 @@
 ##
 
 import os
-import datetime
-from dateutil.tz import tzutc
 from difflib import unified_diff
 import itertools
 
 from twisted.trial.unittest import SkipTest
 
-from twistedcaldav.ical import Component, parse_date, parse_datetime,\
-    parse_date_or_datetime, parse_duration, Property, InvalidICalendarDataError
+from twistedcaldav.ical import Component, Property, InvalidICalendarDataError
 from twistedcaldav.instance import InvalidOverriddenInstanceError
 import twistedcaldav.test.util
 
-from vobject.icalendar import utc
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.timezone import PyCalendarTimezone
+from pycalendar.duration import PyCalendarDuration
 
 class iCalendar (twistedcaldav.test.util.TestCase):
     """
@@ -52,17 +51,183 @@
                 SkipTest("test unimplemented")
 
     def test_component_equality(self):
-        for filename in (
-            os.path.join(self.data_dir, "Holidays", "C318A4BA-1ED0-11D9-A5E0-000A958A3252.ics"),
-            os.path.join(self.data_dir, "Holidays.ics"),
-        ):
-            data = file(filename).read()
+#        for filename in (
+#            os.path.join(self.data_dir, "Holidays", "C318A4BA-1ED0-11D9-A5E0-000A958A3252.ics"),
+#            os.path.join(self.data_dir, "Holidays.ics"),
+#        ):
+#            data = file(filename).read()
+#
+#            calendar1 = Component.fromString(data)
+#            calendar2 = Component.fromString(data)
+#
+#            self.assertEqual(calendar1, calendar2)
+            
+        data1 = (
+            (
+                "1.1 Switch property order",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:COUNT=400;FREQ=DAILY
+EXDATE:20080602T120000Z
+EXDATE:20080603T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:COUNT=400;FREQ=DAILY
+EXDATE:20080603T120000Z
+EXDATE:20080602T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                True,
+            ),
+            (
+                "1.2 Switch component order",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:COUNT=400;FREQ=DAILY
+EXDATE:20080602T120000Z
+EXDATE:20080603T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:COUNT=400;FREQ=DAILY
+EXDATE:20080603T120000Z
+EXDATE:20080602T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                True,
+            ),
+            (
+                "1.3 Switch VALARM order",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:COUNT=400;FREQ=DAILY
+EXDATE:20080602T120000Z
+EXDATE:20080603T120000Z
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:COUNT=400;FREQ=DAILY
+EXDATE:20080603T120000Z
+EXDATE:20080602T120000Z
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""",
+                True,
+            ),
+        )
+        
+        for description, item1, item2, result in data1:
+            if "1.3" not in description:
+                continue
+            calendar1 = Component.fromString(item1)
+            calendar2 = Component.fromString(item2)
+            (self.assertEqual if result else self.assertNotEqual)(
+                calendar1, calendar2, "%s" % (description,)
+            )
 
-            calendar1 = Component.fromString(data)
-            calendar2 = Component.fromString(data)
-
-            self.assertEqual(calendar1, calendar2)
-
     def test_component_validate(self):
         """
         CalDAV resource validation.
@@ -186,13 +351,13 @@
 
         year = 2004
 
-        instances = calendar.expandTimeRanges(datetime.date(2100, 1, 1))
+        instances = calendar.expandTimeRanges(PyCalendarDateTime(2100, 1, 1))
         for key in instances:
             instance = instances[key]
             start = instance.start
             end = instance.end
-            self.assertEqual(start, datetime.datetime(year, 7, 4))
-            self.assertEqual(end  , datetime.datetime(year, 7, 5))
+            self.assertEqual(start, PyCalendarDateTime(year, 7, 4))
+            self.assertEqual(end  , PyCalendarDateTime(year, 7, 5))
             if year == 2050: break
             year += 1
 
@@ -211,14 +376,14 @@
         }
         year = 2004
 
-        instances = calendar.expandTimeRanges(datetime.date(2100, 1, 1))
+        instances = calendar.expandTimeRanges(PyCalendarDateTime(2100, 1, 1))
         for key in instances:
             instance = instances[key]
             start = instance.start
             end = instance.end
             if year in results:
-                self.assertEqual(start, datetime.datetime(year, results[year][0], results[year][1]))
-                self.assertEqual(end  , datetime.datetime(year, results[year][0], results[year][2]))
+                self.assertEqual(start, PyCalendarDateTime(year, results[year][0], results[year][1]))
+                self.assertEqual(end  , PyCalendarDateTime(year, results[year][0], results[year][2]))
             if year == 2050: break
             year += 1
 
@@ -237,14 +402,14 @@
         }
         year = 2002
 
-        instances = calendar.expandTimeRanges(datetime.date(2100, 1, 1))
+        instances = calendar.expandTimeRanges(PyCalendarDateTime(2100, 1, 1))
         for key in instances:
             instance = instances[key]
             start = instance.start
             end = instance.end
             if year in results:
-                self.assertEqual(start, datetime.datetime(year, results[year][0], results[year][1]))
-                self.assertEqual(end  , datetime.datetime(year, results[year][0], results[year][2]))
+                self.assertEqual(start, PyCalendarDateTime(year, results[year][0], results[year][1]))
+                self.assertEqual(end  , PyCalendarDateTime(year, results[year][0], results[year][2]))
             if year == 2050: break
             year += 1
 
@@ -256,13 +421,13 @@
         """
         calendar = Component.fromStream(file(os.path.join(self.data_dir, "Holidays", "C318ABFE-1ED0-11D9-A5E0-000A958A3252.ics")))
 
-        instances = calendar.expandTimeRanges(datetime.date(2100, 1, 1))
+        instances = calendar.expandTimeRanges(PyCalendarDateTime(2100, 1, 1))
         for key in instances:
             instance = instances[key]
             start = instance.start
             end = instance.end
-            self.assertEqual(start, datetime.datetime(2004, 11, 25))
-            self.assertEqual(end, datetime.datetime(2004, 11, 27))
+            self.assertEqual(start, PyCalendarDateTime(2004, 11, 25))
+            self.assertEqual(end, PyCalendarDateTime(2004, 11, 27))
             break;
 
     #test_component_timerange.todo = "recurrence expansion should give us no end date here"
@@ -271,49 +436,41 @@
         """
         parse_date()
         """
-        self.assertEqual(parse_date("19970714"), datetime.date(1997, 7, 14))
+        self.assertEqual(PyCalendarDateTime.parseText("19970714"), PyCalendarDateTime(1997, 7, 14))
 
     def test_parse_datetime(self):
         """
         parse_datetime()
         """
-        try: parse_datetime("19980119T2300")
-        except ValueError: pass
-        else: self.fail("Invalid DATE-TIME should raise ValueError")
+        dt = PyCalendarDateTime.parseText("19980118T230000")
+        self.assertEqual(dt, PyCalendarDateTime(1998, 1, 18, 23, 0, 0))
+        self.assertTrue(dt.floating())
 
-        dt = parse_datetime("19980118T230000")
-        self.assertEqual(dt, datetime.datetime(1998, 1, 18, 23, 0))
-        self.assertNot(dt.tzinfo)
+        dt = PyCalendarDateTime.parseText("19980119T070000Z")
+        self.assertEqual(dt, PyCalendarDateTime(1998, 1, 19, 7, 0, 0, tzid=PyCalendarTimezone(utc=True)))
 
-        dt = parse_datetime("19980119T070000Z")
-        self.assertEqual(dt, datetime.datetime(1998, 1, 19, 07, 0, tzinfo=utc))
-
     def test_parse_date_or_datetime(self):
         """
         parse_date_or_datetime()
         """
-        self.assertEqual(parse_date_or_datetime("19970714"), datetime.date(1997, 7, 14))
+        self.assertEqual(PyCalendarDateTime.parseText("19970714"), PyCalendarDateTime(1997, 7, 14))
 
-        try: parse_date_or_datetime("19980119T2300")
-        except ValueError: pass
-        else: self.fail("Invalid DATE-TIME should raise ValueError")
+        dt = PyCalendarDateTime.parseText("19980118T230000")
+        self.assertEqual(dt, PyCalendarDateTime(1998, 1, 18, 23, 0, 0))
+        self.assertTrue(dt.floating())
 
-        dt = parse_date_or_datetime("19980118T230000")
-        self.assertEqual(dt, datetime.datetime(1998, 1, 18, 23, 0))
-        self.assertNot(dt.tzinfo)
+        dt = PyCalendarDateTime.parseText("19980119T070000Z")
+        self.assertEqual(dt, PyCalendarDateTime(1998, 1, 19, 7, 0, 0, tzid=PyCalendarTimezone(utc=True)))
 
-        dt = parse_date_or_datetime("19980119T070000Z")
-        self.assertEqual(dt, datetime.datetime(1998, 1, 19, 07, 0, tzinfo=utc))
-
     def test_parse_duration(self):
         """
         parse_duration()
         """
-        self.assertEqual(parse_duration( "P15DT5H0M20S"), datetime.timedelta(days= 15, hours= 5, minutes=0, seconds= 20))
-        self.assertEqual(parse_duration("+P15DT5H0M20S"), datetime.timedelta(days= 15, hours= 5, minutes=0, seconds= 20))
-        self.assertEqual(parse_duration("-P15DT5H0M20S"), datetime.timedelta(days=-15, hours=-5, minutes=0, seconds=-20))
+        self.assertEqual(PyCalendarDuration.parseText( "P15DT5H0M20S"), PyCalendarDuration(days= 15, hours= 5, minutes=0, seconds= 20))
+        self.assertEqual(PyCalendarDuration.parseText("+P15DT5H0M20S"), PyCalendarDuration(days= 15, hours= 5, minutes=0, seconds= 20))
+        self.assertEqual(PyCalendarDuration.parseText("-P15DT5H0M20S"), PyCalendarDuration(days=-15, hours=-5, minutes=0, seconds=-20))
 
-        self.assertEqual(parse_duration("P7W"), datetime.timedelta(weeks=7))
+        self.assertEqual(PyCalendarDuration.parseText("P7W"), PyCalendarDuration(weeks=7))
 
     def test_correct_attendee_properties(self):
         
@@ -413,7 +570,7 @@
 """,
                 (
                     ("mailto:user1 at example.com", None),
-                    ("mailto:user1 at example.com", datetime.datetime(2008, 11, 14, 0, 0, tzinfo=tzutc()))
+                    ("mailto:user1 at example.com", PyCalendarDateTime(2008, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)))
                 )
             ),
             (
@@ -437,7 +594,7 @@
 """,
                 (
                     ("mailto:user1 at example.com", None),
-                    ("mailto:user3 at example.com", datetime.datetime(2009, 11, 14, 0, 0, tzinfo=tzutc()))
+                    ("mailto:user3 at example.com", PyCalendarDateTime(2009, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)))
                 )
             ),
             (
@@ -542,8 +699,8 @@
                 False,
                 (
                     ("mailto:user2 at example.com", None),
-                    ("mailto:user2 at example.com", datetime.datetime(2008, 11, 14, 0, 0, tzinfo=tzutc())),
-                    ("mailto:user3 at example.com", datetime.datetime(2008, 11, 14, 0, 0, tzinfo=tzutc()))
+                    ("mailto:user2 at example.com", PyCalendarDateTime(2008, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+                    ("mailto:user3 at example.com", PyCalendarDateTime(2008, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)))
                 )
             ),
             (
@@ -804,8 +961,8 @@
     def test_attendees_views(self):
         
         data = (
-            # Simple component, no Attendees - no filtering
             (
+                "1.1 Simple component, no Attendees - no filtering",
                 """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -828,8 +985,8 @@
                 ()
             ),
 
-            # Simple component, no Attendees - filtering
             (
+                "1.2 Simple component, no Attendees - filtering",
                 """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -848,8 +1005,8 @@
                 ("mailto:user01 at example.com",)
             ),
 
-            # Simple component, with one attendee - filtering match
             (
+                "1.3 Simple component, with one attendee - filtering match",
                 """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -876,8 +1033,8 @@
                 ("mailto:user2 at example.com",)
             ),
 
-            # Simple component, with one attendee - no filtering match
             (
+                "1.4 Simple component, with one attendee - no filtering match",
                 """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -898,8 +1055,8 @@
                 ("mailto:user3 at example.com",)
             ),
 
-            # Recurring component with one instance, each with one attendee - filtering match
             (
+                "2.1 Recurring component with one instance, each with one attendee - filtering match",
                 """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -942,8 +1099,8 @@
                 ("mailto:user2 at example.com",)
             ),
 
-            # Recurring component with one instance, each with one attendee - no filtering match
             (
+                "2.2 Recurring component with one instance, each with one attendee - no filtering match",
                 """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -972,8 +1129,8 @@
                 ("mailto:user3 at example.com",)
             ),        
 
-            # Recurring component with one instance, master with one attendee, instance without attendee - filtering match
             (
+                "2.3 Recurring component with one instance, master with one attendee, instance without attendee - filtering match",
                 """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -1009,8 +1166,8 @@
                 ("mailto:user2 at example.com",)
             ),
 
-            # Recurring component with one instance, master with one attendee, instance without attendee - no filtering match
             (
+                "2.4 Recurring component with one instance, master with one attendee, instance without attendee - no filtering match",
                 """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -1038,8 +1195,8 @@
                 ("mailto:user3 at example.com",)
             ),
 
-            # Recurring component with one instance, master without attendee, instance with attendee - filtering match
             (
+                "2.5 Recurring component with one instance, master without attendee, instance with attendee - filtering match",
                 """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -1074,8 +1231,8 @@
                 ("mailto:user2 at example.com",)
             ),
 
-            # Recurring component with one instance, master without attendee, instance with attendee - no filtering match
             (
+                "2.6 Recurring component with one instance, master without attendee, instance with attendee - no filtering match",
                 """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -1103,8 +1260,8 @@
                 ("mailto:user3 at example.com",)
             ),
 
-                    # Simple component, no Attendees - no filtering
             (
+                "3.1 Simple component, no Attendees - no filtering",
                 """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -1127,8 +1284,8 @@
                 ()
             ),
 
-            # Simple component, no Attendees - filtering
             (
+                "3.2 Simple component, no Attendees - filtering",
                 """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -1147,8 +1304,8 @@
                 ("mailto:user01 at example.com",)
             ),
 
-            # Simple component, with one attendee - filtering match
             (
+                "3.3 Simple component, with one attendee - filtering match",
                 """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -1175,8 +1332,8 @@
                 ("mailto:user2 at example.com",)
             ),
 
-            # Simple component, with one attendee - filtering match
             (
+                "3.4 Simple component, with one attendee - filtering match",
                 """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -1203,8 +1360,8 @@
                 ("mailto:user2 at example.com",)
             ),
 
-            # Simple component, with one attendee - filtering match - no schedule-agent match
             (
+                "3.5 Simple component, with one attendee - filtering match - no schedule-agent match",
                 """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -1225,8 +1382,8 @@
                 ("mailto:user2 at example.com",)
             ),
 
-            # Simple component, with one attendee - filtering match - no schedule-agent match
             (
+                "3.6 Simple component, with one attendee - filtering match - no schedule-agent match",
                 """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//PYVOBJECT//NONSGML Version 1//EN
@@ -1249,10 +1406,10 @@
 
         )
         
-        for original, checkScheduleAgent, filtered, attendees in data:
+        for description, original, checkScheduleAgent, filtered, attendees in data:
             component = Component.fromString(original)
             component.attendeesView(attendees, onlyScheduleAgentServer=checkScheduleAgent)
-            self.assertEqual(filtered, str(component).replace("\r", ""))
+            self.assertEqual(filtered, str(component).replace("\r", ""), "Failed: %s" % (description,))
 
     def test_all_but_one_attendee(self):
         
@@ -1746,12 +1903,12 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 END:VEVENT
 END:VCALENDAR
 """,
                 False,
-                (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()),)
+                (PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),)
             ),
             (
                 "Simple recurring",
@@ -1761,15 +1918,15 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY;COUNT=2
 END:VEVENT
 END:VCALENDAR
 """,
                 False,
                 (
-                    datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()),
-                    datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()),
+                    PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 )
             ),
             (
@@ -1780,7 +1937,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY;COUNT=2
 RDATE:20071116T010000Z
 END:VEVENT
@@ -1788,9 +1945,9 @@
 """,
                 False,
                 (
-                    datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()),
-                    datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()),
-                    datetime.datetime(2007, 11, 16, 1, 0, 0, tzinfo=tzutc()),
+                    PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    PyCalendarDateTime(2007, 11, 16, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 )
             ),
             (
@@ -1801,7 +1958,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY;COUNT=3
 EXDATE:20071115T000000Z
 END:VEVENT
@@ -1809,8 +1966,8 @@
 """,
                 False,
                 (
-                    datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()),
-                    datetime.datetime(2007, 11, 16, 0, 0, 0, tzinfo=tzutc()),
+                    PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    PyCalendarDateTime(2007, 11, 16, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 )
             ),
             (
@@ -1821,7 +1978,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY;COUNT=3
 EXDATE:20071114T000000Z
 END:VEVENT
@@ -1829,8 +1986,8 @@
 """,
                 False,
                 (
-                    datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()),
-                    datetime.datetime(2007, 11, 16, 0, 0, 0, tzinfo=tzutc()),
+                    PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    PyCalendarDateTime(2007, 11, 16, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 )
             ),
             (
@@ -1841,21 +1998,21 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY;COUNT=2
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890-1
 RECURRENCE-ID:20071115T000000Z
 DTSTART:20071115T010000Z
-DURATION:P1H
+DURATION:PT1H
 END:VEVENT
 END:VCALENDAR
 """,
                 False,
                 (
-                    datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()),
-                    datetime.datetime(2007, 11, 15, 1, 0, 0, tzinfo=tzutc()),
+                    PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    PyCalendarDateTime(2007, 11, 15, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 )
             ),
             (
@@ -1866,14 +2023,14 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY;COUNT=2
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890-1
 RECURRENCE-ID:20071115T010000Z
 DTSTART:20071115T000000Z
-DURATION:P1H
+DURATION:PT1H
 END:VEVENT
 END:VCALENDAR
 """,
@@ -1888,21 +2045,21 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY;COUNT=2
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890-1
 RECURRENCE-ID:20071115T010000Z
 DTSTART:20071115T000000Z
-DURATION:P1H
+DURATION:PT1H
 END:VEVENT
 END:VCALENDAR
 """,
                 True,
                 (
-                    datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()),
-                    datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()),
+                    PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 )
             ),
         )
@@ -1910,9 +2067,9 @@
         for description, original, ignoreInvalidInstances, results in data:
             component = Component.fromString(original)
             if results is None:
-                self.assertRaises(InvalidOverriddenInstanceError, component.expandTimeRanges, datetime.date(2100, 1, 1), ignoreInvalidInstances)
+                self.assertRaises(InvalidOverriddenInstanceError, component.expandTimeRanges, PyCalendarDateTime(2100, 1, 1), ignoreInvalidInstances)
             else:
-                instances = component.expandTimeRanges(datetime.date(2100, 1, 1), ignoreInvalidInstances)
+                instances = component.expandTimeRanges(PyCalendarDateTime(2100, 1, 1), ignoreInvalidInstances)
                 self.assertTrue(len(instances.instances) == len(results), "%s: wrong number of instances" % (description,))
                 for instance in instances:
                     self.assertTrue(instances[instance].start in results, "%s: %s missing" % (description, instance,))
@@ -1928,7 +2085,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 END:VEVENT
 END:VCALENDAR
 """,
@@ -1943,7 +2100,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 END:VEVENT
 END:VCALENDAR
 """,
@@ -1958,14 +2115,14 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890-1
 RECURRENCE-ID:20071115T000000Z
 DTSTART:20071115T010000Z
-DURATION:P1H
+DURATION:PT1H
 END:VEVENT
 END:VCALENDAR
 """,
@@ -1980,14 +2137,14 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890-1
 RECURRENCE-ID:20071115T000000Z
 DTSTART:20071115T010000Z
-DURATION:P1H
+DURATION:PT1H
 END:VEVENT
 END:VCALENDAR
 """,
@@ -2002,14 +2159,14 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890-1
 RECURRENCE-ID:20071115T000000Z
 DTSTART:20071115T010000Z
-DURATION:P1H
+DURATION:PT1H
 END:VEVENT
 END:VCALENDAR
 """,
@@ -2024,14 +2181,14 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890-1
 RECURRENCE-ID:20071115T000000Z
 DTSTART:20071115T010000Z
-DURATION:P1H
+DURATION:PT1H
 END:VEVENT
 END:VCALENDAR
 """,
@@ -2046,14 +2203,14 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890-1
 RECURRENCE-ID:20071115T000000Z
 DTSTART:20071115T010000Z
-DURATION:P1H
+DURATION:PT1H
 END:VEVENT
 END:VCALENDAR
 """,
@@ -2068,14 +2225,14 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890-1
 RECURRENCE-ID:20071115T000000Z
 DTSTART:20071115T010000Z
-DURATION:P1H
+DURATION:PT1H
 END:VEVENT
 END:VCALENDAR
 """,
@@ -2099,7 +2256,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 X-ITEM1:True
 END:VEVENT
 END:VCALENDAR
@@ -2110,7 +2267,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 X-ITEM2:True
 END:VEVENT
 END:VCALENDAR
@@ -2121,7 +2278,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 X-ITEM1:True
 X-ITEM2:True
 END:VEVENT
@@ -2137,7 +2294,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 X-ITEM1:True
 END:VEVENT
 END:VCALENDAR
@@ -2148,7 +2305,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 X-ITEM2:True
 X-ITEM3:True
 END:VEVENT
@@ -2160,7 +2317,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 X-ITEM1:True
 X-ITEM2:True
 X-ITEM3:True
@@ -2177,7 +2334,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 X-ITEM1:True
 END:VEVENT
 END:VCALENDAR
@@ -2188,7 +2345,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 X-ITEM2:True
 X-ITEM1:False
 END:VEVENT
@@ -2200,7 +2357,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 X-ITEM1:True
 X-ITEM2:True
 X-ITEM1:False
@@ -2217,7 +2374,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY
 X-ITEM1:True
 END:VEVENT
@@ -2225,7 +2382,7 @@
 UID:12345-67890-1
 RECURRENCE-ID:20071115T000000Z
 DTSTART:20071115T010000Z
-DURATION:P1H
+DURATION:PT1H
 X-ITEM1:False
 END:VEVENT
 END:VCALENDAR
@@ -2236,7 +2393,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY
 X-ITEM2:True
 END:VEVENT
@@ -2244,7 +2401,7 @@
 UID:12345-67890-1
 RECURRENCE-ID:20071115T000000Z
 DTSTART:20071115T010000Z
-DURATION:P1H
+DURATION:PT1H
 X-ITEM2:False
 END:VEVENT
 END:VCALENDAR
@@ -2255,7 +2412,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY
 X-ITEM1:True
 X-ITEM2:True
@@ -2264,7 +2421,7 @@
 UID:12345-67890-1
 RECURRENCE-ID:20071115T000000Z
 DTSTART:20071115T010000Z
-DURATION:P1H
+DURATION:PT1H
 X-ITEM1:False
 X-ITEM2:False
 END:VEVENT
@@ -2280,7 +2437,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY
 X-ITEM1:True
 END:VEVENT
@@ -2288,7 +2445,7 @@
 UID:12345-67890-1
 RECURRENCE-ID:20071115T000000Z
 DTSTART:20071115T010000Z
-DURATION:P1H
+DURATION:PT1H
 X-ITEM1:False
 END:VEVENT
 END:VCALENDAR
@@ -2299,7 +2456,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY
 X-ITEM2:True
 END:VEVENT
@@ -2311,7 +2468,7 @@
 BEGIN:VEVENT
 UID:12345-67890-1
 DTSTART:20071114T000000Z
-DURATION:P1H
+DURATION:PT1H
 RRULE:FREQ=DAILY
 X-ITEM1:True
 X-ITEM2:True
@@ -2320,7 +2477,7 @@
 UID:12345-67890-1
 RECURRENCE-ID:20071115T000000Z
 DTSTART:20071115T010000Z
-DURATION:P1H
+DURATION:PT1H
 X-ITEM1:False
 X-ITEM2:True
 END:VEVENT
@@ -2428,6 +2585,74 @@
 END:VCALENDAR
 """,
             ),
+            (
+                "1.4",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=US/Pacific:20071114T000000
+RRULE:FREQ=WEEKLY;WKST=SU;INTERVAL=1;BYDAY=MO,WE,FR
+TRANSP:OPAQUE
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user02 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user03 at example.com
+ATTENDEE;RSVP=FALSE:mailto:user04 at example.com
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;_TZID=US/Pacific:20071114T080000Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE;RSVP=TRUE:mailto:user02 at example.com
+ATTENDEE:mailto:user03 at example.com
+ATTENDEE:mailto:user04 at example.com
+RRULE:BYDAY=MO,WE,FR;FREQ=WEEKLY;INTERVAL=1;WKST=SU
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
         )
         
         for title, original, result in data:
@@ -2631,7 +2856,7 @@
 END:VEVENT
 END:VCALENDAR
 """,
-                datetime.datetime(2009, 1, 2, 8, 0, 0, tzinfo=tzutc()),
+                PyCalendarDateTime(2009, 1, 2, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 """BEGIN:VEVENT
 UID:12345-67890-1
 RECURRENCE-ID:20090102T080000Z
@@ -2654,7 +2879,7 @@
 END:VEVENT
 END:VCALENDAR
 """,
-                datetime.datetime(2009, 1, 2, 18, 0, 0, tzinfo=tzutc()),
+                PyCalendarDateTime(2009, 1, 2, 18, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 """BEGIN:VEVENT
 UID:12345-67890-1
 RECURRENCE-ID:20090102T180000Z
@@ -2678,7 +2903,7 @@
 END:VEVENT
 END:VCALENDAR
 """,
-                datetime.datetime(2009, 1, 3, 18, 0, 0, tzinfo=tzutc()),
+                PyCalendarDateTime(2009, 1, 3, 18, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 """BEGIN:VEVENT
 UID:12345-67890-1
 RECURRENCE-ID:20090103T180000Z
@@ -2700,7 +2925,7 @@
 END:VEVENT
 END:VCALENDAR
 """,
-                datetime.datetime(2009, 1, 2, 9, 0, 0, tzinfo=tzutc()),
+                PyCalendarDateTime(2009, 1, 2, 9, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 None,
             ),
             (
@@ -2717,7 +2942,7 @@
 END:VEVENT
 END:VCALENDAR
 """,
-                datetime.datetime(2009, 1, 2, 19, 0, 0, tzinfo=tzutc()),
+                PyCalendarDateTime(2009, 1, 2, 19, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 None,
             ),
             (
@@ -2735,7 +2960,7 @@
 END:VEVENT
 END:VCALENDAR
 """,
-                datetime.datetime(2009, 1, 3, 19, 0, 0, tzinfo=tzutc()),
+                PyCalendarDateTime(2009, 1, 3, 19, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 None,
             ),
             (
@@ -2751,7 +2976,7 @@
 END:VEVENT
 END:VCALENDAR
 """,
-                datetime.date(2009, 1, 8),
+                PyCalendarDateTime(2009, 1, 8),
                 """BEGIN:VEVENT
 UID:12345-67890-1
 RECURRENCE-ID;VALUE=DATE:20090108
@@ -2774,7 +2999,7 @@
 END:VEVENT
 END:VCALENDAR
 """,
-                datetime.date(2009, 1, 3),
+                PyCalendarDateTime(2009, 1, 3),
                 """BEGIN:VEVENT
 UID:12345-67890-1
 RECURRENCE-ID;VALUE=DATE:20090103
@@ -2798,7 +3023,7 @@
 END:VEVENT
 END:VCALENDAR
 """,
-                datetime.date(2009, 1, 10),
+                PyCalendarDateTime(2009, 1, 10),
                 """BEGIN:VEVENT
 UID:12345-67890-1
 RECURRENCE-ID;VALUE=DATE:20090110
@@ -2820,7 +3045,7 @@
 END:VEVENT
 END:VCALENDAR
 """,
-                datetime.date(2009, 1, 3),
+                PyCalendarDateTime(2009, 1, 3),
                 None,
             ),
             (
@@ -2837,7 +3062,7 @@
 END:VEVENT
 END:VCALENDAR
 """,
-                datetime.date(2009, 1, 5),
+                PyCalendarDateTime(2009, 1, 5),
                 None,
             ),
             (
@@ -2855,14 +3080,12 @@
 END:VEVENT
 END:VCALENDAR
 """,
-                datetime.datetime(2009, 1, 19),
+                PyCalendarDateTime(2009, 1, 19),
                 None,
             ),
         )
         
         for title, calendar, rid, result in data:
-            if not title.startswith("3"):
-                continue
             ical = Component.fromString(calendar)
             derived = ical.deriveInstance(rid)
             derived = str(derived).replace("\r", "") if derived else None
@@ -2885,8 +3108,8 @@
 END:VCALENDAR
 """,
                 (
-                    datetime.datetime(2009, 1, 2, 8, 0, 0, tzinfo=tzutc()),
-                    datetime.datetime(2009, 1, 4, 8, 0, 0, tzinfo=tzutc()),
+                    PyCalendarDateTime(2009, 1, 2, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    PyCalendarDateTime(2009, 1, 4, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 ),
                 (
                     """BEGIN:VEVENT
@@ -2920,8 +3143,8 @@
 END:VCALENDAR
 """,
                 (
-                    datetime.datetime(2009, 1, 2, 18, 0, 0, tzinfo=tzutc()),
-                    datetime.datetime(2009, 1, 4, 8, 0, 0, tzinfo=tzutc()),
+                    PyCalendarDateTime(2009, 1, 2, 18, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    PyCalendarDateTime(2009, 1, 4, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 ),
                 (
                     """BEGIN:VEVENT
@@ -2956,8 +3179,8 @@
 END:VCALENDAR
 """,
                 (
-                    datetime.datetime(2009, 1, 3, 18, 0, 0, tzinfo=tzutc()),
-                    datetime.datetime(2009, 1, 5, 8, 0, 0, tzinfo=tzutc()),
+                    PyCalendarDateTime(2009, 1, 3, 18, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    PyCalendarDateTime(2009, 1, 5, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 ),
                 (
                     """BEGIN:VEVENT
@@ -2990,8 +3213,8 @@
 END:VCALENDAR
 """,
                 (
-                    datetime.datetime(2009, 1, 2, 9, 0, 0, tzinfo=tzutc()),
-                    datetime.datetime(2009, 1, 3, 8, 0, 0, tzinfo=tzutc()),
+                    PyCalendarDateTime(2009, 1, 2, 9, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    PyCalendarDateTime(2009, 1, 3, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 ),
                 (
                     None,
@@ -3019,8 +3242,8 @@
 END:VCALENDAR
 """,
                 (
-                    datetime.datetime(2009, 1, 2, 19, 0, 0, tzinfo=tzutc()),
-                    datetime.datetime(2009, 1, 3, 8, 0, 0, tzinfo=tzutc()),
+                    PyCalendarDateTime(2009, 1, 2, 19, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    PyCalendarDateTime(2009, 1, 3, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 ),
                 (
                     None,
@@ -3049,8 +3272,8 @@
 END:VCALENDAR
 """,
                 (
-                    datetime.datetime(2009, 1, 3, 19, 0, 0, tzinfo=tzutc()),
-                    datetime.datetime(2009, 1, 3, 8, 0, 0, tzinfo=tzutc()),
+                    PyCalendarDateTime(2009, 1, 3, 19, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    PyCalendarDateTime(2009, 1, 3, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                 ),
                 (
                     None,
@@ -3234,8 +3457,8 @@
 """,
                 (
                     (None, True),
-                    (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2009, 10, 4, 0, 0, 0, tzinfo=tzutc()), False),
+                    (PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2009, 10, 4, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), False),
                 )
             ),
             (
@@ -3252,9 +3475,9 @@
 """,
                 (
                     (None, True),
-                    (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2009, 10, 4, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2009, 10, 5, 0, 0, 0, tzinfo=tzutc()), False),
+                    (PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2009, 10, 4, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2009, 10, 5, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), False),
                 )
             ),
             (
@@ -3271,10 +3494,10 @@
 """,
                 (
                     (None, True),
-                    (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2009, 10, 4, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2009, 10, 4, 1, 0, 0, tzinfo=tzutc()), False),
+                    (PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2009, 10, 4, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2009, 10, 4, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)), False),
                 )
             ),
             (
@@ -3292,11 +3515,11 @@
 """,
                 (
                     (None, True),
-                    (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2009, 10, 4, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2009, 10, 4, 1, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2009, 10, 4, 2, 0, 0, tzinfo=tzutc()), False),
+                    (PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2009, 10, 4, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2009, 10, 4, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2009, 10, 4, 2, 0, 0, tzid=PyCalendarTimezone(utc=True)), False),
                 )
             ),
             (
@@ -3315,12 +3538,12 @@
 """,
                 (
                     (None, True),
-                    (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2009, 10, 4, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2009, 10, 4, 1, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2009, 10, 4, 2, 0, 0, tzinfo=tzutc()), False),
-                    (datetime.datetime(2009, 10, 3, 0, 0, 0, tzinfo=tzutc()), False),
+                    (PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2009, 10, 4, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2009, 10, 4, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2009, 10, 4, 2, 0, 0, tzid=PyCalendarTimezone(utc=True)), False),
+                    (PyCalendarDateTime(2009, 10, 3, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), False),
                 )
             ),
             (
@@ -3342,11 +3565,11 @@
 """,
                 (
                     (None, True),
-                    (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2007, 11, 15, 1, 0, 0, tzinfo=tzutc()), False),
-                    (datetime.datetime(2009, 10, 4, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2009, 10, 4, 1, 0, 0, tzinfo=tzutc()), False),
+                    (PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2007, 11, 15, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)), False),
+                    (PyCalendarDateTime(2009, 10, 4, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2009, 10, 4, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)), False),
                 )
             ),
             (
@@ -3369,12 +3592,12 @@
 """,
                 (
                     (None, True),
-                    (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2007, 11, 15, 1, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2007, 11, 15, 2, 0, 0, tzinfo=tzutc()), False),
-                    (datetime.datetime(2009, 10, 4, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2009, 10, 4, 1, 0, 0, tzinfo=tzutc()), False),
+                    (PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2007, 11, 15, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2007, 11, 15, 2, 0, 0, tzid=PyCalendarTimezone(utc=True)), False),
+                    (PyCalendarDateTime(2009, 10, 4, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2009, 10, 4, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)), False),
                 )
             ),
             (
@@ -3391,9 +3614,9 @@
 """,
                 (
                     (None, False),
-                    (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), False),
-                    (datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()), True),
-                    (datetime.datetime(2009, 10, 4, 0, 0, 0, tzinfo=tzutc()), False),
+                    (PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), False),
+                    (PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
+                    (PyCalendarDateTime(2009, 10, 4, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), False),
                 )
             ),
             (
@@ -3423,7 +3646,7 @@
 END:VCALENDAR
 """,
                 (
-                    (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), True),
+                    (PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), True),
                 )
             ),
             (
@@ -3438,7 +3661,7 @@
 END:VCALENDAR
 """,
                 (
-                    (datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()), False),
+                    (PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), False),
                 )
             ),
         )
@@ -3727,7 +3950,7 @@
                                 ),
                             ),
                             (
-                                datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+                                PyCalendarDateTime(2008, 6, 2, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                                 (
                                     ("", False,),
                                 ),
@@ -3771,7 +3994,7 @@
                                 ),
                             ),
                             (
-                                datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+                                PyCalendarDateTime(2008, 6, 2, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                                 (
                                     ("", False,),
                                     ("user01", False,),
@@ -3824,7 +4047,7 @@
                                 ),
                             ),
                             (
-                                datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+                                PyCalendarDateTime(2008, 6, 2, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                                 (
                                     ("", False,),
                                     ("user01", False,),
@@ -3868,19 +4091,19 @@
                                 ),
                             ),
                             (
-                                datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+                                PyCalendarDateTime(2008, 6, 2, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                                 (
                                     ("", False,),
                                 ),
                             ),
                             (
-                                datetime.datetime(2008, 6, 3, 12, 0, 0, tzinfo=tzutc()),
+                                PyCalendarDateTime(2008, 6, 3, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                                 (
                                     ("", True,),
                                 ),
                             ),
                             (
-                                datetime.datetime(2008, 6, 4, 12, 0, 0, tzinfo=tzutc()),
+                                PyCalendarDateTime(2008, 6, 4, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                                 (
                                     ("", True,),
                                 ),
@@ -3941,21 +4164,21 @@
                                 ),
                             ),
                             (
-                                datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+                                PyCalendarDateTime(2008, 6, 2, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                                 (
                                     ("", False,),
                                     ("user01", True,),
                                 ),
                             ),
                             (
-                                datetime.datetime(2008, 6, 3, 12, 0, 0, tzinfo=tzutc()),
+                                PyCalendarDateTime(2008, 6, 3, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                                 (
                                     ("", False,),
                                     ("user01", True,),
                                 ),
                             ),
                             (
-                                datetime.datetime(2008, 6, 4, 12, 0, 0, tzinfo=tzutc()),
+                                PyCalendarDateTime(2008, 6, 4, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                                 (
                                     ("", False,),
                                     ("user01", False,),
@@ -4038,7 +4261,7 @@
                                 ),
                             ),
                             (
-                                datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+                                PyCalendarDateTime(2008, 6, 2, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                                 (
                                     ("", False,),
                                     ("user01", True,),
@@ -4046,7 +4269,7 @@
                                 ),
                             ),
                             (
-                                datetime.datetime(2008, 6, 3, 12, 0, 0, tzinfo=tzutc()),
+                                PyCalendarDateTime(2008, 6, 3, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                                 (
                                     ("", False,),
                                     ("user01", True,),
@@ -4054,7 +4277,7 @@
                                 ),
                             ),
                             (
-                                datetime.datetime(2008, 6, 4, 12, 0, 0, tzinfo=tzutc()),
+                                PyCalendarDateTime(2008, 6, 4, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
                                 (
                                     ("", False,),
                                     ("user01", False,),

Modified: CalendarServer/trunk/twistedcaldav/test/test_localization.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_localization.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/test/test_localization.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -19,7 +19,8 @@
 from twistedcaldav.localization import translationTo
 from twistedcaldav.ical import Component
 from twistedcaldav.test.util import TestCase
-from datetime import time
+from pycalendar.datetime import PyCalendarDateTime
+
 import os
 
 def getComp(str):
@@ -64,21 +65,21 @@
 
         with translationTo('English', localeDir=localeDir) as t:
 
-            self.assertEquals(t.dtTime(time(0,0)), "12:00 AM")
-            self.assertEquals(t.dtTime(time(12,0)), "12:00 PM")
-            self.assertEquals(t.dtTime(time(23,59)), "11:59 PM")
-            self.assertEquals(t.dtTime(time(6,5)), "6:05 AM")
-            self.assertEquals(t.dtTime(time(16,5)), "4:05 PM")
+            self.assertEquals(t.dtTime(PyCalendarDateTime(2000, 1, 1,  0,  0, 0)), "12:00 AM")
+            self.assertEquals(t.dtTime(PyCalendarDateTime(2000, 1, 1, 12,  0, 0)), "12:00 PM")
+            self.assertEquals(t.dtTime(PyCalendarDateTime(2000, 1, 1, 23, 59, 0)), "11:59 PM")
+            self.assertEquals(t.dtTime(PyCalendarDateTime(2000, 1, 1,  6,  5, 0)), "6:05 AM")
+            self.assertEquals(t.dtTime(PyCalendarDateTime(2000, 1, 1, 16,  5, 0)), "4:05 PM")
 
     def test_TimeFormatting24Hour(self):
 
         with translationTo('pig', localeDir=localeDir) as t:
 
-            self.assertEquals(t.dtTime(time(0,0)), "00:00")
-            self.assertEquals(t.dtTime(time(12,0)), "12:00")
-            self.assertEquals(t.dtTime(time(23,59)), "23:59")
-            self.assertEquals(t.dtTime(time(6,5)), "06:05")
-            self.assertEquals(t.dtTime(time(16,5)), "16:05")
+            self.assertEquals(t.dtTime(PyCalendarDateTime(2000, 1, 1,  0,  0, 0)), "00:00")
+            self.assertEquals(t.dtTime(PyCalendarDateTime(2000, 1, 1, 12,  0, 0)), "12:00")
+            self.assertEquals(t.dtTime(PyCalendarDateTime(2000, 1, 1, 23, 59, 0)), "23:59")
+            self.assertEquals(t.dtTime(PyCalendarDateTime(2000, 1, 1,  6,  5, 0)), "06:05")
+            self.assertEquals(t.dtTime(PyCalendarDateTime(2000, 1, 1, 16,  5, 0)), "16:05")
 
     def test_CalendarFormatting(self):
 
@@ -87,15 +88,15 @@
             comp = data[0][1]
             self.assertEquals(t.date(comp), "Saturday, October 25, 2008")
             self.assertEquals(t.time(comp),
-                (u'9:15 AM to 10:15 AM PDT', u'1 hour 1 second'))
+                (u'9:15 AM to 10:15 AM (PDT)', u'1 hour 1 second'))
 
             comp = data[1][1]
             self.assertEquals(t.time(comp),
-                (u'1:15 PM to 3:15 PM PDT', u'2 hours 2 seconds'))
+                (u'1:15 PM to 3:15 PM (PDT)', u'2 hours 2 seconds'))
 
             comp = data[2][1]
             self.assertEquals(t.time(comp),
-                (u'11:05 AM to 2:15 PM PDT', u'3 hours 10 minutes'))
+                (u'11:05 AM to 2:15 PM (PDT)', u'3 hours 10 minutes'))
 
             comp = data[3][1]
             self.assertEquals(t.time(comp),
@@ -103,11 +104,11 @@
 
             comp = data[4][1]
             self.assertEquals(t.time(comp),
-                (u'1:15 PM PDT', ""))
+                (u'1:15 PM (PDT)', ""))
 
             comp = data[5][1]
             self.assertEquals(t.time(comp),
-                (u'11:05 AM PDT to 6:15 PM EDT', u'4 hours 10 minutes'))
+                (u'11:05 AM (PDT) to 6:15 PM (EDT)', u'4 hours 10 minutes'))
 
             self.assertEquals(t.monthAbbreviation(1), "JAN")
 
@@ -116,15 +117,15 @@
             comp = data[0][1]
             self.assertEquals(t.date(comp), 'Aturdaysay, Octoberway 25, 2008')
             self.assertEquals(t.time(comp),
-                (u'09:15 otay 10:15 PDT', u'1 ourhay 1 econdsay'))
+                (u'09:15 otay 10:15 (PDT)', u'1 ourhay 1 econdsay'))
 
             comp = data[1][1]
             self.assertEquals(t.time(comp),
-                (u'13:15 otay 15:15 PDT', u'2 ourshay 2 econdsay'))
+                (u'13:15 otay 15:15 (PDT)', u'2 ourshay 2 econdsay'))
 
             comp = data[2][1]
             self.assertEquals(t.time(comp),
-                (u'11:05 otay 14:15 PDT', u'3 ourshay 10 inutesmay'))
+                (u'11:05 otay 14:15 (PDT)', u'3 ourshay 10 inutesmay'))
 
             comp = data[3][1]
             self.assertEquals(t.time(comp),
@@ -132,10 +133,10 @@
 
             comp = data[4][1]
             self.assertEquals(t.time(comp),
-                (u'13:15 PDT', ""))
+                (u'13:15 (PDT)', ""))
 
             comp = data[5][1]
             self.assertEquals(t.time(comp),
-                (u'11:05 PDT otay 18:15 EDT', u'4 ourshay 10 inutesmay'))
+                (u'11:05 (PDT) otay 18:15 (EDT)', u'4 ourshay 10 inutesmay'))
 
             self.assertEquals(t.monthAbbreviation(1), "ANJAY")

Modified: CalendarServer/trunk/twistedcaldav/test/test_mail.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_mail.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/test/test_mail.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -65,7 +65,45 @@
 
     def test_processDSN(self):
 
-        template = 'BEGIN:VCALENDAR\nVERSION:2.0\nCALSCALE:GREGORIAN\nMETHOD:REQUEST\nPRODID:-//example Inc.//iCal 3.0//EN\nBEGIN:VTIMEZONE\nTZID:US/Pacific\nBEGIN:STANDARD\nDTSTART:20071104T020000\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\nTZNAME:PST\nTZOFFSETFROM:-0700\nTZOFFSETTO:-0800\nEND:STANDARD\nBEGIN:DAYLIGHT\nDTSTART:20070311T020000\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\nTZNAME:PDT\nTZOFFSETFROM:-0800\nTZOFFSETTO:-0700\nEND:DAYLIGHT\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C\nDTSTART;TZID=US/Pacific:20080812T094500\nDTEND;TZID=US/Pacific:20080812T104500\nATTENDEE;CUTYPE=INDIVIDUAL;CN=User 01;PARTSTAT=ACCEPTED:mailto:user01 at exam\n ple.com\nATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-A\n CTION;CN=nonexistant at example.com:mailto:nonexistant at example.com\nCREATED:20080812T191857Z\nDTSTAMP:20080812T191932Z\nORGANIZER;CN=User 01:mailto:xyzzy+%s at example.com\nSEQUENCE:2\nSUMMARY:New Event\nTRANSP:OPAQUE\nEND:VEVENT\nEND:VCALENDAR\n'
+        template = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+PRODID:-//example Inc.//iCal 3.0//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C
+DTSTART;TZID=US/Pacific:20080812T094500
+DTEND;TZID=US/Pacific:20080812T104500
+ATTENDEE;CUTYPE=INDIVIDUAL;CN=User 01;PARTSTAT=ACCEPTED:mailto:user01 at exam
+ ple.com
+ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-A
+ CTION;CN=nonexistant at example.com:mailto:nonexistant at example.com
+CREATED:20080812T191857Z
+DTSTAMP:20080812T191932Z
+ORGANIZER;CN=User 01:mailto:xyzzy+%s at example.com
+SEQUENCE:2
+SUMMARY:New Event
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+"""
 
         # Make sure an unknown token is not processed
         calBody = template % "bogus_token"
@@ -80,7 +118,42 @@
             "xyzzy", echo)
         self.assertEquals(organizer, 'mailto:user01 at example.com')
         self.assertEquals(attendee, 'mailto:user02 at example.com')
-        self.assertEquals(str(calendar), 'BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nMETHOD:REQUEST\r\nPRODID:-//example Inc.//iCal 3.0//EN\r\nBEGIN:VTIMEZONE\r\nTZID:US/Pacific\r\nBEGIN:STANDARD\r\nDTSTART:20071104T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nTZNAME:PST\r\nTZOFFSETFROM:-0700\r\nTZOFFSETTO:-0800\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nDTSTART:20070311T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nTZNAME:PDT\r\nTZOFFSETFROM:-0800\r\nTZOFFSETTO:-0700\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C\r\nDTSTART;TZID=US/Pacific:20080812T094500\r\nDTEND;TZID=US/Pacific:20080812T104500\r\nCREATED:20080812T191857Z\r\nDTSTAMP:20080812T191932Z\r\nORGANIZER;CN=User 01:mailto:user01 at example.com\r\nREQUEST-STATUS:5.1;Service unavailable\r\nSEQUENCE:2\r\nSUMMARY:New Event\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n')
+        self.assertEquals(str(calendar), """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+PRODID:-//example Inc.//iCal 3.0//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:1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C
+DTSTART;TZID=US/Pacific:20080812T094500
+DTEND;TZID=US/Pacific:20080812T104500
+CREATED:20080812T191857Z
+DTSTAMP:20080812T191932Z
+ORGANIZER;CN=User 01:mailto:user01 at example.com
+REQUEST-STATUS:5.1;Service unavailable
+SEQUENCE:2
+SUMMARY:New Event
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"))
         self.assertEquals(msgId, 'xyzzy')
 
 
@@ -129,8 +202,7 @@
         # have added an attendee back in with a "5.1;Service unavailable"
         # schedule-status
         attendeeProp = calendar.mainComponent().getAttendeeProperty([attendee])
-        self.assertEquals(attendeeProp.paramValue("SCHEDULE-STATUS"),
-            iTIPRequestStatus.SERVICE_UNAVAILABLE)
+        self.assertEquals(attendeeProp.parameterValue("SCHEDULE-STATUS"), iTIPRequestStatus.SERVICE_UNAVAILABLE)
 
 
     @inlineCallbacks

Modified: CalendarServer/trunk/twistedcaldav/test/test_multiget.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_multiget.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/test/test_multiget.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -214,7 +214,7 @@
                             self.fail("Got calendar for unexpected UID %r" % (uid,))
 
                         if data:
-                            original_calendar = ical.Component.fromStream(data[uid])
+                            original_calendar = ical.Component.fromString(data[uid])
                         else:
                             original_filename = file(os.path.join(self.holidays_dir, uid + ".ics"))
                             original_calendar = ical.Component.fromStream(original_filename)

Modified: CalendarServer/trunk/twistedcaldav/test/test_timezones.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_timezones.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/test/test_timezones.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -16,11 +16,11 @@
 
 import twistedcaldav.test.util
 from twistedcaldav.ical import Component
-from vobject.icalendar import utc, getTzid
-from vobject.icalendar import registerTzid
 from twistedcaldav.timezones import TimezoneCache, TimezoneException
 from twistedcaldav.timezones import readTZ, listTZs
-import datetime
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.timezone import PyCalendarTimezone
+
 import os
 
 class TimezoneProblemTest (twistedcaldav.test.util.TestCase):
@@ -30,6 +30,10 @@
 
     data_dir = os.path.join(os.path.dirname(__file__), "data")
 
+    def tearDown(self):
+        TimezoneCache.clear()
+        TimezoneCache.create()
+        
     def doTest(self, filename, dtstart, dtend, testEqual=True):
         
         if testEqual:
@@ -40,7 +44,7 @@
         calendar = Component.fromStream(file(os.path.join(self.data_dir, filename)))
         if calendar.name() != "VCALENDAR": self.fail("Calendar is not a VCALENDAR")
 
-        instances = calendar.expandTimeRanges(datetime.date(2100, 1, 1))
+        instances = calendar.expandTimeRanges(PyCalendarDateTime(2100, 1, 1))
         for key in instances:
             instance = instances[key]
             start = instance.start
@@ -54,83 +58,83 @@
         Properties in components
         """
         
-        oldtzid = getTzid("America/New_York")
-        try:
-            registerTzid("America/New_York", None)
-            self.doTest("TruncatedApr01.ics", datetime.datetime(2007, 04, 01, 16, 0, 0, tzinfo=utc), datetime.datetime(2007, 04, 01, 17, 0, 0, tzinfo=utc))
-        finally:
-            registerTzid("America/New_York", oldtzid)
+        TimezoneCache.create("")
+        TimezoneCache.clear()
 
+        self.doTest(
+            "TruncatedApr01.ics",
+            PyCalendarDateTime(2007, 04, 01, 16, 0, 0, PyCalendarTimezone(utc=True)),
+            PyCalendarDateTime(2007, 04, 01, 17, 0, 0, PyCalendarTimezone(utc=True))
+        )
+
     def test_truncatedDec(self):
         """
         Properties in components
         """
-        oldtzid = getTzid("America/New_York")
-        try:
-            registerTzid("America/New_York", None)
-            self.doTest("TruncatedDec10.ics", datetime.datetime(2007, 12, 10, 17, 0, 0, tzinfo=utc), datetime.datetime(2007, 12, 10, 18, 0, 0, tzinfo=utc))
-        finally:
-            registerTzid("America/New_York", oldtzid)
+        TimezoneCache.create("")
+        TimezoneCache.clear()
 
+        self.doTest(
+            "TruncatedDec10.ics",
+            PyCalendarDateTime(2007, 12, 10, 17, 0, 0, PyCalendarTimezone(utc=True)),
+            PyCalendarDateTime(2007, 12, 10, 18, 0, 0, PyCalendarTimezone(utc=True))
+        )
+
     def test_truncatedAprThenDecFail(self):
         """
         Properties in components
         """
-        if TimezoneCache.activeCache:
-            TimezoneCache.activeCache.unregister()
 
-        oldtzid = getTzid("America/New_York")
-        try:
-            registerTzid("America/New_York", None)
-            self.doTest(
-                "TruncatedApr01.ics",
-                datetime.datetime(2007, 04, 01, 16, 0, 0, tzinfo=utc),
-                datetime.datetime(2007, 04, 01, 17, 0, 0, tzinfo=utc),
-            )
-            self.doTest(
-                "TruncatedDec10.ics",
-                datetime.datetime(2007, 12, 10, 17, 0, 0, tzinfo=utc),
-                datetime.datetime(2007, 12, 10, 18, 0, 0, tzinfo=utc),
-                testEqual=False
-            )
-        finally:
-            registerTzid("America/New_York", oldtzid)
+        TimezoneCache.create("")
+        TimezoneCache.clear()
 
+        self.doTest(
+            "TruncatedApr01.ics",
+            PyCalendarDateTime(2007, 04, 01, 16, 0, 0, PyCalendarTimezone(utc=True)),
+            PyCalendarDateTime(2007, 04, 01, 17, 0, 0, PyCalendarTimezone(utc=True)),
+        )
+        self.doTest(
+            "TruncatedDec10.ics",
+            PyCalendarDateTime(2007, 12, 10, 17, 0, 0, PyCalendarTimezone(utc=True)),
+            PyCalendarDateTime(2007, 12, 10, 18, 0, 0, PyCalendarTimezone(utc=True)),
+            testEqual=False
+        )
+
     def test_truncatedAprThenDecOK(self):
         """
         Properties in components
         """
-        oldtzid = getTzid("America/New_York")
-        try:
-            registerTzid("America/New_York", None)
-            tzcache = TimezoneCache()
-            tzcache.register()
-            self.doTest(
-                "TruncatedApr01.ics",
-                datetime.datetime(2007, 04, 01, 16, 0, 0, tzinfo=utc),
-                datetime.datetime(2007, 04, 01, 17, 0, 0, tzinfo=utc),
-            )
-            self.doTest(
-                "TruncatedDec10.ics",
-                datetime.datetime(2007, 12, 10, 17, 0, 0, tzinfo=utc),
-                datetime.datetime(2007, 12, 10, 18, 0, 0, tzinfo=utc),
-            )
-            tzcache.unregister()
-        finally:
-            registerTzid("America/New_York", oldtzid)
+        TimezoneCache.create()
 
+        self.doTest(
+            "TruncatedApr01.ics",
+            PyCalendarDateTime(2007, 04, 01, 16, 0, 0, PyCalendarTimezone(utc=True)),
+            PyCalendarDateTime(2007, 04, 01, 17, 0, 0, PyCalendarTimezone(utc=True)),
+        )
+        self.doTest(
+            "TruncatedDec10.ics",
+            PyCalendarDateTime(2007, 12, 10, 17, 0, 0, PyCalendarTimezone(utc=True)),
+            PyCalendarDateTime(2007, 12, 10, 18, 0, 0, PyCalendarTimezone(utc=True)),
+        )
+
     def test_truncatedDecThenApr(self):
         """
         Properties in components
         """
-        oldtzid = getTzid("America/New_York")
-        try:
-            registerTzid("America/New_York", None)
-            self.doTest("TruncatedDec10.ics", datetime.datetime(2007, 12, 10, 17, 0, 0, tzinfo=utc), datetime.datetime(2007, 12, 10, 18, 0, 0, tzinfo=utc))
-            self.doTest("TruncatedApr01.ics", datetime.datetime(2007, 04, 01, 16, 0, 0, tzinfo=utc), datetime.datetime(2007, 04, 01, 17, 0, 0, tzinfo=utc))
-        finally:
-            registerTzid("America/New_York", oldtzid)
+        TimezoneCache.create("")
+        TimezoneCache.clear()
 
+        self.doTest(
+            "TruncatedDec10.ics",
+            PyCalendarDateTime(2007, 12, 10, 17, 0, 0, PyCalendarTimezone(utc=True)),
+            PyCalendarDateTime(2007, 12, 10, 18, 0, 0, PyCalendarTimezone(utc=True))
+        )
+        self.doTest(
+            "TruncatedApr01.ics",
+            PyCalendarDateTime(2007, 04, 01, 16, 0, 0, PyCalendarTimezone(utc=True)),
+            PyCalendarDateTime(2007, 04, 01, 17, 0, 0, PyCalendarTimezone(utc=True))
+        )
+
 class TimezoneCacheTest (twistedcaldav.test.util.TestCase):
     """
     Timezone support tests
@@ -140,19 +144,13 @@
 
     def test_basic(self):
         
-        registerTzid("America/New_York", None)
-        registerTzid("US/Eastern", None)
+        TimezoneCache.create()
+        self.assertTrue(readTZ("America/New_York"))
+        self.assertTrue(readTZ("US/Eastern"))
 
-        tzcache = TimezoneCache()
-        tzcache.register()
-        self.assertTrue(tzcache.loadTimezone("America/New_York"))
-        self.assertTrue(tzcache.loadTimezone("US/Eastern"))
-        tzcache.unregister()
-
     def test_not_in_cache(self):
         
-        tzcache = TimezoneCache()
-        tzcache.register()
+        TimezoneCache.create()
 
         data = """BEGIN:VCALENDAR
 VERSION:2.0
@@ -186,21 +184,24 @@
 
         calendar = Component.fromString(data)
         if calendar.name() != "VCALENDAR": self.fail("Calendar is not a VCALENDAR")
-        instances = calendar.expandTimeRanges(datetime.date(2100, 1, 1))
+        instances = calendar.expandTimeRanges(PyCalendarDateTime(2100, 1, 1))
         for key in instances:
             instance = instances[key]
             start = instance.start
             end = instance.end
-            self.assertEqual(start, datetime.datetime(2007, 12, 25, 05, 0, 0, tzinfo=utc))
-            self.assertEqual(end, datetime.datetime(2007, 12, 25, 06, 0, 0, tzinfo=utc))
+            self.assertEqual(start, PyCalendarDateTime(2007, 12, 25, 05, 0, 0, PyCalendarTimezone(utc=True)))
+            self.assertEqual(end, PyCalendarDateTime(2007, 12, 25, 06, 0, 0, PyCalendarTimezone(utc=True)))
             break;
-        tzcache.unregister()
 
 class TimezonePackageTest (twistedcaldav.test.util.TestCase):
     """
     Timezone support tests
     """
 
+    def setUp(self):
+        TimezoneCache.clear()
+        TimezoneCache.create()
+        
     def test_ReadTZ(self):
         
         self.assertTrue(readTZ("America/New_York").find("TZID:America/New_York") != -1)
@@ -223,7 +224,6 @@
     def test_ListTZsCached(self):
         
         results = listTZs()
-        results = listTZs()
         self.assertTrue("America/New_York" in results)
         self.assertTrue("Europe/London" in results)
         self.assertTrue("GB" in results)

Modified: CalendarServer/trunk/twistedcaldav/test/test_upgrade.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_upgrade.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/test/test_upgrade.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -1304,20 +1304,20 @@
 PRODID:-//Apple Inc.//iCal 3.0//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;BYMONTH=11;BYDAY=1SU
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
 TZNAME:PST
 TZOFFSETFROM:-0700
 TZOFFSETTO:-0800
 END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:20070311T020000
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
-TZNAME:PDT
-TZOFFSETFROM:-0800
-TZOFFSETTO:-0700
-END:DAYLIGHT
 END:VTIMEZONE
 BEGIN:VEVENT
 UID:1E238CA1-3C95-4468-B8CD-C8A399F78C71

Modified: CalendarServer/trunk/twistedcaldav/test/test_validation.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_validation.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/test/test_validation.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -44,8 +44,9 @@
         self.destination.name = lambda : '1'
         self.destinationParent = CalDAVResource()
         self.destinationParent.name = lambda : '2'
-        self.sampleCalendar = Component.fromString("""
-BEGIN:VCALENDAR
+
+    def _getSampleCalendar(self):
+        return Component.fromString("""BEGIN:VCALENDAR
 VERSION:2.0
 BEGIN:VEVENT
 UID:12345-67890
@@ -56,6 +57,9 @@
 END:VEVENT
 END:VCALENDAR
 """)
+
+    def _getStorer(self, calendar):
+        self.sampleCalendar = calendar
         req = SimpleRequest(None, "COPY", "http://example.com/foo/bar")
         self.storer = StoreCalendarObjectResource(
             req,
@@ -64,8 +68,8 @@
             destination_uri="http://example.com/foo/baz",
             calendar=self.sampleCalendar
         )
-
-
+        return self.storer
+                
     @inlineCallbacks
     def test_simpleValidRequest(self):
         """
@@ -73,7 +77,7 @@
         L{StoreCalendarObjectResource.fullValidation} results in a L{Deferred}
         which fires with C{None} (and raises no exception).
         """
-        self.assertEquals((yield self.storer.fullValidation()), None)
+        self.assertEquals((yield self._getStorer(self._getSampleCalendar()).fullValidation()), None)
 
 
     @inlineCallbacks
@@ -87,13 +91,14 @@
         """
 
         # Get the event, and add too many attendees to it.
+        self.sampleCalendar = self._getSampleCalendar()
         eventComponent = list(self.sampleCalendar.subcomponents())[0]
         for x in xrange(config.MaxAttendeesPerInstance):
             eventComponent.addProperty(
-                Property(u"ATTENDEE", u"mailto:user%d at example.com" % (x+3,)))
+                Property("ATTENDEE", "mailto:user%d at example.com" % (x+3,)))
 
         try:
-            yield self.storer.fullValidation()
+            yield self._getStorer(self.sampleCalendar).fullValidation()
         except HTTPError, err:
             element = XML(err.response.stream.mem)[0]
             self.assertEquals(

Modified: CalendarServer/trunk/twistedcaldav/timezones.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/timezones.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/timezones.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -16,13 +16,9 @@
 
 import os
 
-import vobject
-from vobject.icalendar import getTzid
-from vobject.icalendar import registerTzid
-
 from twext.python.log import Logger
 
-from twistedcaldav.ical import Component
+from pycalendar.timezonedb import PyCalendarTimezoneDatabase
 
 log = Logger()
 
@@ -51,114 +47,55 @@
 
 class TimezoneCache(object):
     
-    activeCache = None
+    dirName = None
 
     @staticmethod
-    def create():
-        if TimezoneCache.activeCache is None:
-            TimezoneCache.activeCache = TimezoneCache()
-            TimezoneCache.activeCache.register()
+    def _getDBPath():
+        if TimezoneCache.dirName is None:
+            try:
+                import pkg_resources
+            except ImportError:
+                TimezoneCache.dirName = os.path.join(os.path.dirname(__file__), "zoneinfo")
+            else:
+                TimezoneCache.dirName = pkg_resources.resource_filename("twistedcaldav", "zoneinfo")
         
-    def __init__(self):
-        self._caching = False
+        return TimezoneCache.dirName
 
-    def register(self):
-        self.vobjectRegisterTzid = registerTzid
-        vobject.icalendar.registerTzid = self.registerTzidFromCache
+    @staticmethod
+    def create(dbpath=None):
+        PyCalendarTimezoneDatabase.createTimezoneDatabase(TimezoneCache._getDBPath() if dbpath is None else dbpath)
     
-    def unregister(self):
-        vobject.icalendar.registerTzid = self.vobjectRegisterTzid
+    @staticmethod
+    def clear():
+        PyCalendarTimezoneDatabase.clearTimezoneDatabase()
 
-    def loadTimezone(self, tzid):
-        # Make sure it is not already loaded
-        if getTzid(tzid) != None:
-            return False
+# zoneinfo never changes in a running instance so cache all this data as we use it
+cachedTZs = {}
+cachedTZIDs = []
 
-        tzData = readTZ(tzid)
-        calendar = Component.fromString(tzData)
+def readTZ(tzid):
 
-        if calendar.name() != "VCALENDAR":
-            raise TimezoneException("%s does not contain valid iCalendar data." % (tzid,))
-
-        # Check that we now have it cached
-        if getTzid(tzid) == None:
-            raise TimezoneException("Could not read timezone %s from timezone cache." % (tzid,))
+    if tzid not in cachedTZs:
         
-        return True
-
-    def registerTzidFromCache(self, tzid, tzinfo):
-        if not self._caching:
-            self._caching = True
-            try:
-                self.loadTimezone(tzid)
-            except TimezoneException:
-                # Fallback to vobject processing the actual tzdata
-                log.err("Cannot load timezone data for %s from timezone cache" % (tzid,))
-                self.vobjectRegisterTzid(tzid, tzinfo)
-            self._caching = False
+        tzcal = PyCalendarTimezoneDatabase.getTimezoneInCalendar(tzid)
+        if tzcal:
+            cachedTZs[tzid] = str(tzcal)
         else:
-            self.vobjectRegisterTzid(tzid, tzinfo)
-
-try:
-    # zoneinfo never changes in a running instance so cache all this data as we use it
-    cachedTZs = {}
-    cachedTZIDs = []
-
-    import pkg_resources
-except ImportError:
-    #
-    # We don't have pkg_resources, so assume file paths work, since that's all we have
-    #
-    
-    dirname = os.path.join(os.path.dirname(__file__), "zoneinfo")
-    def readTZ(tzid):
-
-        if tzid not in cachedTZs:
-            tzpath = os.path.join(*tzid.split("/")) # Don't assume "/" from tzid is a path separator
-            tzpath = os.path.join(dirname, tzpath + ".ics")
-            try:
-                cachedTZs[tzid] = file(tzpath).read()
-            except IOError:
-                raise TimezoneException("Unknown time zone: %s" % (tzid,))
-            
-        return cachedTZs[tzid]
+            raise TimezoneException("Unknown time zone: %s" % (tzid,))
         
-    def listTZs(path=""):
-        if not path and cachedTZIDs:
-            return cachedTZIDs
+    return cachedTZs[tzid]
 
-        result = []
-        for item in os.listdir(os.path.join(dirname, path)):
-            if item.find('.') == -1:
-                result.extend(listTZs(os.path.join(path, item)))
-            elif item.endswith(".ics"):
-                result.append(os.path.join(path, item[:-4]))
-                
-        if not path:
-            cachedTZIDs.extend(result)
-        return result
-else:
-    def readTZ(tzid):
-        if tzid not in cachedTZs:
-            # Here, "/" is always the path separator
-            try:
-                cachedTZs[tzid] = pkg_resources.resource_stream("twistedcaldav", "zoneinfo/%s.ics" % (tzid,)).read()
-            except IOError:
-                raise TimezoneException("Unknown time zone: %s" % (tzid,))
-            
-        return cachedTZs[tzid]
+def listTZs(path=""):
+    if not path and cachedTZIDs:
+        return cachedTZIDs
 
-    def listTZs(path=""):  
-        if not path and cachedTZIDs:
-            return cachedTZIDs
-
-        result = []
-        for item in pkg_resources.resource_listdir("twistedcaldav", os.path.join("zoneinfo", path)):
-            if item.find('.') == -1:
-                result.extend(listTZs(os.path.join(path, item)))
-            elif item.endswith(".ics"):
-                result.append(os.path.join(path, item[:-4]))
-                
-        if not path:
-            cachedTZIDs.extend(result)
-        return result
+    result = []
+    for item in os.listdir(os.path.join(TimezoneCache._getDBPath(), path)):
+        if item.find('.') == -1:
+            result.extend(listTZs(os.path.join(path, item)))
+        elif item.endswith(".ics"):
+            result.append(os.path.join(path, item[:-4]))
+            
+    if not path:
+        cachedTZIDs.extend(result)
+    return result

Modified: CalendarServer/trunk/twistedcaldav/timezoneservice.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/timezoneservice.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/twistedcaldav/timezoneservice.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -40,13 +40,14 @@
 from twistedcaldav.customxml import calendarserver_namespace
 from twistedcaldav.extensions import DAVResource,\
     DAVResourceWithoutChildrenMixin
-from twistedcaldav.ical import parse_date_or_datetime
 from twistedcaldav.ical import tzexpand
 from twistedcaldav.resource import ReadOnlyNoCopyResourceMixIn
 from twistedcaldav.timezones import TimezoneException
 from twistedcaldav.timezones import listTZs
 from twistedcaldav.timezones import readTZ
 
+from pycalendar.datetime import PyCalendarDateTime
+
 class TimezoneServiceResource (ReadOnlyNoCopyResourceMixIn, DAVResourceWithoutChildrenMixin, DAVResource):
     """
     Timezone Service resource.
@@ -234,7 +235,7 @@
             start = request.args.get("start", ())
             if len(start) != 1:
                 raise ValueError()
-            start = parse_date_or_datetime(start[0])
+            start = PyCalendarDateTime.parseText(start[0])
         except ValueError:
             raise HTTPError(ErrorResponse(
                 responsecode.BAD_REQUEST,
@@ -246,7 +247,7 @@
             end = request.args.get("end", ())
             if len(end) != 1:
                 raise ValueError()
-            end = parse_date_or_datetime(end[0])
+            end = PyCalendarDateTime.parseText(end[0])
             if end <= start:
                 raise ValueError()
         except ValueError:

Modified: CalendarServer/trunk/txdav/caldav/datastore/index_file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/index_file.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/txdav/caldav/datastore/index_file.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -39,8 +39,6 @@
 except ImportError:
     from pysqlite2 import dbapi2 as sqlite
 
-from vobject.icalendar import utc
-
 from twisted.internet.defer import maybeDeferred, succeed
 
 from twext.python.log import Logger, LoggingMixIn
@@ -48,6 +46,7 @@
 from txdav.common.icommondatastore import SyncTokenValidException,\
     ReservationError, IndexedSearchException
 
+from twistedcaldav.dateops import pyCalendarTodatetime
 from twistedcaldav.ical import Component
 from twistedcaldav.query import calendarquery, calendarqueryfilter
 from twistedcaldav.sql import AbstractSQLDatabase
@@ -56,6 +55,10 @@
 from twistedcaldav.config import config
 from twistedcaldav.memcachepool import CachePoolUserMixIn
 
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.duration import PyCalendarDuration
+from pycalendar.timezone import PyCalendarTimezone
+
 log = Logger()
 
 db_basename = db_prefix + "sqlite"
@@ -311,9 +314,10 @@
                 # will have been indexed with an "infinite" value always included.
                 maxDate, isStartDate = filter.getmaxtimerange()
                 if maxDate:
-                    maxDate = maxDate.date()
+                    maxDate = maxDate.duplicate()
+                    maxDate.setDateOnly(True)
                     if isStartDate:
-                        maxDate += datetime.timedelta(days=365)
+                        maxDate += PyCalendarDuration(days=365)
                     self.testAndUpdateIndex(maxDate)
             else:
                 # We cannot handle this filter in an indexed search
@@ -605,7 +609,7 @@
         Gives all resources which have not been expanded beyond a given date
         in the index
         """
-        return self._db_values_for_sql("select NAME from RESOURCE where RECURRANCE_MAX < :1", minDate)
+        return self._db_values_for_sql("select NAME from RESOURCE where RECURRANCE_MAX < :1", pyCalendarTodatetime(minDate))
 
     def reExpandResource(self, name, expand_until):
         """
@@ -635,11 +639,10 @@
         # Decide how far to expand based on the component
         doInstanceIndexing = False
         master = calendar.masterComponent()
-        if master is None or not calendar.isRecurring() and not calendar.isRecurringUnbounded():
+        if master is None or not calendar.isRecurring():
             # When there is no master we have a set of overridden components - index them all.
             # When there is one instance - index it.
-            # When bounded - index all.
-            expand = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
+            expand = PyCalendarDateTime(2100, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
             doInstanceIndexing = True
         else:
             # If migrating or re-creating or config option for delayed indexing is off, always index
@@ -650,8 +653,8 @@
             # by default.  This is a caching parameter which affects the size of the index;
             # it does not affect search results beyond this period, but it may affect
             # performance of such a search.
-            expand = (datetime.date.today() +
-                      datetime.timedelta(days=config.FreeBusyIndexExpandAheadDays))
+            expand = (PyCalendarDateTime.getToday() +
+                      PyCalendarDuration(days=config.FreeBusyIndexExpandAheadDays))
 
             if expand_until and expand_until > expand:
                 expand = expand_until
@@ -668,8 +671,8 @@
             # occurrences into some obscenely far-in-the-future date, so we cap the caching
             # period.  Searches beyond this period will always be relatively expensive for
             # resources with occurrences beyond this period.
-            if expand > (datetime.date.today() +
-                         datetime.timedelta(days=config.FreeBusyIndexExpandMaxDays)):
+            if expand > (PyCalendarDateTime.getToday() +
+                         PyCalendarDuration(days=config.FreeBusyIndexExpandMaxDays)):
                 raise IndexedSearchException()
 
         # Always do recurrence expansion even if we do not intend to index - we need this to double-check the
@@ -684,7 +687,7 @@
         # Now coerce indexing to off if needed 
         if not doInstanceIndexing:
             instances = None
-            recurrenceLimit = datetime.datetime(1900, 1, 1, 0, 0, 0, tzinfo=utc)
+            recurrenceLimit = PyCalendarDateTime(1900, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
             
         self._delete_from_db(name, uid, False)
 
@@ -693,7 +696,7 @@
             """
             insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX, ORGANIZER)
             values (:1, :2, :3, :4, :5)
-            """, name, uid, calendar.resourceType(), recurrenceLimit, organizer
+            """, name, uid, calendar.resourceType(), pyCalendarTodatetime(recurrenceLimit) if recurrenceLimit else None, organizer
         )
         resourceid = self.lastrowid
 
@@ -720,9 +723,9 @@
         if doInstanceIndexing:
             for key in instances:
                 instance = instances[key]
-                start = instance.start.replace(tzinfo=utc)
-                end = instance.end.replace(tzinfo=utc)
-                float = 'Y' if instance.start.tzinfo is None else 'N'
+                start = instance.start
+                end = instance.end
+                float = 'Y' if instance.start.floating() else 'N'
                 transp = 'T' if instance.component.propertyValue("TRANSP") == "TRANSPARENT" else 'F'
                 self._db_execute(
                     """
@@ -731,8 +734,8 @@
                     """,
                     resourceid,
                     float,
-                    start,
-                    end,
+                    pyCalendarTodatetime(start),
+                    pyCalendarTodatetime(end),
                     icalfbtype_to_indexfbtype.get(instance.component.getFBType(), 'F'),
                     transp
                 )
@@ -751,14 +754,14 @@
             # Special - for unbounded recurrence we insert a value for "infinity"
             # that will allow an open-ended time-range to always match it.
             if calendar.isRecurringUnbounded():
-                start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
-                end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
+                start = PyCalendarDateTime(2100, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
+                end = PyCalendarDateTime(2100, 1, 1, 1, 0, 0, tzid=PyCalendarTimezone(utc=True))
                 float = 'N'
                 self._db_execute(
                     """
                     insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE, TRANSPARENT)
                     values (:1, :2, :3, :4, :5, :6)
-                    """, resourceid, float, start, end, '?', '?'
+                    """, resourceid, float, pyCalendarTodatetime(start), pyCalendarTodatetime(end), '?', '?'
                 )
                 instanceid = self.lastrowid
                 peruserdata = calendar.perUserTransparency(None)


Property changes on: CalendarServer/trunk/txdav/caldav/datastore/index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/caldav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/index.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/caldav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/index.py:6322-6394

Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -39,7 +39,7 @@
 from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
 from twistedcaldav.config import config
 from twistedcaldav.dateops import normalizeForIndex, datetimeMktime,\
-    parseSQLTimestamp
+    parseSQLTimestamp, pyCalendarTodatetime
 from twistedcaldav.ical import Component
 from twistedcaldav.instance import InvalidOverriddenInstanceError
 from twistedcaldav.memcacher import Memcacher
@@ -69,10 +69,10 @@
 from twext.enterprise.dal.syntax import Len
 from txdav.common.icommondatastore import IndexedSearchException
 
-from vobject.icalendar import utc
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.duration import PyCalendarDuration
+from pycalendar.timezone import PyCalendarTimezone
 
-import datetime
-
 from zope.interface.declarations import implements
 
 class CalendarHome(CommonHome):
@@ -399,13 +399,11 @@
         # Decide how far to expand based on the component
         doInstanceIndexing = False
         master = component.masterComponent()
-        if ( master is None or not component.isRecurring()
-             and not component.isRecurringUnbounded() ):
+        if ( master is None or not component.isRecurring() ):
             # When there is no master we have a set of overridden components -
             #   index them all.
             # When there is one instance - index it.
-            # When bounded - index all.
-            expand = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
+            expand = PyCalendarDateTime(2100, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
             doInstanceIndexing = True
         else:
             
@@ -417,8 +415,8 @@
             # by default.  This is a caching parameter which affects the size of the index;
             # it does not affect search results beyond this period, but it may affect
             # performance of such a search.
-            expand = (datetime.date.today() +
-                      datetime.timedelta(days=config.FreeBusyIndexExpandAheadDays))
+            expand = (PyCalendarDateTime.getToday() +
+                      PyCalendarDuration(days=config.FreeBusyIndexExpandAheadDays))
 
             if expand_until and expand_until > expand:
                 expand = expand_until
@@ -435,8 +433,8 @@
             # occurrences into some obscenely far-in-the-future date, so we cap the caching
             # period.  Searches beyond this period will always be relatively expensive for
             # resources with occurrences beyond this period.
-            if expand > (datetime.date.today() +
-                         datetime.timedelta(days=config.FreeBusyIndexExpandMaxDays)):
+            if expand > (PyCalendarDateTime.getToday() +
+                         PyCalendarDuration(days=config.FreeBusyIndexExpandMaxDays)):
                 raise IndexedSearchException
 
         # Always do recurrence expansion even if we do not intend to index - we need this to double-check the
@@ -458,7 +456,7 @@
         # Now coerce indexing to off if needed 
         if not doInstanceIndexing:
             instances = None
-            recurrenceLimit = datetime.datetime(1900, 1, 1, 0, 0, 0, tzinfo=utc)
+            recurrenceLimit = PyCalendarDateTime(1900, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
             
         co = schema.CALENDAR_OBJECT
         tr = schema.TIME_RANGE
@@ -503,7 +501,7 @@
                 co.DROPBOX_ID                      : self._dropboxID,
                 co.ORGANIZER                       : organizer,
                 co.RECURRANCE_MAX                  :
-                    normalizeForIndex(recurrenceLimit) if recurrenceLimit else None,
+                    pyCalendarTodatetime(normalizeForIndex(recurrenceLimit)) if recurrenceLimit else None,
                 co.ACCESS                          : self._access,
                 co.SCHEDULE_OBJECT                 : self._schedule_object,
                 co.SCHEDULE_TAG                    : self._schedule_tag,
@@ -535,7 +533,7 @@
         else:
             values = {
                 co.RECURRANCE_MAX :
-                    normalizeForIndex(recurrenceLimit) if recurrenceLimit else None,
+                    pyCalendarTodatetime(normalizeForIndex(recurrenceLimit)) if recurrenceLimit else None,
             }
     
             yield Update(
@@ -553,16 +551,18 @@
             # TIME_RANGE table update
             for key in instances:
                 instance = instances[key]
-                start = instance.start.replace(tzinfo=utc)
-                end = instance.end.replace(tzinfo=utc)
-                float = instance.start.tzinfo is None
+                start = instance.start
+                end = instance.end
+                float = instance.start.floating()
+                start.setTimezoneUTC(True)
+                end.setTimezoneUTC(True)
                 transp = instance.component.propertyValue("TRANSP") == "TRANSPARENT"
                 instanceid = (yield Insert({
                     tr.CALENDAR_RESOURCE_ID        : self._calendar._resourceID,
                     tr.CALENDAR_OBJECT_RESOURCE_ID : self._resourceID,
                     tr.FLOATING                    : float,
-                    tr.START_DATE                  : start,
-                    tr.END_DATE                    : end,
+                    tr.START_DATE                  : pyCalendarTodatetime(start),
+                    tr.END_DATE                    : pyCalendarTodatetime(end),
                     tr.FBTYPE                      :
                         icalfbtype_to_indexfbtype.get(
                             instance.component.getFBType(),
@@ -580,16 +580,16 @@
             # Special - for unbounded recurrence we insert a value for "infinity"
             # that will allow an open-ended time-range to always match it.
             if component.isRecurringUnbounded():
-                start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
-                end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
+                start = PyCalendarDateTime(2100, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
+                end = PyCalendarDateTime(2100, 1, 1, 1, 0, 0, tzid=PyCalendarTimezone(utc=True))
                 float = False
                 transp = True
                 instanceid = (yield Insert({
                     tr.CALENDAR_RESOURCE_ID        : self._calendar._resourceID,
                     tr.CALENDAR_OBJECT_RESOURCE_ID : self._resourceID,
                     tr.FLOATING                    : float,
-                    tr.START_DATE                  : start,
-                    tr.END_DATE                    : end,
+                    tr.START_DATE                  : pyCalendarTodatetime(start),
+                    tr.END_DATE                    : pyCalendarTodatetime(end),
                     tr.FBTYPE                      :
                         icalfbtype_to_indexfbtype["UNKNOWN"],
                     tr.TRANSPARENT                 : transp,

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/common.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/common.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -104,7 +104,7 @@
         "BEGIN:VALARM\r\n"
           "X-WR-ALARMUID:1377CCC7-F85C-4610-8583-9513D4B364E1\r\n"
           "TRIGGER:-PT20M\r\n"
-          "ATTACH;VALUE=URI:Basso\r\n"
+          "ATTACH:Basso\r\n"
           "ACTION:AUDIO\r\n"
         "END:VALARM\r\n"
       "END:VEVENT\r\n"
@@ -130,7 +130,7 @@
         "BEGIN:VALARM\r\n"
           "X-WR-ALARMUID:1377CCC7-F85C-4610-8583-9513D4B364E1\r\n"
           "TRIGGER:-PT20M\r\n"
-          "ATTACH;VALUE=URI:Basso\r\n"
+          "ATTACH:Basso\r\n"
           "ACTION:AUDIO\r\n"
         "END:VALARM\r\n"
       "END:VEVENT\r\n"

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -15,6 +15,7 @@
 ##
 
 from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks
 from twisted.internet.task import deferLater
 
 from txdav.caldav.datastore.index_file import Index, MemcachedUIDReserver
@@ -29,11 +30,10 @@
 from twistedcaldav.test.util import InMemoryMemcacheProtocol
 import twistedcaldav.test.util
 
-import datetime
+from pycalendar.datetime import PyCalendarDateTime
+
 import os
-from twisted.internet.defer import inlineCallbacks
 
-
 class MinimalCalendarObjectReplacement(object):
     """
     Provide the minimal set of attributes and methods from CalDAVFile required
@@ -301,7 +301,7 @@
             else:
                 self.assertFalse(self.db.resourceExists(name), msg=description)
 
-        self.db.testAndUpdateIndex(datetime.date(2020, 1, 1))
+        self.db.testAndUpdateIndex(PyCalendarDateTime(2020, 1, 1))
         for description, name, calendar_txt, reCreate, ok in data:
             if ok:
                 self.assertTrue(self.db.resourceExists(name), msg=description)


Property changes on: CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/caldav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_index.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/caldav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_index.py:6322-6394

Modified: CalendarServer/trunk/txdav/caldav/datastore/util.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/util.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/txdav/caldav/datastore/util.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -89,16 +89,15 @@
     # Now look at each ATTACH property and see if it might be a dropbox item
     # and if so extract the id from that
 
-    attachments = (yield calendarObject.component()
-        ).getAllPropertiesInAnyComponent(
+    attachments = (yield calendarObject.component()).getAllPropertiesInAnyComponent(
         "ATTACH",
         depth=1,
     )
     for attachment in attachments:
 
         # Make sure the value type is URI and http(s) and it is in a dropbox
-        valueType = attachment.params().get("VALUE", ("TEXT",))
-        if valueType[0] == "URI" and attachment.value().startswith("http"):
+        valueType = attachment.parameterValue("VALUE", "URI")
+        if valueType == "URI" and attachment.value().startswith("http"):
             segments = attachment.value().split("/")
             try:
                 if segments[-3] == "dropbox":

Modified: CalendarServer/trunk/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/icalendarstore.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/txdav/caldav/icalendarstore.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -35,16 +35,6 @@
 ]
 
 
-# The following imports are used by the L{} links below, but shouldn't actually
-# be imported.as they're not really needed.
-
-# from datetime import datetime, date, tzinfo
-
-# from twext.python.vcomponent import VComponent
-
-# from txdav.idav import IPropertyStore
-# from txdav.idav import ITransaction
-
 class ICalendarTransaction(ICommonTransaction):
     """
     Transaction functionality required to be implemented by calendar stores.
@@ -288,9 +278,9 @@
         instances that occur within the time range that begins at
         C{start} and ends at C{end}.
 
-        @param start: a L{datetime} or L{date}.
-        @param end: a L{datetime} or L{date}.
-        @param timeZone: a L{tzinfo}.
+        @param start: a L{PyCalendarDateTime}.
+        @param end: a L{PyCalendarDateTime}.
+        @param timeZone: a L{PyCalendarTimezone}.
         @return: an iterable of L{ICalendarObject}s.
         """
 


Property changes on: CalendarServer/trunk/txdav/carddav/datastore/index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/carddav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/vcardindex.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/carddav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/vcardindex.py:6322-6394


Property changes on: CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/carddav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/carddav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py:6322-6394

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -73,7 +73,8 @@
 from txdav.base.propertystore.sql import PropertyStore
 
 from twistedcaldav.customxml import NotificationType
-from twistedcaldav.dateops import datetimeMktime, parseSQLTimestamp
+from twistedcaldav.dateops import datetimeMktime, parseSQLTimestamp,\
+    pyCalendarTodatetime
 
 v1_schema = getModule(__name__).filePath.sibling("sql_schema_v1.sql").getContent()
 
@@ -302,12 +303,12 @@
     def eventsOlderThan(self, cutoff, batchSize=None):
         """
         Return up to the oldest batchSize events which exist completely earlier
-        than "cutoff" (datetime)
+        than "cutoff" (PyCalendarDateTime)
 
         Returns a deferred to a list of (uid, calendarName, eventName, maxDate)
         tuples.
         """
-        kwds = { "CutOff" : cutoff }
+        kwds = { "CutOff" : pyCalendarTodatetime(cutoff) }
         if batchSize is not None:
             kwds["batchSize"] = batchSize
             query = self._oldEventsLimited

Modified: CalendarServer/trunk/txdav/common/datastore/sql_legacy.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_legacy.py	2011-03-17 18:53:26 UTC (rev 7207)
+++ CalendarServer/trunk/txdav/common/datastore/sql_legacy.py	2011-03-17 18:54:11 UTC (rev 7208)
@@ -20,7 +20,6 @@
 PostgreSQL data store.
 """
 
-import datetime
 import StringIO
 
 from twistedcaldav.sharing import SharedCollectionRecord
@@ -33,7 +32,7 @@
 
 from twistedcaldav import carddavxml
 from twistedcaldav.config import config
-from twistedcaldav.dateops import normalizeForIndex
+from twistedcaldav.dateops import normalizeForIndex, pyCalendarTodatetime
 from twistedcaldav.memcachepool import CachePoolUserMixIn
 from twistedcaldav.notifications import NotificationRecord
 from twistedcaldav.query import (
@@ -56,6 +55,8 @@
     ADDRESSBOOK_HOME_TABLE, ADDRESSBOOK_BIND_TABLE, schema)
 
 
+from pycalendar.duration import PyCalendarDuration
+
 log = Logger()
 
 indexfbtype_to_icalfbtype = {
@@ -1073,7 +1074,7 @@
         """
         returnValue([row[0] for row in (
             yield self._notExpandedBeyondQuery.on(
-                self._txn, minDate=normalizeForIndex(minDate),
+                self._txn, minDate=pyCalendarTodatetime(normalizeForIndex(minDate)),
                 resourceID=self.calendar._resourceID))]
         )
 
@@ -1138,9 +1139,10 @@
                 # "infinite" value always included.
                 maxDate, isStartDate = filter.getmaxtimerange()
                 if maxDate:
-                    maxDate = maxDate.date()
+                    maxDate = maxDate.duplicate()
+                    maxDate.setDateOnly(True)
                     if isStartDate:
-                        maxDate += datetime.timedelta(days=365)
+                        maxDate += PyCalendarDuration(days=365)
                     yield self.testAndUpdateIndex(maxDate)
             else:
                 # We cannot handler this filter in an indexed search
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110317/1ebc37c2/attachment-0001.html>


More information about the calendarserver-changes mailing list