[CalendarServer-changes] [5225] CalendarServer/branches/users/cdaboo/shared-calendars-5187

source_changes at macosforge.org source_changes at macosforge.org
Mon Mar 1 10:49:49 PST 2010


Revision: 5225
          http://trac.macosforge.org/projects/calendarserver/changeset/5225
Author:   cdaboo at apple.com
Date:     2010-03-01 10:49:46 -0800 (Mon, 01 Mar 2010)
Log Message:
-----------
Merge from trunk.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/provision/root.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/provision/test/test_root.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/tools/anonymize.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/tools/fixcalendardata.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/tools/gateway.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/tools/test/test_gateway.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/contrib/migration/59_calendarmigrator.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/doc/Admin/ExtendedLogItems.txt
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/run
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/python/datetime.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/python/test/test_datetime.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/dateops.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/addressbook.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/wiki.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/ical.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/instance.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/report_addressbook_findshared.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/icaldiff.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/itip.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/scheduler.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/test/test_wiki.py

Removed Paths:
-------------
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_dateops.py

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/provision/root.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/provision/root.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/provision/root.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -1,3 +1,4 @@
+# -*- test-case-name: calendarserver.provision.test.test_root -*-
 ##
 # Copyright (c) 2005-2010 Apple Inc. All rights reserved.
 #
@@ -28,6 +29,7 @@
 
 from twext.python.log import Logger
 
+from twistedcaldav.resource import CalDAVComplianceMixIn
 from twistedcaldav.extensions import DAVFile, CachingPropertyStore
 from twistedcaldav.extensions import DirectoryPrincipalPropertySearchMixIn
 from twistedcaldav.extensions import ReadOnlyResourceMixIn
@@ -41,7 +43,7 @@
 log = Logger()
 
 
-class RootResource (ReadOnlyResourceMixIn, DirectoryPrincipalPropertySearchMixIn, DAVFile):
+class RootResource (ReadOnlyResourceMixIn, DirectoryPrincipalPropertySearchMixIn, CalDAVComplianceMixIn, DAVFile):
     """
     A special root resource that contains support checking SACLs
     as well as adding responseFilters.
@@ -165,44 +167,48 @@
         # server for the corresponding record name.  If that maps to a
         # principal, assign that to authnuser.
 
-        wikiConfig = config.Authentication.Wiki
-        cookies = request.headers.getHeader("cookie")
-        if wikiConfig["Enabled"] and cookies is not None:
-            for cookie in cookies:
-                if cookie.name == wikiConfig["Cookie"]:
-                    token = cookie.value
-                    break
-            else:
-                token = None
+        if not hasattr(request, "checkedWiki"):
+            # Only do this once per request
+            request.checkedWiki = True
 
-            if token is not None and token != "unauthenticated":
-                log.debug("Wiki sessionID cookie value: %s" % (token,))
-                proxy = Proxy(wikiConfig["URL"])
-                try:
-                    username = (yield proxy.callRemote(wikiConfig["UserMethod"], token))
-                except Exception, e:
-                    log.error("Failed to look up wiki token (%s)" % (e,))
-                    username = None
+            wikiConfig = config.Authentication.Wiki
+            cookies = request.headers.getHeader("cookie")
+            if wikiConfig["Enabled"] and cookies is not None:
+                for cookie in cookies:
+                    if cookie.name == wikiConfig["Cookie"]:
+                        token = cookie.value
+                        break
+                else:
+                    token = None
 
-                if username is not None:
-                    log.debug("Wiki lookup returned user: %s" % (username,))
-                    principal = None
-                    directory = request.site.resource.getDirectory()
-                    record = directory.recordWithShortName("users", username)
-                    log.debug("Wiki user record for user %s : %s" % (username, record))
-                    if record:
-                        # Note: record will be None if it's a /Local/Default user
-                        for collection in self.principalCollections():
-                            principal = collection.principalForRecord(record)
-                            if principal is not None:
-                                break
+                if token is not None and token != "unauthenticated":
+                    log.debug("Wiki sessionID cookie value: %s" % (token,))
+                    proxy = Proxy(wikiConfig["URL"])
+                    try:
+                        username = (yield proxy.callRemote(wikiConfig["UserMethod"], token))
+                    except Exception, e:
+                        log.error("Failed to look up wiki token (%s)" % (e,))
+                        username = None
 
-                    if principal:
-                        log.debug("Found wiki principal and setting authnuser and authzuser")
-                        request.authzUser = request.authnUser = davxml.Principal(
-                            davxml.HRef.fromString("/principals/__uids__/%s/" % (record.guid,))
-                        )
+                    if username is not None:
+                        log.debug("Wiki lookup returned user: %s" % (username,))
+                        principal = None
+                        directory = request.site.resource.getDirectory()
+                        record = directory.recordWithShortName("users", username)
+                        log.debug("Wiki user record for user %s : %s" % (username, record))
+                        if record:
+                            # Note: record will be None if it's a /Local/Default user
+                            for collection in self.principalCollections():
+                                principal = collection.principalForRecord(record)
+                                if principal is not None:
+                                    break
 
+                        if principal:
+                            log.debug("Wiki-authenticated principal %s being assigned to authnUser" % (record.guid,))
+                            request.authnUser = davxml.Principal(
+                                davxml.HRef.fromString("/principals/__uids__/%s/" % (record.guid,))
+                            )
+
         # We don't want the /inbox resource to pay attention to SACLs because
         # we just want it to use the hard-coded ACL for the imip reply user.
         # The /timezones resource is used by the wiki web calendar, so open
@@ -223,6 +229,7 @@
             else:
                 wikiName = segments[2][5:]
             if wikiName:
+                log.debug("Wiki principal %s being assigned to authzUser" % (wikiName,))
                 request.authzUser = davxml.Principal(
                     davxml.HRef.fromString("/principals/wikis/%s/" % (wikiName,))
                 )

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/provision/test/test_root.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/provision/test/test_root.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/provision/test/test_root.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -17,7 +17,8 @@
 import os
 
 from twisted.cred.portal import Portal
-from twisted.internet.defer import inlineCallbacks, maybeDeferred
+from twisted.internet.defer import inlineCallbacks, maybeDeferred, returnValue
+
 from twext.web2 import http_headers
 from twext.web2 import responsecode
 from twext.web2 import server
@@ -34,6 +35,7 @@
 from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile
 
 from calendarserver.provision.root import RootResource
+from twistedcaldav.config import config
 from twistedcaldav.directory import augment
 
 class FakeCheckSACL(object):
@@ -54,6 +56,12 @@
     def setUp(self):
         super(RootTests, self).setUp()
 
+        # XXX make sure that the config hooks have been run, so that we get the
+        # default RootResourceACL key is set and traversal works.  This is not
+        # great, and the ACLs supported by the root resource should really be
+        # an _attribute_ on the root resource. -glyph
+        config.update({})
+
         self.docroot = self.mktemp()
         os.mkdir(self.docroot)
 
@@ -81,6 +89,41 @@
 
         self.site = server.Site(self.root)
 
+
+
+class ComplianceTests(RootTests):
+    """
+    Tests to verify CalDAV compliance of the root resource.
+    """
+
+    @inlineCallbacks
+    def issueRequest(self, segments, method='GET'):
+        """
+        Get a resource from a particular path from the root URI, and return a
+        Deferred which will fire with (something adaptable to) an HTTP response
+        object.
+        """
+        request = SimpleRequest(self.site, method, ('/'.join([''] + segments)))
+        rsrc = self.root
+        while segments:
+            rsrc, segments = (yield maybeDeferred(rsrc.locateChild, request, segments))
+
+        result = yield rsrc.renderHTTP(request)
+        returnValue(result)
+
+
+    @inlineCallbacks
+    def test_optionsIncludeCalendar(self):
+        """
+        OPTIONS request should include a DAV header that mentions the
+        addressbook capability.
+        """
+        self.patch(config, 'EnableCardDAV', True)
+        response = yield self.issueRequest([''], 'OPTIONS')
+        self.assertIn('addressbook', response.headers.getHeader('DAV'))
+
+
+
 class SACLTests(RootTests):
     
     @inlineCallbacks
@@ -324,3 +367,18 @@
 
         d = self.send(request, gotResponse1)
         return d
+
+class WikiTests(RootTests):
+    
+    @inlineCallbacks
+    def test_oneTime(self):
+        """
+        Make sure wiki auth lookup is only done once per request;
+        request.checkedWiki will be set to True
+        """
+
+        request = SimpleRequest(self.site, "GET", "/principals/")
+
+        resrc, segments = (yield maybeDeferred(self.root.locateChild, request, ['principals']))
+        resrc, segments = (yield maybeDeferred(resrc.locateChild, request, ['principals']))
+        self.assertTrue(request.checkedWiki)

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/tools/anonymize.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/tools/anonymize.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/tools/anonymize.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -23,7 +23,6 @@
 import datetime
 import hashlib
 import os
-import plistlib
 import random
 import shutil
 import sys
@@ -33,6 +32,8 @@
 import xattr
 import zlib
 
+from twext.python.plistlib import readPlistFromString
+
 COPY_CAL_XATTRS = (
     'WebDAV:{DAV:}resourcetype',
     'WebDAV:{urn:ietf:params:xml:ns:caldav}calendar-timezone',
@@ -375,7 +376,7 @@
             if child.returncode:
                 raise DirectoryError(error)
             else:
-                records = plistlib.readPlistFromString(output)
+                records = readPlistFromString(output)
                 random.shuffle(records) # so we don't go alphabetically
 
                 for record in records:

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/tools/fixcalendardata.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/tools/fixcalendardata.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/tools/fixcalendardata.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -16,7 +16,6 @@
 # limitations under the License.
 ##
 
-from plistlib import readPlist
 import re
 import datetime
 import getopt
@@ -26,6 +25,8 @@
 import time
 import xattr
 
+from twext.python.plistlib import readPlist
+
 PLIST_FILE = "/etc/caldavd/caldavd.plist"
 SCAN_FILE = "problems.txt"
 

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/tools/gateway.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/tools/gateway.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/tools/gateway.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -20,10 +20,11 @@
 from grp import getgrnam
 from pwd import getpwnam
 import os
-import plistlib
 import sys
 import xml
 
+from twext.python.plistlib import readPlistFromString, writePlistToString
+
 from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks
 from twisted.python.util import switchUID
@@ -115,7 +116,7 @@
     #
     rawInput = sys.stdin.read()
     try:
-        plist = plistlib.readPlistFromString(rawInput)
+        plist = readPlistFromString(rawInput)
     except xml.parsers.expat.ExpatError, e:
         respondWithError(str(e))
         return
@@ -438,10 +439,10 @@
     respond(command, result)
 
 def respond(command, result):
-    sys.stdout.write(plistlib.writePlistToString( { 'command' : command['command'], 'result' : result } ) )
+    sys.stdout.write(writePlistToString( { 'command' : command['command'], 'result' : result } ) )
 
 def respondWithError(msg, status=1):
-    sys.stdout.write(plistlib.writePlistToString( { 'error' : msg, } ) )
+    sys.stdout.write(writePlistToString( { 'error' : msg, } ) )
     """
     try:
         reactor.stop()

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/tools/test/test_gateway.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/tools/test/test_gateway.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/calendarserver/tools/test/test_gateway.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -15,7 +15,7 @@
 ##
 
 import os
-import plistlib
+from twext.python.plistlib import readPlistFromString
 import xml
 
 from twext.python.filepath import CachingFilePath as FilePath
@@ -90,7 +90,7 @@
         reactor.spawnProcess(CapturingProcessProtocol(deferred, command), python, args, env=os.environ, path=cwd)
         output = yield deferred
         try:
-            plist = plistlib.readPlistFromString(output)
+            plist = readPlistFromString(output)
         except xml.parsers.expat.ExpatError, e:
             print "Error (%s) parsing (%s)" % (e, output)
             raise

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/contrib/migration/59_calendarmigrator.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/contrib/migration/59_calendarmigrator.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/contrib/migration/59_calendarmigrator.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -23,11 +23,11 @@
 import datetime
 import optparse
 import os
-import plistlib
 import shutil
-import subprocess
 import sys
 
+from twext.python.plistlib import readPlist, writePlist
+
 LAUNCHD_KEY = "org.calendarserver.calendarserver"
 LOG = "/Library/Logs/Migration/calendarmigrator.log"
 SERVICE_NAME = "calendar"
@@ -134,10 +134,10 @@
                 if name == "caldavd.plist":
                     # Migrate certain settings from the old plist to new:
                     log("Parsing %s" % (oldPath,))
-                    oldPlist = plistlib.readPlist(oldPath)
+                    oldPlist = readPlist(oldPath)
                     if os.path.exists(newPath):
                         log("Parsing %s" % (newPath,))
-                        newPlist = plistlib.readPlist(newPath)
+                        newPlist = readPlist(newPath)
                         log("Removing %s" % (newPath,))
                         os.remove(newPath)
                     else:
@@ -145,7 +145,7 @@
                     log("Processing %s" % (oldPath,))
                     mergePlist(oldPlist, newPlist)
                     log("Writing %s" % (newPath,))
-                    plistlib.writePlist(newPlist, newPath)
+                    writePlist(newPlist, newPath)
 
                 else:
                     # Copy the file over, overwriting copy in newConfigDir
@@ -208,7 +208,7 @@
 
     overridesPath = os.path.join(source, LAUNCHD_OVERRIDES)
     if os.path.isfile(overridesPath):
-        overrides = plistlib.readPlist(overridesPath)
+        overrides = readPlist(overridesPath)
         try:
             return overrides[service]['Disabled']
         except KeyError:
@@ -217,7 +217,7 @@
 
     prefsPath = os.path.join(source, LAUNCHD_PREFS_DIR, "%s.plist" % service)
     if os.path.isfile(prefsPath):
-        prefs = plistlib.readPlist(prefsPath)
+        prefs = readPlist(prefsPath)
         try:
             return prefs['Disabled']
         except KeyError:
@@ -238,11 +238,11 @@
 
     overridesPath = os.path.join(target, LAUNCHD_OVERRIDES)
     if os.path.isfile(overridesPath):
-        overrides = plistlib.readPlist(overridesPath)
+        overrides = readPlist(overridesPath)
         if not overrides.has_key(service):
             overrides[service] = { }
         overrides[service]['Disabled'] = disabled
-        plistlib.writePlist(overrides, overridesPath)
+        writePlist(overrides, overridesPath)
 
 
 class ServiceStateError(Exception):

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/doc/Admin/ExtendedLogItems.txt
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/doc/Admin/ExtendedLogItems.txt	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/doc/Admin/ExtendedLogItems.txt	2010-03-01 18:49:46 UTC (rev 5225)
@@ -63,6 +63,10 @@
 
     Either ``reply`` or ``cancel`` depending on...???
 
+  ``fwd``
+
+    the value of the X-Forwarded-For header, if present
+
 In the following example, we see a ``CalDAV:calendar-multiget``
 ``REPORT`` for 32 resources in a user's calendar, which was handled by
 instance ``8459`` in 183.0ms, with one outstanding request (the one

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/run
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/run	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/run	2010-03-01 18:49:46 UTC (rev 5225)
@@ -157,7 +157,15 @@
   fi;
 
   if "${kill}" || "${restart}"; then
+    # mimic logic of 'fullServerPath' from twistedcaldav/config.py to find the pid file
     pidfile="$(conf_read_key "PIDFile")";
+    serverroot="$(conf_read_key "ServerRoot")";
+    runroot="$(conf_read_key "RunRoot")";
+    # examine first character of $pidfile
+    if ( [ "${pidfile:0:1}" == "/" ] || [ "${pidfile:0:1}" == "." ]; ) then
+        pidfile=$pidfile;
+        else pidfile=${serverroot}/${runroot}/${pidfile};
+    fi
     if [ ! -r "${pidfile}" ]; then
       echo "Unreadable PID file: ${pidfile}";
       exit 1

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/python/datetime.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/python/datetime.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/python/datetime.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -19,28 +19,36 @@
 """
 
 __all__ = [
+    "utc",
+    "tzWithID",
     "dateordatetime",
     "timerange",
-    "utc",
+    "asTimeZone",
+    "asUTC",
+    "iCalendarString",
 ]
 
 date     = __import__("datetime").date
 datetime = __import__("datetime").datetime
 
-from vobject.icalendar import dateTimeToString, dateToString, utc
+from vobject.icalendar import dateTimeToString, dateToString
+from vobject.icalendar import utc, getTzid as tzWithID
 
 
+# FIXME, add constants for begining/end of time
+
 class dateordatetime(object):
     def __init__(self, dateOrDatetime, defaultTZ=None):
         """
         @param dateOrDatetime: a L{date} or L{datetime}.
         """
-        assert dateOrDatetime is not None
+        assert dateOrDatetime is not None, "dateOrDatetime is None"
 
         self._dateOrDatetime = dateOrDatetime
         if isinstance(dateOrDatetime, datetime):
             self._isDatetime = True
         else:
+            assert isinstance(dateOrDatetime, date)
             self._isDatetime = False
         self.defaultTZ = defaultTZ
 
@@ -68,22 +76,43 @@
 
     def __eq__(self, other):
         if isinstance(other, dateordatetime):
-            other = other.dateOrDatetime
-        return self._dateOrDatetime == other
+            other = other.dateOrDatetime()
+        dt1, dt2 = self._comparableDatetimes(other)
+        return dt1 == dt2
 
-    def __cmp__(self, other):
-        if not isinstance(other, (date, datetime, dateordatetime)):
+    def __ne__(self, other):
+        if isinstance(other, dateordatetime):
+            other = other.dateOrDatetime()
+        dt1, dt2 = self._comparableDatetimes(other)
+        return dt1 != dt2
+
+    def __lt__(self, other):
+        if not isinstance(other, comparableTypes):
             return NotImplemented
+        dt1, dt2 = self._comparableDatetimes(other)
+        return dt1 < dt2
 
+    def __le__(self, other):
+        if not isinstance(other, comparableTypes):
+            return NotImplemented
         dt1, dt2 = self._comparableDatetimes(other)
+        return dt1 <= dt2
 
-        if dt1 == dt2:
-            return 0
-        elif dt1 < dt2:
-            return -1
-        else:
-            return 1
+    def __gt__(self, other):
+        if not isinstance(other, comparableTypes):
+            return NotImplemented
+        dt1, dt2 = self._comparableDatetimes(other)
+        return dt1 > dt2
 
+    def __ge__(self, other):
+        if not isinstance(other, comparableTypes):
+            return NotImplemented
+        dt1, dt2 = self._comparableDatetimes(other)
+        return dt1 >= dt2
+
+    def __hash__(self):
+        return self._dateOrDatetime.__hash__()
+
     def __sub__(self, other):
         if not isinstance(other, (date, datetime, dateordatetime)):
             return NotImplemented
@@ -126,7 +155,9 @@
     def asUTC(self):
         return self.asTimeZone(utc)
 
+comparableTypes = (date, datetime, dateordatetime)
 
+
 class timerange(object):
     def __init__(self, start=None, end=None, duration=None):
         """
@@ -135,7 +166,7 @@
         @param duration: a L{timedelta}, L{date} or L{datetime}
         @param tzinfo: a L{tzinfo}
         """
-        assert end is None or duration is None
+        assert end is None or duration is None, "end or duration must be None"
 
         if start is None or isinstance(start, dateordatetime):
             self._start = start
@@ -151,6 +182,54 @@
         if duration is not None:
             self._duration = duration
 
+    def __repr__(self):
+        return "timerange(%r, %s)" % (self.start(), self.end())
+
+    def __eq__(self, other):
+        if not isinstance(other, timerange):
+            return NotImplemented
+        if self.start() != other.start():
+            return False
+        return self.end() == other.end()
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __lt__(self, other):
+        if not isinstance(other, timerange):
+            return NotImplemented
+        if self.start() == other.start():
+            return self.end() < other.end()
+        else:
+            return self.start() < other.start()
+
+    def __le__(self, other):
+        if not isinstance(other, timerange):
+            return NotImplemented
+        if self.start() == other.start():
+            return self.end() <= other.end()
+        else:
+            return self.start() <= other.start()
+
+    def __gt__(self, other):
+        if not isinstance(other, timerange):
+            return NotImplemented
+        if self.start() == other.start():
+            return self.end() > other.end()
+        else:
+            return self.start() > other.start()
+
+    def __ge__(self, other):
+        if not isinstance(other, timerange):
+            return NotImplemented
+        if self.start() == other.start():
+            return self.end() >= other.end()
+        else:
+            return self.start() >= other.start()
+
+    def __hash__(self, other):
+        return hash((self.start(), self.end()))
+
     def start(self):
         return self._start
 
@@ -193,3 +272,27 @@
             return self.end() < other.end() and self.end() > other.start()
         else:
             return False
+
+
+##
+# Convenience functions
+##
+
+def asTimeZone(dateOrDatetime, tzinfo):
+    """
+    Convert a L{date} or L{datetime} to the given time zone.
+    """
+    return dateordatetime(dateOrDatetime).asTimeZone(tzinfo).dateOrDatetime()
+
+def asUTC(dateOrDatetime):
+    """
+    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
+    in an iCalendar property.
+    """
+    return dateordatetime(dateOrDatetime).iCalendarString()

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/python/test/test_datetime.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/python/test/test_datetime.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/python/test/test_datetime.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -15,48 +15,132 @@
 ##
 
 from datetime import date, datetime, timedelta
+from dateutil.tz import tzstr
 
+from twisted.internet.defer import DeferredList
+
 from twext.python.datetime import dateordatetime, timerange, utc
 
-from twistedcaldav.test.util import TestCase, testUnimplemented
+from twistedcaldav.test.util import TestCase, featureUnimplemented, testUnimplemented
 
 
-class DateTimeTests(TestCase):
-    def test_date_date(self):
-        d = date.today()
-        dodt = dateordatetime(d)
-        self.assertEquals(dodt.date(), d)
+tzUSEastern = tzstr("EST5EDT")
 
-    def test_date_date_tz(self):
+
+def timezones(f):
+    """
+    Decorator for a test to be called with multiple timezones.
+    """
+    return lambda self: DeferredList([
+        d for d in (
+            f(self, tz) for tz in (None, utc, tzUSEastern)
+        ) if d is not None
+    ])
+
+def timeSeries(n):
+    now = datetime.now()
+    for i in range(0, n):
+        dodt = dateordatetime(now + timedelta(days=i))
+        dodt.n = "t%d" %(i+1,)
+        yield dodt
+
+
+class DatetimeTests(TestCase):
+    @timezones
+    def test_date_date(self, tz):
         d = date.today()
-        dodt = dateordatetime(d, defaultTZ=utc)
+        dodt = dateordatetime(d, defaultTZ=tz)
         self.assertEquals(dodt.date(), d)
 
-    def test_date_datetime(self):
+    @timezones
+    def test_date_datetime(self, tz):
         d = date.today()
-        dodt = dateordatetime(d)
-        self.assertEquals(dodt.datetime(), datetime(d.year, d.month, d.day))
+        dodt = dateordatetime(d, defaultTZ=tz)
+        self.assertEquals(dodt.datetime(), datetime(d.year, d.month, d.day, tzinfo=tz))
 
-    def test_date_datetime_tz(self):
-        d = date.today()
-        dodt = dateordatetime(d, defaultTZ=utc)
-        self.assertEquals(dodt.datetime(), datetime(d.year, d.month, d.day, tzinfo=utc))
-
     def test_datetime_date(self):
         dt = datetime.now()
         dodt = dateordatetime(dt)
         self.assertEquals(dodt.date(), dt.date())
 
-    def test_datetime_datetime(self):
+    @timezones
+    def test_datetime_datetime(self, tz):
         dt = datetime.now()
-        dodt = dateordatetime(dt)
+        dodt = dateordatetime(dt, defaultTZ=tz)
         self.assertEquals(dodt.datetime(), dt)
 
-    def test_datetime_datetime_tz(self):
-        dt = datetime.now()
-        dodt = dateordatetime(dt, defaultTZ=utc)
-        self.assertEquals(dodt.datetime(), dt)
+    def test_compare_date_date(self):
+        return self._test_compare(date, date.today())
 
+    @timezones
+    def test_compare_date_datetime(self, tz):
+        return self._test_compare(date, datetime.now(), tz=tz)
+
+    def test_compare_datetime_date(self):
+        return self._test_compare(datetime, date.today())
+
+    @timezones
+    def test_compare_datetime_datetime(self, tz):
+        return self._test_compare(datetime, datetime.now(), tz=tz)
+
+    def _test_compare(self, baseclass, now, tz=None):
+        first  = dateordatetime(now + timedelta(days=0))
+        second = dateordatetime(now + timedelta(days=1))
+        third  = dateordatetime(now + timedelta(days=2))
+
+        def base(dodt):
+            if tz:
+                return dodt.dateOrDatetime().replace(tzinfo=tz)
+            else:
+                return dodt.dateOrDatetime()
+
+        #
+        # date & datetime's comparators do not correctly return
+        # NotImplemented when they should, which breaks comparison
+        # operators if date/datetime is first.  Boo.  Seriously weak.
+        #
+
+        self.assertTrue (first        == base(first) )
+       #self.assertTrue (base(first)  == first       ) # Bug in datetime
+        self.assertTrue (first        == base(first) )
+        self.assertTrue (first        != base(second))
+        self.assertTrue (base(first)  != second      )
+        self.assertTrue (first        != second      )
+        self.assertTrue (first        <  second      )
+        self.assertTrue (second       <  third       )
+        self.assertTrue (first        <  base(second))
+       #self.assertTrue (base(second) <  third       ) # Bug in datetime
+        self.assertTrue (first        <  second      )
+        self.assertTrue (second       <  third       )
+       #self.assertTrue (base(first)  <  second      )
+        self.assertTrue (second       <  base(third) ) # Bug in datetime
+        self.assertTrue (first        <= second      )
+        self.assertTrue (second       <= third       )
+        self.assertTrue (first        <= base(second))
+       #self.assertTrue (base(second) <= third       ) # Bug in datetime
+        self.assertTrue (first        <= base(second))
+       #self.assertTrue (base(second) <= third       ) # Bug in datetime
+        self.assertTrue (first        <= second      )
+        self.assertTrue (second       <= third       )
+       #self.assertTrue (base(first)  <= second      ) # Bug in datetime
+        self.assertTrue (second       <= base(third) )
+        self.assertFalse(first        >  second      )
+        self.assertFalse(second       >  third       )
+        self.assertFalse(first        >  base(second))
+       #self.assertFalse(base(second) >  third       ) # Bug in datetime
+        self.assertFalse(first        >  second      )
+        self.assertFalse(second       >  third       )
+       #self.assertFalse(base(first)  >  second      ) # Bug in datetime
+        self.assertFalse(second       >  base(third) )
+        self.assertFalse(first        >= second      )
+        self.assertFalse(second       >= third       )
+        self.assertFalse(first        >= base(second))
+       #self.assertFalse(base(second) >= third       ) # Bug in datetime
+        self.assertFalse(first        >= second      )
+        self.assertFalse(second       >= third       )
+       #self.assertFalse(base(first)  >= second      ) # Bug in datetime
+        self.assertFalse(second       >= base(third) )
+
     def test_date_iCalendarString(self):
         d = date(2010, 2, 22)
         dodt = dateordatetime(d)
@@ -72,20 +156,29 @@
         dodt = dateordatetime(dt)
         self.assertEquals(dodt.iCalendarString(), "20100222T174442Z")
 
-    @testUnimplemented
     def test_datetime_iCalendarString_tz(self):
-        # Need to test a non-UTC timezone also
-        raise NotImplementedError()
+        dt = datetime(2010, 2, 22, 17, 44, 42, 98303, tzinfo=tzUSEastern)
+        dodt = dateordatetime(dt)
+        self.assertEquals(dodt.iCalendarString(), "20100222T174442")
 
-    @testUnimplemented
     def test_asTimeZone(self):
-        raise NotImplementedError()
+        dt = datetime(2010, 2, 22, 17, 44, 42, 98303, tzinfo=utc)
+        asUTC = dateordatetime(dt)
+        asEast = asUTC.asTimeZone(tzUSEastern)
+        self.assertEquals(asEast.datetime().tzinfo, tzUSEastern) # tz is changed
+        self.assertEquals(asEast.datetime().hour, 12)            # hour is changed
+        self.assertEquals(asUTC, asEast)                         # still equal
 
-    @testUnimplemented
     def test_asUTC(self):
-        raise NotImplementedError()
+        dt = datetime(2010, 2, 22, 17, 44, 42, 98303, tzinfo=tzUSEastern)
+        asEast = dateordatetime(dt)
+        asUTC = asEast.asTimeZone(utc)
+        self.assertEquals(asUTC.datetime().tzinfo, utc) # tz is changed
+        self.assertEquals(asUTC.datetime().hour, 22)    # hour is changed
+        self.assertEquals(asEast, asUTC)                # still equal
 
-class TimeRangeTests(TestCase):
+
+class TimerangeTests(TestCase):
     def test_start(self):
         start = datetime.now()
         tr = timerange(start=start)
@@ -137,10 +230,212 @@
         self.assertEquals(tr.duration(), duration)
 
     @testUnimplemented
+    def test_compare(self):
+        raise NotImplemented
+
+    @featureUnimplemented
     def test_overlapsWith(self):
-        # Need a few tests; combinations of:
-        #  - start/end are None
-        #  - overlapping and not
-        #  - dates and datetimes
-        #  - timezones
-        raise NotImplementedError()
+        t1, t2, t3, t4 = timeSeries(4)
+
+        d1 = dateordatetime(t1.date()); d1.n = "d1"
+        d2 = dateordatetime(t2.date()); d2.n = "d2"
+        d3 = dateordatetime(t3.date()); d3.n = "d3"
+        d4 = dateordatetime(t4.date()); d4.n = "d4"
+
+        for start1, end1, start2, end2, overlaps in (
+            # T-T-T-T
+
+            (t1, t2, t1, t2, True ),
+            (t1, t2, t1, t3, True ),
+            (t1, t2, t2, t3, False),
+            (t1, t2, t3, t4, False),
+
+            (t1, t3, t1, t2, True ),
+            (t1, t3, t2, t3, True ),
+
+            (t2, t3, t1, t2, False),
+            (t2, t3, t1, t3, True ),
+            (t2, t3, t1, t4, True ),
+
+            (t2, t4, t1, t3, True ),
+
+            (t3, t4, t1, t2, False),
+
+            # D-T-T-T
+
+            (d1, t2, t1, t2, True ),
+            (d1, t2, t1, t3, True ),
+            (d1, t2, t2, t3, False),
+            (d1, t2, t3, t4, False),
+
+            (d1, t3, t1, t2, True ),
+            (d1, t3, t2, t3, True ),
+
+            (d2, t3, t1, t2, False),
+            (d2, t3, t1, t3, True ),
+            (d2, t3, t1, t4, True ),
+
+            (d2, t4, t1, t3, True ),
+
+            (d3, t4, t1, t2, False),
+
+            # T-D-T-T
+
+            (t1, d2, t1, t2, True ),
+            (t1, d2, t1, t3, True ),
+            (t1, d2, t2, t3, False),
+            (t1, d2, t3, t4, False),
+
+            (t1, d3, t1, t2, True ),
+            (t1, d3, t2, t3, True ),
+
+            (t2, d3, t1, t2, False),
+            (t2, d3, t1, t3, True ),
+            (t2, d3, t1, t4, True ),
+
+            (t2, d4, t1, t3, True ),
+
+            (t3, d4, t1, t2, False),
+
+            # T-T-D-T
+
+            (t1, t2, d1, t2, True ),
+            (t1, t2, d1, t3, True ),
+            (t1, t2, d2, t3, False),
+            (t1, t2, d3, t4, False),
+
+            (t1, t3, d1, t2, True ),
+            (t1, t3, d2, t3, True ),
+
+            (t2, t3, d1, t2, False),
+            (t2, t3, d1, t3, True ),
+            (t2, t3, d1, t4, True ),
+
+            (t2, t4, d1, t3, True ),
+
+            (t3, t4, d1, t2, False),
+
+            # T-T-T-D
+
+            (t1, t2, t1, d2, True ),
+            (t1, t2, t1, d3, True ),
+            (t1, t2, t2, d3, False),
+            (t1, t2, t3, d4, False),
+
+            (t1, t3, t1, d2, True ),
+            (t1, t3, t2, d3, True ),
+
+            (t2, t3, t1, d2, False),
+            (t2, t3, t1, d3, True ),
+            (t2, t3, t1, d4, True ),
+
+            (t2, t4, t1, d3, True ),
+
+            (t3, t4, t1, d2, False),
+
+            # D-D-T-T
+
+            (d1, d2, t1, t2, True ),
+            (d1, d2, t1, t3, True ),
+            (d1, d2, t2, t3, False),
+            (d1, d2, t3, t4, False),
+
+            (d1, d3, t1, t2, True ),
+            (d1, d3, t2, t3, True ),
+
+            (d2, d3, t1, t2, False),
+            (d2, d3, t1, t3, True ),
+            (d2, d3, t1, t4, True ),
+
+            (d2, d4, t1, t3, True ),
+
+            (d3, d4, t1, t2, False),
+
+            # T-D-D-T
+
+            (t1, d2, d1, t2, True ),
+            (t1, d2, d1, t3, True ),
+            (t1, d2, d2, t3, False),
+            (t1, d2, d3, t4, False),
+
+            (t1, d3, d1, t2, True ),
+            (t1, d3, d2, t3, True ),
+
+            (t2, d3, d1, t2, False),
+            (t2, d3, d1, t3, True ),
+            (t2, d3, d1, t4, True ),
+
+            (t2, d4, d1, t3, True ),
+
+            (t3, d4, d1, t2, False),
+
+            # D-T-D-T
+
+            (d1, t2, d1, t2, True ),
+            (d1, t2, d1, t3, True ),
+            (d1, t2, d2, t3, False),
+            (d1, t2, d3, t4, False),
+
+            (d1, t3, d1, t2, True ),
+            (d1, t3, d2, t3, True ),
+
+            (d2, t3, d1, t2, False),
+            (d2, t3, d1, t3, True ),
+            (d2, t3, d1, t4, True ),
+
+            (d2, t4, d1, t3, True ),
+
+            (d3, t4, d1, t2, False),
+
+            # T-T-D-D
+
+            (t1, t2, d1, d2, True ),
+            (t1, t2, d1, d3, True ),
+            (t1, t2, d2, d3, False),
+            (t1, t2, d3, d4, False),
+
+            (t1, t3, d1, d2, True ),
+            (t1, t3, d2, d3, True ),
+
+            (t2, t3, d1, d2, False),
+            (t2, t3, d1, d3, True ),
+            (t2, t3, d1, d4, True ),
+
+            (t2, t4, d1, d3, True ),
+
+            (t3, t4, d1, d2, False),
+
+            # D-D-D-D
+
+            (d1, d2, d1, d2, True ),
+            (d1, d2, d1, d3, True ),
+            (d1, d2, d2, d3, False),
+            (d1, d2, d3, d4, False),
+
+            (d1, d3, d1, d2, True ),
+            (d1, d3, d2, d3, True ),
+
+            (d2, d3, d1, d2, False),
+            (d2, d3, d1, d3, True ),
+            (d2, d3, d1, d4, True ),
+
+            (d2, d4, d1, d3, True ),
+
+            (d3, d4, d1, d2, False),
+        ):
+            #print start1.n, end1.n, start2.n, end2.n, overlaps
+
+            if overlaps:
+                test = self.assertTrue
+                error = "should overlap with"
+            else:
+                test = self.assertFalse
+                error = "should not overlap with"
+
+            tr1 = timerange(start1, end1)
+            tr2 = timerange(start2, end2)
+
+            test(
+                tr1.overlapsWith(tr2),
+                "%r (%s-%s) %s %r (%s-%s)" % (tr1, start1.n, end1.n, error, tr2, start2.n, end2.n)
+            )

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/dateops.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/dateops.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/dateops.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -19,13 +19,10 @@
 """
 
 __all__ = [
-    "normalizeStartEndDuration",
-    "normalizeToUTC",
     "normalizeForIndex",
     "floatoffset",
     "compareDateTime",
     "differenceDateTime",
-    "makeComparableDateTime",
     "timeRangesOverlap",
     "periodEnd",
     "normalizePeriodList",
@@ -33,53 +30,10 @@
 ]
 
 import datetime
-from vobject.icalendar import utc, dateTimeToString, dateToString
+from vobject.icalendar import utc
 
-def toString(dt):
-    """
-    Convert a L{datetime.date} or L{datetime.datetime} object to a string.
-    @param dt: a L{datetime.date} or L{datetime.datetime} object to normalize
-    @return: the converted date or datetime
-    """
-    if not isinstance(dt, datetime.date):
-        raise TypeError("%r is not a datetime.date instance" % (dt,))
-    
-    return dateTimeToString(dt) if isinstance(dt, datetime.datetime) else dateToString(dt)
+from twext.python.datetime import dateordatetime
 
-def normalizeStartEndDuration(dtstart, dtend=None, duration=None):
-    """
-    Given a DTSTART and DTEND or DURATION (or neither), return a normalized tuple of
-    DTSTART and DTEND.
-    """
-    
-    assert dtend is None or duration is None, "Cannot specify both dtend and duration"
-    if dtstart is not None:
-        dtstart = normalizeToUTC(dtstart)
-    if dtend is not None:
-        dtend = normalizeToUTC(dtend)
-    elif duration:
-        dtend = dtstart + duration
-    
-    return (dtstart, dtend)
-
-def normalizeToUTC(dt):
-    """
-    Normalize a L{datetime.date} or L{datetime.datetime} object to UTC.
-    If its a L{datetime.date}, just return it as-is.
-    @param dt: a L{datetime.date} or L{datetime.datetime} object to normalize
-    @return: the normalized date or datetime
-    """
-    if not isinstance(dt, datetime.date):
-        raise TypeError("%r is not a datetime.date instance" % (dt,))
-    
-    if isinstance(dt, datetime.datetime):
-        if dt.tzinfo is not None:
-            return dt.astimezone(utc)
-        else:
-            return dt
-    else:
-        return dt
-
 def normalizeForIndex(dt):
     """
     Normalize a L{datetime.date} or L{datetime.datetime} object for use in the Index.
@@ -113,22 +67,9 @@
         tzinfo = utc
     return dt.astimezone(tzinfo).replace(tzinfo=utc)
 
-def compareDateTime(dt1, dt2, defaulttz = None):
-    """
-    Compare two L{datetime.date} or L{datetime.datetime} objects in
-    a transparent manner that does not depend on the nature of the objects
-    and whether timesones are set.
-    @param dt1: a L{datetime.datetime} or L{datetime.date} specifying a date to test.
-    @param dt2: a L{datetime.datetime} or L{datetime.date} specifying a date to test.
-    @param defaulttz: a L{datetime.tzinfo} for the VTIMEZONE object to use if one of the
-        datetime's is a date or floating.
-    @return:  0 if dt1 == dt2,
-        -1 if dt1 < dt2
-        1 if dt1 > dt2
-    """
-
-    dt1, dt2 = makeComparableDateTime(dt1, dt2, defaulttz)
-
+def compareDateTime(dt1, dt2, defaulttz=None):
+    dt1 = dateordatetime(dt1, defaultTZ=defaulttz)
+    dt2 = dateordatetime(dt2, defaultTZ=defaulttz)
     if dt1 == dt2:
         return 0
     elif dt1 < dt2:
@@ -137,78 +78,21 @@
         return 1
 
 def differenceDateTime(start, end, defaulttz = None):
-    """
-    Determines the difference between start and end datetime's returning the duration.
-    NB This handles the case where start and end are not of the same datetime type.
-    
-    @param start: a L{datetime.datetime} or L{datetime.date} specify the start time.
-    @param end: a L{datetime.datetime} or L{datetime.date} specify the end time.
-    @param defaulttz: a L{datetime.tzinfo} for the VTIMEZONE object to use if one of the
-        datetime's is a date or floating.
-    @return: the L{datetime.timedelta} for the difference between the two.
-    """
+    return dateordatetime(end, defaultTZ=defaulttz) - dateordatetime(start)
 
-    start, end = makeComparableDateTime(start, end, defaulttz)
-    return end - 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)
 
-def makeComparableDateTime(dt1, dt2, defaulttz = None):  
-    """
-    Ensure that the two datetime objects passed in are of a comparable type for arithmetic
-    and comparison operations..
-    
-    @param start: a L{datetime.datetime} or L{datetime.date} specifying one time.
-    @param end: a L{datetime.datetime} or L{datetime.date} specifying another time.
-    @param defaulttz: a L{datetime.tzinfo} for the VTIMEZONE object to use if one of the
-        datetime's is a date or floating.
-    @return: a C{tuple} of two L{datetime.xx}'s for the comparable items.
-    """
-
-    for dt in (dt1, dt2):
-        if not isinstance(dt, datetime.date):
-            raise TypeError("%r is not a datetime.date instance" % (dt,))
-
-    # Pick appropriate tzinfo
-    tzi = [None]
-    def getTzinfo(dtzi):
-        if tzi[0] is None:
-            if defaulttz is not None:
-                tzi[0] = defaulttz
-            else:
-                return dtzi
-        return tzi[0]
-
-    # If any one argument is a datetime.date, convert that into a datetime.datetime
-    # with the time set to midnight and the same timezone as the other argument
-    if isinstance(dt1, datetime.datetime) and not isinstance(dt2, datetime.datetime):
-        dt2 = datetime.datetime(dt2.year, dt2.month, dt2.day, 0, 0, 0, 0, getTzinfo(dt1.tzinfo))
-    elif not isinstance(dt1, datetime.datetime) and isinstance(dt2, datetime.datetime):
-        dt1 = datetime.datetime(dt1.year, dt1.month, dt1.day, 0, 0, 0, 0, getTzinfo(dt2.tzinfo))
-    elif isinstance(dt1, datetime.datetime) and isinstance(dt2, datetime.datetime):
-        # Ensure that they both have or have not a tzinfo
-        if (dt1.tzinfo is not None and dt2.tzinfo is None):
-            dt2 = dt2.replace(tzinfo=getTzinfo(dt1.tzinfo))
-        elif (dt1.tzinfo is None and dt2.tzinfo is not None):
-            dt1 = dt1.replace(tzinfo=getTzinfo(dt2.tzinfo))
-    
-    return (dt1, dt2)
-
 def timeRangesOverlap(start1, end1, start2, end2, defaulttz = None):
-    """
-    Determines whether two time ranges overlap.
-    @param start1: a L{datetime.datetime} or L{datetime.date} specifying the
-        beginning of the first time span.
-    @param end1: a L{datetime.datetime} or L{datetime.date} specifying the
-        end of the first time span.  C{end} may be None, indicating that
-        there is no end date.
-    @param start2: a L{datetime.datetime} or L{datetime.date} specifying the
-        beginning of the second time span.
-    @param end2: a L{datetime.datetime} or L{datetime.date} specifying the
-        end of the second time span.  C{end} may be None, indicating that
-        there is no end date.
-    @param defaulttz: a L{datetime.tzinfo} for the VTIMEZONE object to use if one of the
-        datetime's is a date or floating.
-    @return: True if the two given time spans overlap, False otherwise.
-    """
     # 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()

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/addressbook.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/addressbook.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/addressbook.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -36,7 +36,7 @@
 
 from twistedcaldav.config import config
 from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource
-from twistedcaldav.resource import CalDAVResource, SearchAddressBookResource, SearchAllAddressBookResource
+from twistedcaldav.resource import CalDAVResource, SearchAddressBookResource, SearchAllAddressBookResource, CalDAVComplianceMixIn
 from twistedcaldav.directory.idirectory import IDirectoryService
 from twistedcaldav.directory.resource import AutoProvisioningResourceMixIn
 
@@ -51,6 +51,7 @@
 class DirectoryAddressBookProvisioningResource (
     AutoProvisioningResourceMixIn,
     ReadOnlyResourceMixIn,
+    CalDAVComplianceMixIn,
     DAVResource,
 ):
     def defaultAccessControlList(self):

Copied: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/test/test_wiki.py (from rev 5224, CalendarServer/trunk/twistedcaldav/directory/test/test_wiki.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/test/test_wiki.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/test/test_wiki.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -0,0 +1,34 @@
+##
+# Copyright (c) 2005-2010 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.test.util import TestCase
+from twistedcaldav.directory.wiki import WikiDirectoryService, WikiDirectoryRecord
+
+class WikiTestCase(TestCase):
+    """
+    Test the Wiki Directory Service
+    """
+
+    def test_enabled(self):
+        service = WikiDirectoryService()
+        service.realmName = "Test"
+        record = WikiDirectoryRecord(service,
+            WikiDirectoryService.recordType_wikis,
+            "test",
+            None
+        )
+        self.assertTrue(record.enabled)
+        self.assertTrue(record.enabledForCalendaring)

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/wiki.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/wiki.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/wiki.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -123,6 +123,8 @@
             enabledForCalendaring=True,
             uid="%s%s" % (WikiDirectoryService.UIDPrefix, shortName),
         )
+        # Wiki enabling doesn't come from augments db, so enable here...
+        self.enabled = True
 
 
 

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/ical.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/ical.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -42,13 +42,12 @@
 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.web2.dav.util import allDataFromStream
+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
 
-from twext.python.log import Logger
-
-from twistedcaldav.dateops import compareDateTime, normalizeToUTC, timeRangesOverlap,\
-    normalizeStartEndDuration, toString, normalizeForIndex, differenceDateTime
+from twistedcaldav.dateops import timeRangesOverlap, normalizeForIndex, differenceDateTime
 from twistedcaldav.instance import InstanceList
 from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
 
@@ -485,7 +484,7 @@
             if component.name() == "VTIMEZONE":
                 continue
             rid = component.getRecurrenceIDUTC()
-            if rid and recurrence_id and compareDateTime(rid, recurrence_id) == 0:
+            if rid and recurrence_id and dateordatetime(rid) == recurrence_id:
                 return component
             elif rid is None and recurrence_id is None:
                 return component
@@ -623,7 +622,7 @@
         """
         dtstart = self.propertyNativeValue("DTSTART")
         if dtstart is not None:
-            return normalizeToUTC(dtstart)
+            return asUTC(dtstart)
         else:
             return None
  
@@ -643,7 +642,7 @@
                 dtend = dtstart + duration
 
         if dtend is not None:
-            return normalizeToUTC(dtend)
+            return asUTC(dtend)
         else:
             return None
 
@@ -662,7 +661,7 @@
                 due = dtstart + duration
 
         if due is not None:
-            return normalizeToUTC(due)
+            return asUTC(due)
         else:
             return None
  
@@ -675,7 +674,7 @@
         rid = self.propertyNativeValue("RECURRENCE-ID")
 
         if rid is not None:
-            return normalizeToUTC(rid)
+            return asUTC(rid)
         else:
             return None
  
@@ -1104,7 +1103,7 @@
         # Check whether recurrence-id matches an RDATE - if so it is OK
         rdates = set()
         for rdate in master.properties("RDATE"):
-            rdates.update([normalizeToUTC(item) for item in rdate.value()])
+            rdates.update([asUTC(item) for item in rdate.value()])
         if rid not in rdates:
             # Check whether we have a truncated RRULE
             rrules = master.properties("RRULE")
@@ -1847,7 +1846,7 @@
                 remaining -= 1
                 continue
             rid = component.getRecurrenceIDUTC()
-            if (toString(rid) if rid else "") not in rids:
+            if (iCalendarString(rid) if rid else "") not in rids:
                 self.removeComponent(component)
                 remaining -= 1
                 
@@ -2012,28 +2011,28 @@
             dtend = self.getProperty("DTEND")
             duration = self.getProperty("DURATION")
             
-            newdtstart, newdtend = normalizeStartEndDuration(
-                dtstart.value(),
-                dtend.value() if dtend is not None else None,
-                duration.value() if duration is not None else None,
+            timeRange = timerange(
+                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(newdtstart)
+
+            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"]
             if dtend is not None:
-                dtend.setValue(newdtend)
+                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"]
             elif duration is not None:
                 self.removeProperty(duration)
-                self.addProperty(Property("DTEND", newdtend))
+                self.addProperty(Property("DTEND", timeRange.end().asUTC().dateOrDatetime()))
 
             exdates = self.properties("EXDATE")
             for exdate in exdates:
-                exdate.setValue([normalizeToUTC(value) for value in exdate.value()])
+                exdate.setValue([asUTC(value) for value in exdate.value()])
                 try:
                     del exdate.params()["TZID"]
                 except KeyError:
@@ -2041,7 +2040,7 @@
 
             rid = self.getProperty("RECURRENCE-ID")
             if rid is not None:
-                rid.setValue(normalizeToUTC(rid.value()))
+                rid.setValue(asUTC(rid.value()))
                 try:
                     del rid.params()["TZID"]
                 except KeyError:

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/instance.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/instance.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/instance.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -20,10 +20,12 @@
 
 import datetime
 
-from twistedcaldav.dateops import normalizeForIndex, compareDateTime, differenceDateTime, periodEnd
-
 from vobject.icalendar import utc
 
+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
@@ -279,7 +281,7 @@
 
     def _addMasterComponent(self, component, limit, start, end, duration):
         # Always add first instance if included in range.
-        if compareDateTime(start, limit) < 0:
+        if dateordatetime(start) < limit:
             # dateutils does not do all-day - so convert to datetime.datetime
             start = normalizeForIndex(start)
             end = normalizeForIndex(end)
@@ -299,7 +301,7 @@
         recur = component.getRRuleSet(True)
         if recur is not None:
             for startDate in recur:
-                if compareDateTime(startDate, limit) >= 0:
+                if dateordatetime(startDate) >= limit:
                     self.limit = limit
                     break
                 endDate = startDate + duration
@@ -319,12 +321,12 @@
         rid = normalizeForIndex(rid)
 
         # Make sure start is within the limit
-        if compareDateTime(start, limit) > 0 and compareDateTime(rid, limit) > 0:
+        if dateordatetime(start) > limit and dateordatetime(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 compareDateTime(rid, limit) <= 0:
+            if str(rid) not in self.instances and dateordatetime(rid) <= limit:
                 if self.ignoreInvalidInstances:
                     return
                 else:
@@ -373,7 +375,7 @@
         """
 
         start = component.getStartDateUTC()
-        if start is not None and (compareDateTime(start, limit) >= 0):
+        if start is not None and dateordatetime(start) >= limit:
             # If the free busy is beyond the end of the range we want, ignore it
             return
 
@@ -387,7 +389,7 @@
             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 compareDateTime(period[0], limit) >= 0:
+                if dateordatetime(period[0]) >= limit:
                     continue
                 start = normalizeForIndex(period[0])
                 end = normalizeForIndex(periodEnd(period))
@@ -405,7 +407,7 @@
         """
 
         start = component.getStartDateUTC()
-        if start is not None and (compareDateTime(start, limit) >= 0):
+        if start is not None and dateordatetime(start) >= limit:
             # If the free busy is beyond the end of the range we want, ignore it
             return
         if start is None:

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/report_addressbook_findshared.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/report_addressbook_findshared.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/report_addressbook_findshared.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -26,7 +26,7 @@
     "getWritersGroupForSharedAddressBookGroup",
 ]
 
-from plistlib import readPlist
+from twext.python.plistlib import readPlist
 #import traceback
 
 import opendirectory

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/icaldiff.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/icaldiff.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/icaldiff.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -15,10 +15,9 @@
 ##
 
 from twext.python.log import Logger
+from twext.python.datetime import timerange, asUTC, iCalendarString
 
 from twistedcaldav.config import config
-from twistedcaldav.dateops import normalizeToUTC, toString,\
-    normalizeStartEndDuration
 from twistedcaldav.ical import Component, Property
 from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
 from twistedcaldav.scheduling.itip import iTipGenerator
@@ -281,7 +280,7 @@
                 # Get all EXDATEs in UTC
                 exdates = set()
                 for exdate in master.properties("EXDATE"):
-                    exdates.update([normalizeToUTC(value) for value in exdate.value()])
+                    exdates.update([asUTC(value) for value in exdate.value()])
                
             return exdates, map, master
         
@@ -330,7 +329,7 @@
                     # Mark Attendee as DECLINED in the server instance
                     if self._attendeeDecline(self.newCalendar.overriddenComponent(rid)):
                         changeCausesReply = True
-                        changedRids.append(toString(rid) if rid else "")
+                        changedRids.append(iCalendarString(rid) 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
@@ -406,7 +405,7 @@
                 #return False, False, (), None
             changeCausesReply |= reply
             if reply:
-                changedRids.append(toString(rid) if rid else "")
+                changedRids.append(iCalendarString(rid) if rid else "")
 
         # We need to derive instances for any declined using an EXDATE
         for decline in sorted(declines):
@@ -417,7 +416,7 @@
                     self.newCalendar.addComponent(overridden)
                     if self._attendeeDecline(overridden):
                         changeCausesReply = True
-                        changedRids.append(toString(decline) if decline else "")
+                        changedRids.append(iCalendarString(decline) if decline else "")
                 else:
                     self._logDiffError("attendeeMerge: Unable to override an instance to mark as DECLINED: %s" % (decline,))
                     return False, False, (), None
@@ -539,7 +538,7 @@
             # Bad if EXDATEs have been removed
             missing = serverProps[-1] - clientProps[-1]
             if missing:
-                log.debug("EXDATEs missing: %s" % (", ".join([toString(exdate) for exdate in missing]),))
+                log.debug("EXDATEs missing: %s" % (", ".join([iCalendarString(exdate) for exdate in missing]),))
                 return False
             declines.extend(clientProps[-1] - serverProps[-1])
             return True
@@ -554,10 +553,10 @@
             dtend = component.getProperty("DTEND")
             duration = component.getProperty("DURATION")
             
-            newdtstart, newdtend = normalizeStartEndDuration(
-                dtstart.value() if dtstart is not None else None,
-                dtend.value() if dtend is not None else None,
-                duration.value() if duration is not None else None,
+            timeRange = timerange(
+                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,
             )
             newdue = None
             
@@ -566,17 +565,16 @@
             duration = component.getProperty("DURATION")
             
             if dtstart or duration:
-                newdtstart, newdtend = normalizeStartEndDuration(
-                    dtstart.value() if dtstart is not None else None,
-                    None,
-                    duration.value() if duration is not None else None,
+                timeRange = timerange(
+                    start    = dtstart.value()  if dtstart  is not None else None,
+                    duration = duration.value() if duration is not None else None,
                 )
             else:
-                newdtstart = newdtend = None
+                timeRange = timerange()
 
             newdue = component.getProperty("DUE")
             if newdue is not None:
-                newdue = normalizeToUTC(newdue.value())
+                newdue = asUTC(newdue.value())
             
         # Recurrence rules - we need to normalize the order of the value parts
         newrrules = set()
@@ -591,15 +589,15 @@
         newrdates = set()
         rdates = component.properties("RDATE")
         for rdate in rdates:
-            newrdates.update([normalizeToUTC(value) for value in rdate.value()])
+            newrdates.update([asUTC(value) for value in rdate.value()])
         
         # EXDATEs
         newexdates = set()
         exdates = component.properties("EXDATE")
         for exdate in exdates:
-            newexdates.update([normalizeToUTC(value) for value in exdate.value()])
+            newexdates.update([asUTC(value) for value in exdate.value()])
 
-        return newdtstart, newdtend, newdue, newrrules, newrdates, newexdates
+        return timeRange.start(), timeRange.end(), newdue, newrrules, newrdates, newexdates
 
     def _transferProperty(self, propName, serverComponent, clientComponent):
 
@@ -738,7 +736,7 @@
         
         if addedChanges:
             rid = comp1.getRecurrenceIDUTC()
-            rids[toString(rid) if rid is not None else ""] = propsChanged
+            rids[iCalendarString(rid) if rid is not None else ""] = propsChanged
 
     def _logDiffError(self, title):
 

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/itip.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/itip.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/itip.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -36,9 +36,9 @@
 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.dateops import normalizeToUTC, toString
 from twistedcaldav.ical import Property, iCalendarProductID, Component
 
 log = Logger()
@@ -321,7 +321,7 @@
             if attendee:
                 attendees.add(attendee)
                 if rids is not None and (partstat or private_comment):
-                    rids.add((toString(rid), partstat, private_comment,))
+                    rids.add((iCalendarString(rid), partstat, private_comment,))
 
         # Check for an invalid instance by itself
         len_attendees = len(attendees)
@@ -542,7 +542,7 @@
             comp.addProperty(Property("SEQUENCE", seq))
             comp.addProperty(instance.getOrganizerProperty())
             if instance_rid:
-                comp.addProperty(Property("RECURRENCE-ID", normalizeToUTC(instance_rid)))
+                comp.addProperty(Property("RECURRENCE-ID", asUTC(instance_rid)))
             
             def addProperties(propname):
                 for property in instance.properties(propname):

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/scheduler.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/scheduler.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/scheduler.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -14,38 +14,41 @@
 # limitations under the License.
 ##
 
-from twext.python.log import Logger, LoggingMixIn
-from twext.web2.dav.http import ErrorResponse
+import itertools
+import re
+import socket
+import urlparse
 
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.python.failure import Failure
+
+from twext.python.log import Logger, LoggingMixIn
+from twext.python.datetime import iCalendarString
 from twext.web2 import responsecode
+from twext.web2.http import HTTPError, Response, StatusResponse
+from twext.web2.http_headers import MimeType
 from twext.web2.dav import davxml
 from twext.web2.dav.http import errorForFailure, messageForFailure, statusForFailure
-from twext.web2.http import HTTPError, Response, StatusResponse
-from twext.web2.http_headers import MimeType
+from twext.web2.dav.http import ErrorResponse
 
-from twistedcaldav import caldavxml, dateops
+from twistedcaldav import caldavxml
+from twistedcaldav.caldavxml import caldav_namespace
+from twistedcaldav.customxml import calendarserver_namespace
 from twistedcaldav.accounting import accountingEnabled, emitAccounting
-from twistedcaldav.caldavxml import caldav_namespace, TimeRange
 from twistedcaldav.config import config
-from twistedcaldav.customxml import calendarserver_namespace
 from twistedcaldav.ical import Component
 from twistedcaldav.scheduling import addressmapping
 from twistedcaldav.scheduling.caldav import ScheduleViaCalDAV
-from twistedcaldav.scheduling.cuaddress import InvalidCalendarUser,\
-    LocalCalendarUser, RemoteCalendarUser, EmailCalendarUser,\
-    PartitionedCalendarUser
+from twistedcaldav.scheduling.cuaddress import InvalidCalendarUser
+from twistedcaldav.scheduling.cuaddress import LocalCalendarUser
+from twistedcaldav.scheduling.cuaddress import RemoteCalendarUser
+from twistedcaldav.scheduling.cuaddress import EmailCalendarUser
+from twistedcaldav.scheduling.cuaddress import PartitionedCalendarUser
 from twistedcaldav.scheduling.imip import ScheduleViaIMip
 from twistedcaldav.scheduling.ischedule import ScheduleViaISchedule
 from twistedcaldav.scheduling.ischeduleservers import IScheduleServers
 from twistedcaldav.scheduling.itip import iTIPRequestStatus
 
-import itertools
-import re
-import socket
-import urlparse
-
 """
 CalDAV/Server-to-Server scheduling behavior.
 """
@@ -324,7 +327,7 @@
                     log.err("VFREEBUSY start or end not UTC: %s" % (self.calendar,))
                     raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data"), description="VFREEBUSY start or end not UTC"))
 
-                self.timeRange = TimeRange(start=dateops.toString(dtstart), end=dateops.toString(dtend))
+                self.timeRange = caldavxml.TimeRange(start=iCalendarString(dtstart), end=iCalendarString(dtend))
                 self.timeRange.start = dtstart
                 self.timeRange.end = dtend
         

Deleted: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_dateops.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_dateops.py	2010-02-27 04:51:40 UTC (rev 5224)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_dateops.py	2010-03-01 18:49:46 UTC (rev 5225)
@@ -1,155 +0,0 @@
-##
-# Copyright (c) 2008 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.
-##
-
-
-import twistedcaldav.test.util
-from vobject.icalendar import utc, getTzid
-from twistedcaldav.dateops import normalizeStartEndDuration
-from twistedcaldav.timezones import TimezoneCache
-import datetime
-
-#TODO: add tests for all the methods in dateops
-
-class Tests_normalizeStartEndDuration (twistedcaldav.test.util.TestCase):
-    """
-    Test abstract SQL DB class
-    """
-
-    def setUp(self):
-        super(Tests_normalizeStartEndDuration, self).setUp()
-        
-        TimezoneCache.create()
-        TimezoneCache.activeCache.loadTimezone("America/New_York")
-
-    def test_invalid(self):
-        
-        start = datetime.datetime(2008, 1, 1, 0, 0, 0, tzinfo=utc)
-        end = datetime.datetime(2008, 1, 1, 1, 0, 0, tzinfo=utc)
-        duration = end - start
-        
-        self.assertRaises(AssertionError, normalizeStartEndDuration, start, end, duration)
-
-    def test_start_only_utc(self):
-        
-        start = datetime.datetime(2008, 1, 1, 0, 0, 0, tzinfo=utc)
-        
-        newstart, newend = normalizeStartEndDuration(start)
-        self.assertEqual(newstart, start)
-        self.assertTrue(newend is None)
-
-    def test_start_only_float(self):
-        start = datetime.datetime(2008, 1, 1, 0, 0, 0)
-        
-        newstart, newend = normalizeStartEndDuration(start)
-        self.assertEqual(newstart, start)
-        self.assertTrue(newend is None)
-
-    def test_start_only_date(self):
-        start = datetime.date(2008, 1, 1)
-        
-        newstart, newend = normalizeStartEndDuration(start)
-        self.assertEqual(newstart, start)
-        self.assertTrue(newend is None)
-
-    def test_start_only_tzid(self):
-
-        start = datetime.datetime(2008, 1, 1, 0, 0, 0, tzinfo=getTzid("America/New_York"))
-        utcstart = datetime.datetime(2008, 1, 1, 5, 0, 0, tzinfo=utc)
-        
-        newstart, newend = normalizeStartEndDuration(start)
-        self.assertEqual(newstart, utcstart)
-        self.assertTrue(newend is None)
-
-    def test_start_end_utc(self):
-
-        start = datetime.datetime(2008, 1, 1, 0, 0, 0, tzinfo=utc)
-        end = datetime.datetime(2008, 1, 1, 1, 0, 0, tzinfo=utc)
-        
-        newstart, newend = normalizeStartEndDuration(start, dtend=end)
-        self.assertEqual(newstart, start)
-        self.assertEqual(newend, end)
-
-    def test_start_end_float(self):
-
-        start = datetime.datetime(2008, 1, 1, 0, 0, 0)
-        end = datetime.datetime(2008, 1, 1, 1, 0, 0)
-        
-        newstart, newend = normalizeStartEndDuration(start, dtend=end)
-        self.assertEqual(newstart, start)
-        self.assertEqual(newend, end)
-
-    def test_start_end_date(self):
-
-        start = datetime.date(2008, 1, 1)
-        end = datetime.date(2008, 1, 2)
-        
-        newstart, newend = normalizeStartEndDuration(start, dtend=end)
-        self.assertEqual(newstart, start)
-        self.assertEqual(newend, end)
-
-    def test_start_end_tzid(self):
-
-        start = datetime.datetime(2008, 1, 1, 0, 0, 0, tzinfo=getTzid("America/New_York"))
-        end = datetime.datetime(2008, 1, 1, 1, 0, 0, tzinfo=getTzid("America/New_York"))
-        utcstart = datetime.datetime(2008, 1, 1, 5, 0, 0, tzinfo=utc)
-        utcend = datetime.datetime(2008, 1, 1, 6, 0, 0, tzinfo=utc)
-        
-        newstart, newend = normalizeStartEndDuration(start, dtend=end)
-        self.assertEqual(newstart, utcstart)
-        self.assertEqual(newend, utcend)
-
-    def test_start_duration_utc(self):
-
-        start = datetime.datetime(2008, 1, 1, 0, 0, 0, tzinfo=utc)
-        end = datetime.datetime(2008, 1, 1, 1, 0, 0, tzinfo=utc)
-        duration = end - start
-        
-        newstart, newend = normalizeStartEndDuration(start, duration=duration)
-        self.assertEqual(newstart, start)
-        self.assertEqual(newend, end)
-
-    def test_start_duration_float(self):
-
-        start = datetime.datetime(2008, 1, 1, 0, 0, 0)
-        end = datetime.datetime(2008, 1, 1, 1, 0, 0)
-        duration = end - start
-        
-        newstart, newend = normalizeStartEndDuration(start, duration=duration)
-        self.assertEqual(newstart, start)
-        self.assertEqual(newend, end)
-
-    def test_start_duration_date(self):
-
-        start = datetime.date(2008, 1, 1)
-        end = datetime.date(2008, 1, 2)
-        duration = end - start
-        
-        newstart, newend = normalizeStartEndDuration(start, duration=duration)
-        self.assertEqual(newstart, start)
-        self.assertEqual(newend, end)
-
-    def test_start_duration_tzid(self):
- 
-        start = datetime.datetime(2008, 1, 1, 0, 0, 0, tzinfo=getTzid("America/New_York"))
-        end = datetime.datetime(2008, 1, 1, 1, 0, 0, tzinfo=getTzid("America/New_York"))
-        utcstart = datetime.datetime(2008, 1, 1, 5, 0, 0, tzinfo=utc)
-        utcend = datetime.datetime(2008, 1, 1, 6, 0, 0, tzinfo=utc)
-        duration = end - start
-        
-        newstart, newend = normalizeStartEndDuration(start, duration=duration)
-        self.assertEqual(newstart, utcstart)
-        self.assertEqual(newend, utcend)
-
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100301/177a9805/attachment-0001.html>


More information about the calendarserver-changes mailing list