[CalendarServer-changes] [11393] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri Jun 21 14:33:23 PDT 2013


Revision: 11393
          http://trac.calendarserver.org//changeset/11393
Author:   cdaboo at apple.com
Date:     2013-06-21 14:33:23 -0700 (Fri, 21 Jun 2013)
Log Message:
-----------
Implement code to split a recurring iCalendar object into two smaller pieces.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tools/calverify.py
    CalendarServer/trunk/calendarserver/tools/purge.py
    CalendarServer/trunk/calendarserver/tools/shell/vfs.py
    CalendarServer/trunk/support/build.sh
    CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py
    CalendarServer/trunk/twistedcaldav/ical.py
    CalendarServer/trunk/twistedcaldav/test/test_extensions.py
    CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
    CalendarServer/trunk/twistedcaldav/test/test_localization.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/imip/outbound.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py
    CalendarServer/trunk/txdav/caldav/datastore/sql.py
    CalendarServer/trunk/txdav/caldav/datastore/util.py

Added Paths:
-----------
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/icalsplitter.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icalsplitter.py

Modified: CalendarServer/trunk/calendarserver/tools/calverify.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/calverify.py	2013-06-21 19:26:50 UTC (rev 11392)
+++ CalendarServer/trunk/calendarserver/tools/calverify.py	2013-06-21 21:33:23 UTC (rev 11393)
@@ -66,7 +66,7 @@
 from twistedcaldav.dateops import pyCalendarTodatetime
 from twistedcaldav.directory.directory import DirectoryService
 from twistedcaldav.ical import Component, ignoredComponents, \
-    InvalidICalendarDataError, Property
+    InvalidICalendarDataError, Property, PERUSER_COMPONENT
 from txdav.caldav.datastore.scheduling.itip import iTipGenerator
 from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
 from twistedcaldav.util import normalizationLookup
@@ -184,7 +184,7 @@
     test and optionally remove alarms that have the same ACTION and TRIGGER values in the same component.
     """
     changed = False
-    if self.name() in ("VCALENDAR", "X-CALENDARSERVER-PERUSER",):
+    if self.name() in ("VCALENDAR", PERUSER_COMPONENT,):
         for component in self.subcomponents():
             if component.name() in ("VTIMEZONE",):
                 continue

Modified: CalendarServer/trunk/calendarserver/tools/purge.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/purge.py	2013-06-21 19:26:50 UTC (rev 11392)
+++ CalendarServer/trunk/calendarserver/tools/purge.py	2013-06-21 21:33:23 UTC (rev 11393)
@@ -1038,11 +1038,7 @@
         if event.mainType() != "VEVENT":
             return cls.CANCELEVENT_SKIPPED
 
-        main = event.masterComponent()
-        if main is None:
-            # No master component, so this is an attendee being invited to one or
-            # more occurrences
-            main = event.mainComponent(allow_multiple=True)
+        main = event.mainComponent()
 
         # Anything completely in the future is deleted
         dtstart = main.getStartDateUTC()

Modified: CalendarServer/trunk/calendarserver/tools/shell/vfs.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/shell/vfs.py	2013-06-21 19:26:50 UTC (rev 11392)
+++ CalendarServer/trunk/calendarserver/tools/shell/vfs.py	2013-06-21 21:33:23 UTC (rev 11393)
@@ -139,27 +139,33 @@
         self.service = service
         self.path = path
 
+
     def __str__(self):
         return "/" + "/".join(self.path)
 
+
     def __repr__(self):
         return "<%s: %s>" % (self.__class__.__name__, self)
 
+
     def __eq__(self, other):
         if isinstance(other, File):
             return self.path == other.path
         else:
             return NotImplemented
 
+
     def describe(self):
         return succeed("%s (%s)" % (self, self.__class__.__name__))
 
+
     def list(self):
         return succeed((
             ListEntry(self, self.__class__, self.path[-1]),
         ))
 
 
+
 class Folder(File):
     """
     Location in virtual data hierarchy.
@@ -170,12 +176,14 @@
         self._children = {}
         self._childClasses = {}
 
+
     def __str__(self):
         if self.path:
             return "/" + "/".join(self.path) + "/"
         else:
             return "/"
 
+
     @inlineCallbacks
     def locate(self, path):
         if path and path[-1] == "":
@@ -194,6 +202,7 @@
 
         returnValue(target)
 
+
     @inlineCallbacks
     def child(self, name):
         # FIXME: Move this logic to locate()
@@ -217,6 +226,7 @@
 
         raise NotFoundError("Folder %r has no child %r" % (str(self), name))
 
+
     def list(self):
         result = {}
         for name in self._children:
@@ -227,6 +237,7 @@
         return succeed(result.itervalues())
 
 
+
 class RootFolder(Folder):
     """
     Root of virtual data hierarchy.
@@ -255,13 +266,14 @@
     def __init__(self, service):
         Folder.__init__(self, service, ())
 
-        self._childClasses["uids"     ] = UIDsFolder
-        self._childClasses["users"    ] = UsersFolder
+        self._childClasses["uids"] = UIDsFolder
+        self._childClasses["users"] = UsersFolder
         self._childClasses["locations"] = LocationsFolder
         self._childClasses["resources"] = ResourcesFolder
-        self._childClasses["groups"   ] = GroupsFolder
+        self._childClasses["groups"] = GroupsFolder
 
 
+
 class UIDsFolder(Folder):
     """
     Folder containing all principals by UID.
@@ -269,6 +281,7 @@
     def child(self, name):
         return PrincipalHomeFolder(self.service, self.path + (name,), name)
 
+
     @inlineCallbacks
     def list(self):
         results = {}
@@ -303,6 +316,7 @@
         recordType = getattr(self.service.directory, recordTypeAttr)
         return self.service.directory.recordWithShortName(recordType, name)
 
+
     def child(self, name):
         record = self._recordForName(name)
 
@@ -316,6 +330,7 @@
             record=record
         )
 
+
     def list(self):
         names = set()
 
@@ -337,6 +352,7 @@
     list.fieldNames = ("UID", "Full Name")
 
 
+
 class UsersFolder(RecordFolder):
     """
     Folder containing all user principals by name.
@@ -344,6 +360,7 @@
     recordType = "users"
 
 
+
 class LocationsFolder(RecordFolder):
     """
     Folder containing all location principals by name.
@@ -351,6 +368,7 @@
     recordType = "locations"
 
 
+
 class ResourcesFolder(RecordFolder):
     """
     Folder containing all resource principals by name.
@@ -358,6 +376,7 @@
     recordType = "resources"
 
 
+
 class GroupsFolder(RecordFolder):
     """
     Folder containing all group principals by name.
@@ -365,6 +384,7 @@
     recordType = "groups"
 
 
+
 class PrincipalHomeFolder(Folder):
     """
     Folder containing everything related to a given principal.
@@ -381,6 +401,7 @@
         self.uid = uid
         self.record = record
 
+
     @inlineCallbacks
     def _initChildren(self):
         if not hasattr(self, "_didInitChildren"):
@@ -459,6 +480,7 @@
 
         self._didInitChildren = True
 
+
     def _needsChildren(m):
         def decorate(self, *args, **kwargs):
             d = self._initChildren()
@@ -466,18 +488,22 @@
             return d
         return decorate
 
+
     @_needsChildren
     def child(self, name):
         return Folder.child(self, name)
 
+
     @_needsChildren
     def list(self):
         return Folder.list(self)
 
+
     def describe(self):
         return recordInfo(self.service.directory, self.record)
 
 
+
 class CalendarHomeFolder(Folder):
     """
     Calendar home folder.
@@ -488,6 +514,7 @@
         self.home = home
         self.record = record
 
+
     @inlineCallbacks
     def child(self, name):
         calendar = (yield self.home.calendarWithName(name))
@@ -496,6 +523,7 @@
         else:
             raise NotFoundError("Calendar home %r has no calendar %r" % (self, name))
 
+
     @inlineCallbacks
     def list(self):
         calendars = (yield self.home.calendars())
@@ -512,6 +540,7 @@
             result.append(ListEntry(self, CalendarFolder, calendar.name(), **info))
         returnValue(result)
 
+
     @inlineCallbacks
     def describe(self):
         description = ["Calendar home:\n"]
@@ -553,6 +582,7 @@
         returnValue("\n".join(description))
 
 
+
 class CalendarFolder(Folder):
     """
     Calendar.
@@ -562,11 +592,13 @@
 
         self.calendar = calendar
 
+
     @inlineCallbacks
     def _childWithObject(self, object):
         uid = (yield object.uid())
         returnValue(CalendarObject(self.service, self.path + (uid,), object, uid))
 
+
     @inlineCallbacks
     def child(self, name):
         object = (yield self.calendar.calendarObjectWithUID(name))
@@ -577,6 +609,7 @@
         child = (yield self._childWithObject(object))
         returnValue(child)
 
+
     @inlineCallbacks
     def list(self):
         result = []
@@ -588,6 +621,7 @@
 
         returnValue(result)
 
+
     @inlineCallbacks
     def describe(self):
         description = ["Calendar:\n"]
@@ -614,6 +648,7 @@
 
         returnValue("\n".join(description))
 
+
     def delete(self, implicit=True):
 
         if implicit:
@@ -624,6 +659,7 @@
             self.calendarObject.remove()
 
 
+
 class CalendarObject(File):
     """
     Calendar object.
@@ -634,13 +670,14 @@
         self.object = calendarObject
         self.uid = uid
 
+
     @inlineCallbacks
     def lookup(self):
         if not hasattr(self, "component"):
             component = (yield self.object.component())
 
             try:
-                mainComponent = component.mainComponent(allow_multiple=True)
+                mainComponent = component.mainComponent()
 
                 assert self.uid == mainComponent.propertyValue("UID")
 
@@ -657,6 +694,7 @@
 
             self.component = component
 
+
     @inlineCallbacks
     def list(self):
         (yield self.lookup())
@@ -672,6 +710,7 @@
         (yield self.lookup())
         returnValue(str(self.component))
 
+
     @inlineCallbacks
     def describe(self):
         (yield self.lookup())
@@ -732,6 +771,7 @@
         returnValue("\n".join(description))
 
 
+
 class AddressBookHomeFolder(Folder):
     """
     Address book home folder.
@@ -745,6 +785,7 @@
     # FIXME
 
 
+
 def tableString(rows, header=None):
     table = Table()
     if header:
@@ -757,6 +798,7 @@
     return output.getvalue()
 
 
+
 def tableStringForProperties(properties):
     return "Properties:\n%s" % (tableString((
         (name.toString(), truncateAtNewline(properties[name]))
@@ -764,6 +806,7 @@
     )))
 
 
+
 def timeString(time):
     if time is None:
         return "(unknown)"
@@ -771,6 +814,7 @@
     return strftime("%a, %d %b %Y %H:%M:%S %z(%Z)", localtime(time))
 
 
+
 def truncateAtNewline(text):
     text = str(text)
     try:

Modified: CalendarServer/trunk/support/build.sh
===================================================================
--- CalendarServer/trunk/support/build.sh	2013-06-21 19:26:50 UTC (rev 11392)
+++ CalendarServer/trunk/support/build.sh	2013-06-21 21:33:23 UTC (rev 11393)
@@ -795,7 +795,7 @@
     "${pypi}/p/${n}/${p}.tar.gz";
 
   # XXX actually PyCalendar should be imported in-place.
-  py_dependency -fe -i "src" -r 11005 \
+  py_dependency -fe -i "src" -r 11390 \
     "PyCalendar" "pycalendar" "pycalendar" \
     "${svn_uri_base}/PyCalendar/trunk";
 

Modified: CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py	2013-06-21 19:26:50 UTC (rev 11392)
+++ CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py	2013-06-21 21:33:23 UTC (rev 11393)
@@ -15,7 +15,8 @@
 ##
 
 from twistedcaldav.datafilters.filter import CalendarFilter
-from twistedcaldav.ical import Component, Property
+from twistedcaldav.ical import Component, Property, PERUSER_COMPONENT, \
+    PERUSER_UID, PERINSTANCE_COMPONENT
 
 __all__ = [
     "PerUserDataFilter",
@@ -58,15 +59,16 @@
     Filter per-user data
     """
 
-    # If any of these change also update usage in ical.py
-    PERUSER_COMPONENT = "X-CALENDARSERVER-PERUSER"
-    PERUSER_UID = "X-CALENDARSERVER-PERUSER-UID"
-    PERINSTANCE_COMPONENT = "X-CALENDARSERVER-PERINSTANCE"
+    # Regular properties that need to be treated as per-user
+    PERUSER_PROPERTIES = ("TRANSP",)
 
-    PERUSER_PROPERTIES = ("TRANSP",)
+    # Regular components that need to be treated as per-user
     PERUSER_SUBCOMPONENTS = ("VALARM",)
-    IGNORE_X_PROPERTIES = ("X-CALENDARSERVER-HIDDEN-INSTANCE",)
 
+    # X- properties that are ignored - by default all X- properties are treated as per-user except for the
+    # ones listed here
+    IGNORE_X_PROPERTIES = (Component.HIDDEN_INSTANCE_PROPERTY,)
+
     def __init__(self, uid):
         """
 
@@ -94,10 +96,10 @@
         # Look for matching per-user sub-component, removing all the others
         peruser_component = None
         for component in tuple(ical.subcomponents()):
-            if component.name() == PerUserDataFilter.PERUSER_COMPONENT:
+            if component.name() == PERUSER_COMPONENT:
 
                 # Check user id - remove if not matches
-                if component.propertyValue(PerUserDataFilter.PERUSER_UID) != self.uid:
+                if component.propertyValue(PERUSER_UID) != self.uid:
                     ical.removeComponent(component)
                 elif peruser_component is None:
                     peruser_component = component
@@ -129,7 +131,7 @@
 
         # There cannot be any X-CALENDARSERVER-PERUSER components in the new data
         for component in tuple(icalnew.subcomponents()):
-            if component.name() == PerUserDataFilter.PERUSER_COMPONENT:
+            if component.name() == PERUSER_COMPONENT:
                 raise ValueError("Cannot merge calendar data with X-CALENDARSERVER-PERUSER components in it")
 
         # First split the new data into common and per-user pieces
@@ -157,7 +159,7 @@
         # Iterate over each instance in the per-user data and build mapping
         peruser_recurrence_map = {}
         for subcomponent in peruser.subcomponents():
-            if subcomponent.name() != PerUserDataFilter.PERINSTANCE_COMPONENT:
+            if subcomponent.name() != PERINSTANCE_COMPONENT:
                 raise AssertionError("Wrong sub-component '%s' in a X-CALENDARSERVER-PERUSER component" % (subcomponent.name(),))
             peruser_recurrence_map[subcomponent.getRecurrenceIDUTC()] = subcomponent
 
@@ -227,9 +229,9 @@
         """
 
         def init_peruser_component():
-            peruser = Component(PerUserDataFilter.PERUSER_COMPONENT)
+            peruser = Component(PERUSER_COMPONENT)
             peruser.addProperty(Property("UID", ical.resourceUID()))
-            peruser.addProperty(Property(PerUserDataFilter.PERUSER_UID, self.uid))
+            peruser.addProperty(Property(PERUSER_UID, self.uid))
             return peruser
 
         components = tuple(ical.subcomponents())
@@ -242,7 +244,7 @@
             rid = component.propertyValue("RECURRENCE-ID")
             rid = rid.duplicate() if rid is not None else None
 
-            perinstance_component = Component(PerUserDataFilter.PERINSTANCE_COMPONENT) if self.uid else None
+            perinstance_component = Component(PERINSTANCE_COMPONENT) if self.uid else None
             perinstance_id_different = False
 
             # Transfer per-user properties from main component to per-instance component
@@ -329,8 +331,8 @@
         old_recur = icalold.isRecurring()
         new_recur_has_no_master = new_recur and (icalnew.masterComponent() is None)
         for component in icalold.subcomponents():
-            if component.name() == PerUserDataFilter.PERUSER_COMPONENT:
-                if component.propertyValue(PerUserDataFilter.PERUSER_UID) != self.uid and not new_recur_has_no_master:
+            if component.name() == PERUSER_COMPONENT:
+                if component.propertyValue(PERUSER_UID) != self.uid and not new_recur_has_no_master:
                     newcomponent = component.duplicate()
 
                     # Only transfer the master components from the old data to the new when the old
@@ -348,8 +350,8 @@
 
         # Take all per-user components from old and add to new, except for our user
         for component in icalold.subcomponents():
-            if component.name() == PerUserDataFilter.PERUSER_COMPONENT:
-                if component.propertyValue(PerUserDataFilter.PERUSER_UID) != self.uid:
+            if component.name() == PERUSER_COMPONENT:
+                if component.propertyValue(PERUSER_UID) != self.uid:
                     newcomponent = component.duplicate()
 
                     # See which of the instances are still valid

Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py	2013-06-21 19:26:50 UTC (rev 11392)
+++ CalendarServer/trunk/twistedcaldav/ical.py	2013-06-21 21:33:23 UTC (rev 11393)
@@ -15,7 +15,6 @@
 # limitations under the License.
 ##
 
-
 """
 iCalendar Utilities
 """
@@ -33,6 +32,7 @@
 import codecs
 import heapq
 import itertools
+import uuid
 
 from twext.python.log import Logger
 from twext.web2.stream import IStream
@@ -70,6 +70,11 @@
     # "VAVAILABILITY",
 )
 
+# Additional per-user data components - see datafilters.peruserdata.py for details
+PERUSER_COMPONENT = "X-CALENDARSERVER-PERUSER"
+PERUSER_UID = "X-CALENDARSERVER-PERUSER-UID"
+PERINSTANCE_COMPONENT = "X-CALENDARSERVER-PERINSTANCE"
+
 # 2445 default values and parameters
 # Structure: propname: (<default value>, <parameter defaults dict>)
 
@@ -136,7 +141,7 @@
     "ORGANIZER": normalizeCUAddr,
 }
 
-ignoredComponents = ("VTIMEZONE", "X-CALENDARSERVER-PERUSER",)
+ignoredComponents = ("VTIMEZONE", PERUSER_COMPONENT,)
 
 # Used for min/max time-range query limits
 minDateTime = PyCalendarDateTime(1900, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
@@ -577,11 +582,12 @@
         return mtype
 
 
-    def mainComponent(self, allow_multiple=False):
+    def mainComponent(self):
         """
-        Return the primary iCal component in this calendar.
+        Return the primary iCal component in this calendar. If a master component exists, use that,
+        otherwise use the first override.
+
         @return: the L{Component} of the primary type.
-        @raise: L{InvalidICalendarDataError} if there is more than one primary type.
         """
         assert self.name() == "VCALENDAR", "Must be a VCALENDAR: %r" % (self,)
 
@@ -589,12 +595,10 @@
         for component in self.subcomponents():
             if component.name() in ignoredComponents:
                 continue
-            elif not allow_multiple and (result is not None):
-                raise InvalidICalendarDataError("Calendar contains more than one primary component: %r" % (self,))
-            else:
+            if not component.hasProperty("RECURRENCE-ID"):
+                return component
+            elif result is None:
                 result = component
-                if allow_multiple:
-                    break
 
         return result
 
@@ -1070,6 +1074,154 @@
         return changed
 
 
+    def onlyPastInstances(self, rid):
+        """
+        Remove all recurrence instances at or beyond the specified recurrence-id. Adjust the bounds of any RRULEs
+        to match the new limit, remove RDATEs/EXDATEs and overridden components beyond the limit.
+
+        @param rid: the recurrence-id limit
+        @type rid: L{PyCalendarDateTime}
+        """
+
+        master = self.masterComponent()
+        if not master.isRecurring():
+            return
+        if master:
+            # Adjust any RRULE first
+            rrules = master._pycalendar.getRecurrenceSet()
+            if rrules:
+                for rrule in rrules.getRules():
+                    rrule.setUseUntil(True)
+                    rrule.setUseCount(False)
+                    until = rid.duplicate()
+                    until.offsetSeconds(-1)
+                    rrule.setUntil(until)
+
+            # Remove any RDATEs or EXDATEs in the future
+            for property in list(itertools.chain(
+                master.properties("RDATE"),
+                master.properties("EXDATE"),
+            )):
+                for value in list(property.value()):
+                    if value.getValue() >= rid:
+                        property.value().remove(value)
+                if len(property.value()) == 0:
+                    master.removeProperty(property)
+
+        # Remove overrides in the future
+        for component in list(self.subcomponents()):
+            if component.name() in ignoredComponents:
+                continue
+            c_rid = component.getRecurrenceIDUTC()
+            if c_rid is not None and c_rid >= rid:
+                self.removeComponent(component)
+
+        # Handle per-user data component by removing ones in the future
+        for component in list(self.subcomponents()):
+            if component.name() == PERUSER_COMPONENT:
+                for subcomponent in list(component.subcomponents()):
+                    c_rid = subcomponent.getRecurrenceIDUTC()
+                    if c_rid is not None and c_rid >= rid:
+                        component.removeComponent(subcomponent)
+                if len(list(component.subcomponents())) == 0:
+                    self.removeComponent(component)
+
+        self._markAsDirty()
+
+
+    def onlyFutureInstances(self, rid):
+        """
+        Remove all recurrence instances from the specified recurrence-id into the past. Adjust the bounds of
+        any RRULEs to match the new limit, remove RDATEs/EXDATEs and overridden components beyond the limit.
+        This also requires "re-basing" the master component to the new first instance - but noting that has to
+        match any RRULE pattern.
+
+        @param rid: the recurrence-id limit
+        @type rid: L{PyCalendarDateTime}
+        """
+
+        master = self.masterComponent()
+        if not master.isRecurring():
+            return
+        if master:
+            # Check if cut-off matches an RDATE
+            adjusted_rid = rid
+            continuing_rrule = True
+            rdates = set([v.getValue() for v in itertools.chain(*[rdate.value() for rdate in master.properties("RDATE")])])
+            if rid in rdates:
+                # Need to detect the first valid RRULE instance after the cut-off
+                rrules = master._pycalendar.getRecurrenceSet()
+                if rrules and len(rrules.getRules()) != 0:
+                    rrule = rrules.getRules()[0]
+                    upperlimit = rid.duplicate()
+                    upperlimit.offsetYear(1)
+                    rrule_expanded = []
+                    rrule.expand(
+                        master.propertyValue("DTSTART"),
+                        PyCalendarPeriod(PyCalendarDateTime(1900, 1, 1), upperlimit),
+                        rrule_expanded,
+                    )
+                    for i in sorted(rrule_expanded):
+                        if i > rid:
+                            adjusted_rid = i
+                            break
+                    else:
+                        # RRULE not needed in derived master
+                        continuing_rrule = False
+
+            # Adjust master to previously derived instance
+            derived = self.deriveInstance(adjusted_rid, allowExcluded=True)
+            if derived is None:
+                return
+
+            # Fix up recurrence properties so the derived one looks like the master
+            derived.removeProperty(derived.getProperty("RECURRENCE-ID"))
+            for property in list(itertools.chain(
+                master.properties("RRULE") if continuing_rrule else (),
+                master.properties("RDATE"),
+                master.properties("EXDATE"),
+            )):
+                derived.addProperty(property)
+
+            # Now switch over to using the new "derived" master
+            self.removeComponent(master)
+            master = derived
+            self.addComponent(master)
+
+            # Remove any RDATEs or EXDATEs in the past
+            for property in list(itertools.chain(
+                master.properties("RDATE"),
+                master.properties("EXDATE"),
+            )):
+                for value in list(property.value()):
+                    # If the derived master was derived from an RDATE we remove the RDATE
+                    if value.getValue() < rid or property.name() == "RDATE" and value.getValue() == adjusted_rid:
+                        property.value().remove(value)
+                if len(property.value()) == 0:
+                    master.removeProperty(property)
+
+        # Remove overrides in the past - but do not remove any override matching
+        # the cut-off as that is still a valid override after "re-basing" the master.
+        for component in list(self.subcomponents()):
+            if component.name() in ignoredComponents:
+                continue
+            c_rid = component.getRecurrenceIDUTC()
+            if c_rid is not None and c_rid < rid:
+                self.removeComponent(component)
+
+        # Handle per-user data component by removing ones in the past
+        for component in list(self.subcomponents()):
+            if component.name() == PERUSER_COMPONENT:
+                for subcomponent in list(component.subcomponents()):
+                    c_rid = subcomponent.getRecurrenceIDUTC()
+                    if c_rid is not None and c_rid < rid:
+                        component.removeComponent(subcomponent)
+                if len(list(component.subcomponents())) == 0:
+                    self.removeComponent(component)
+
+        self._markAsDirty()
+
+
     def expand(self, start, end, timezone=None):
         """
         Expand the components into a set of new components, one for each
@@ -1271,7 +1423,7 @@
         return False
 
 
-    def deriveInstance(self, rid, allowCancelled=False, newcomp=None):
+    def deriveInstance(self, rid, allowCancelled=False, newcomp=None, allowExcluded=False):
         """
         Derive an instance from the master component that has the provided RECURRENCE-ID, but
         with all other properties, components etc from the master. If the requested override is
@@ -1282,6 +1434,8 @@
         @type rid: L{PyCalendarDateTime} or C{str}
         @param allowCancelled: whether to allow a STATUS:CANCELLED override
         @type allowCancelled: C{bool}
+        @param allowExcluded: whether to derive an instance for an existing EXDATE
+        @type allowExcluded: C{bool}
 
         @return: L{Component} for newly derived instance, or None if not valid override
         """
@@ -1300,6 +1454,7 @@
         # TODO: Check that the recurrence-id is a valid instance
         # For now we just check that there is no matching EXDATE
         didCancel = False
+        matchedExdate = False
         for exdate in tuple(master.properties("EXDATE")):
             for exdateValue in exdate.value():
                 if exdateValue.getValue() == rid:
@@ -1313,26 +1468,30 @@
                         if hasattr(self, "cachedInstances"):
                             delattr(self, "cachedInstances")
                         break
+                    elif allowExcluded:
+                        matchedExdate = True
+                        break
                     else:
                         # Cannot derive from an existing EXDATE
                         return None
 
-        # Check whether recurrence-id matches an RDATE - if so it is OK
-        rdates = set()
-        for rdate in master.properties("RDATE"):
-            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)):
-                instances = self.cacheExpandedTimeRanges(rid)
-                instance_rid = normalizeForIndex(rid)
-                if str(instance_rid) not in instances.instances:
-                    # No match to a valid RRULE instance
+        if not matchedExdate:
+            # Check whether recurrence-id matches an RDATE - if so it is OK
+            rdates = set()
+            for rdate in master.properties("RDATE"):
+                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)):
+                    instances = self.cacheExpandedTimeRanges(rid)
+                    instance_rid = normalizeForIndex(rid)
+                    if str(instance_rid) not in instances.instances:
+                        # No match to a valid RRULE instance
+                        return None
+                else:
+                    # No RRULE and no match to an RDATE => error
                     return None
-            else:
-                # No RRULE and no match to an RDATE => error
-                return None
 
         # If we were fed an already derived component, use that, otherwise make a new one
         if newcomp is None:
@@ -1458,6 +1617,19 @@
         return self._resource_uid
 
 
+    def newUID(self):
+        """
+        Generate a new UID for all components in this VCALENDAR
+        """
+        assert self.name() == "VCALENDAR", "Not a calendar: %r" % (self,)
+
+        newUID = str(uuid.uuid4())
+        self._pycalendar.changeUID(self.resourceUID(), newUID)
+        self._resource_uid = newUID
+        self._markAsDirty()
+        return self._resource_uid
+
+
     def resourceType(self):
         """
         @return: the name of the iCalendar type of the subcomponents in this
@@ -2558,7 +2730,7 @@
         Test and optionally remove alarms that have the same ACTION and TRIGGER values in the same component.
         """
         changed = False
-        if self.name() in ("VCALENDAR", "X-CALENDARSERVER-PERUSER",):
+        if self.name() in ("VCALENDAR", PERUSER_COMPONENT,):
             for component in self.subcomponents():
                 if component.name() in ("VTIMEZONE",):
                     continue
@@ -3074,8 +3246,8 @@
 
         results = set()
         for component in self.subcomponents():
-            if component.name() == "X-CALENDARSERVER-PERUSER":
-                results.add(component.propertyValue("X-CALENDARSERVER-PERUSER-UID"))
+            if component.name() == PERUSER_COMPONENT:
+                results.add(component.propertyValue(PERUSER_UID))
         return results
 
 
@@ -3088,10 +3260,10 @@
 
             # Do per-user data
             for component in self.subcomponents():
-                if component.name() == "X-CALENDARSERVER-PERUSER":
-                    uid = component.propertyValue("X-CALENDARSERVER-PERUSER-UID")
+                if component.name() == PERUSER_COMPONENT:
+                    uid = component.propertyValue(PERUSER_UID)
                     for subcomponent in component.subcomponents():
-                        if subcomponent.name() == "X-CALENDARSERVER-PERINSTANCE":
+                        if subcomponent.name() == PERINSTANCE_COMPONENT:
                             instancerid = subcomponent.propertyValue("RECURRENCE-ID")
                             transp = subcomponent.propertyValue("TRANSP") == "TRANSPARENT"
                             self._perUserTransparency.setdefault(uid, {})[instancerid] = transp

Modified: CalendarServer/trunk/twistedcaldav/test/test_extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_extensions.py	2013-06-21 19:26:50 UTC (rev 11392)
+++ CalendarServer/trunk/twistedcaldav/test/test_extensions.py	2013-06-21 21:33:23 UTC (rev 11393)
@@ -266,6 +266,7 @@
         self.assertTrue(applyTo)
         self.assertEquals(clientLimit, 42)
 
+
     def test_validateTokens(self):
         """
         Ensure validateTokens only returns True if there is at least one token
@@ -278,5 +279,3 @@
         self.assertFalse(validateTokens(["a", "b", "c"]))
         self.assertFalse(validateTokens([""]))
         self.assertFalse(validateTokens([]))
-
-

Modified: CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2013-06-21 19:26:50 UTC (rev 11392)
+++ CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2013-06-21 21:33:23 UTC (rev 11393)
@@ -9234,3 +9234,821 @@
             ical = Component.fromString(calendar)
             ical.removeAllPropertiesWithParameterMatch(property, param_name, param_value, param_default)
             self.assertEqual(str(ical), result.replace("\n", "\r\n"), "Failed remove property: %s" % (title,))
+
+
+    def test_newUID(self):
+
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090131T080000Z
+DTSTART:20090131T090000Z
+DTEND:20090131T100000Z
+DTSTAMP:20080601T120000Z
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+UID:valarm-1
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090102T080000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01-20090102
+TRIGGER;RELATED=START:-PT10M
+UID:valarm-2
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090131T080000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01-20090131
+TRIGGER;RELATED=START:-PT10M
+UID:valarm-3
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090201T080000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01-20090201
+TRIGGER;RELATED=START:-PT10M
+UID:valarm-4
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090202T080000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01-20090202
+TRIGGER;RELATED=START:-PT10M
+UID:valarm-5
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090202T080000Z
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test02
+TRIGGER;RELATED=START:-PT10M
+UID:valarm-6
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+"""
+
+        result = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:*
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:*
+RECURRENCE-ID:20090131T080000Z
+DTSTART:20090131T090000Z
+DTEND:20090131T100000Z
+DTSTAMP:20080601T120000Z
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:*
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+UID:valarm-1
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090102T080000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01-20090102
+TRIGGER;RELATED=START:-PT10M
+UID:valarm-2
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090131T080000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01-20090131
+TRIGGER;RELATED=START:-PT10M
+UID:valarm-3
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090201T080000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01-20090201
+TRIGGER;RELATED=START:-PT10M
+UID:valarm-4
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090202T080000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01-20090202
+TRIGGER;RELATED=START:-PT10M
+UID:valarm-5
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:*
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090202T080000Z
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test02
+TRIGGER;RELATED=START:-PT10M
+UID:valarm-6
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+"""
+
+        ical = Component.fromString(data)
+        self.assertEqual(ical.resourceUID(), "12345-67890-1")
+        newUID = ical.newUID()
+        self.assertNotEqual(ical.resourceUID(), "12345-67890-1")
+        self.assertEqual(ical.resourceUID(), newUID)
+        self.assertEqual(str(ical).replace(newUID, "*"), result.replace("\n", "\r\n"), "Failed newUID")
+
+
+    def test_onlyFuturePastInstances(self):
+
+        data = (
+            (
+                "1.1 - simple RRULE",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                PyCalendarDateTime(2009, 2, 1, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090201T080000Z
+DTEND:20090201T090000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY;UNTIL=20090201T075959Z
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "1.2 - RRULE with overrides",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090102T080000Z
+DTSTART:20090102T090000Z
+DTEND:20090102T100000Z
+DTSTAMP:20080601T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090131T080000Z
+DTSTART:20090131T090000Z
+DTEND:20090131T100000Z
+DTSTAMP:20080601T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090201T080000Z
+DTSTART:20090201T090000Z
+DTEND:20090201T100000Z
+DTSTAMP:20080601T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090202T080000Z
+DTSTART:20090202T090000Z
+DTEND:20090202T100000Z
+DTSTAMP:20080601T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                PyCalendarDateTime(2009, 2, 1, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090201T080000Z
+DTEND:20090201T090000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090201T080000Z
+DTSTART:20090201T090000Z
+DTEND:20090201T100000Z
+DTSTAMP:20080601T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090202T080000Z
+DTSTART:20090202T090000Z
+DTEND:20090202T100000Z
+DTSTAMP:20080601T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY;UNTIL=20090201T075959Z
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090102T080000Z
+DTSTART:20090102T090000Z
+DTEND:20090102T100000Z
+DTSTAMP:20080601T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090131T080000Z
+DTSTART:20090131T090000Z
+DTEND:20090131T100000Z
+DTSTAMP:20080601T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "1.3 - simple RRULE, EXDATE",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+EXDATE:20090102T080000Z
+EXDATE:20090131T080000Z
+EXDATE:20090201T080000Z
+EXDATE:20090202T080000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                PyCalendarDateTime(2009, 2, 1, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090201T080000Z
+DTEND:20090201T090000Z
+DTSTAMP:20080601T120000Z
+EXDATE:20090201T080000Z
+EXDATE:20090202T080000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+EXDATE:20090102T080000Z
+EXDATE:20090131T080000Z
+RRULE:FREQ=DAILY;UNTIL=20090201T075959Z
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "1.4 - simple RRULE, RDATE",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RDATE:20090102T180000Z
+RDATE:20090131T010000Z
+RDATE:20090131T180000Z
+RDATE:20090201T180000Z
+RDATE:20090202T180000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                PyCalendarDateTime(2009, 2, 1, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090201T080000Z
+DTEND:20090201T090000Z
+DTSTAMP:20080601T120000Z
+RDATE:20090201T180000Z
+RDATE:20090202T180000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RDATE:20090102T180000Z
+RDATE:20090131T010000Z
+RDATE:20090131T180000Z
+RRULE:FREQ=DAILY;UNTIL=20090201T075959Z
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "1.5 - just RDATE",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RDATE:20090102T180000Z
+RDATE:20090131T010000Z
+RDATE:20090131T080000Z
+RDATE:20090131T180000Z
+RDATE:20090201T180000Z
+RDATE:20090202T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                PyCalendarDateTime(2009, 1, 31, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090131T080000Z
+DTEND:20090131T090000Z
+DTSTAMP:20080601T120000Z
+RDATE:20090131T180000Z
+RDATE:20090201T180000Z
+RDATE:20090202T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RDATE:20090102T180000Z
+RDATE:20090131T010000Z
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "1.6 - RRULE, RDATE, cutoff on RDATE",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RDATE:20090102T180000Z
+RDATE:20090131T010000Z
+RDATE:20090131T060000Z
+RDATE:20090131T180000Z
+RDATE:20090201T180000Z
+RDATE:20090202T180000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                PyCalendarDateTime(2009, 1, 31, 6, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090131T080000Z
+DTEND:20090131T090000Z
+DTSTAMP:20080601T120000Z
+RDATE:20090131T060000Z
+RDATE:20090131T180000Z
+RDATE:20090201T180000Z
+RDATE:20090202T180000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RDATE:20090102T180000Z
+RDATE:20090131T010000Z
+RRULE:FREQ=DAILY;UNTIL=20090131T055959Z
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "2.1 - simple RRULE with peruser data on master",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test02
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                PyCalendarDateTime(2009, 2, 1, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090201T080000Z
+DTEND:20090201T090000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test02
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY;UNTIL=20090201T075959Z
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test02
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+            ),
+            (
+                "2.2 - simple RRULE with override peruser data",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090102T080000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01-20090102
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090131T080000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01-20090131
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090201T080000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01-20090201
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090202T080000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01-20090202
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090202T080000Z
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test02
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                PyCalendarDateTime(2009, 2, 1, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090201T080000Z
+DTEND:20090201T090000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090201T080000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01-20090201
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090202T080000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01-20090202
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090202T080000Z
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test02
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY;UNTIL=20090201T075959Z
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090102T080000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01-20090102
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20090131T080000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01-20090131
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+            ),
+        )
+
+        for title, calendar, rid, split_future, split_past in data:
+            ical1 = Component.fromString(calendar)
+            ical1.onlyFutureInstances(rid)
+            self.assertEqual(str(ical1), split_future.replace("\n", "\r\n"), "Failed future: %s" % (title,))
+            ical2 = Component.fromString(calendar)
+            ical2.onlyPastInstances(rid)
+            self.assertEqual(str(ical2), split_past.replace("\n", "\r\n"), "Failed past: %s" % (title,))

Modified: CalendarServer/trunk/twistedcaldav/test/test_localization.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_localization.py	2013-06-21 19:26:50 UTC (rev 11392)
+++ CalendarServer/trunk/twistedcaldav/test/test_localization.py	2013-06-21 21:33:23 UTC (rev 11393)
@@ -26,9 +26,7 @@
 
 def getComp(str):
     calendar = Component.fromString(str)
-    comp = calendar.masterComponent()
-    if comp is None:
-        comp = calendar.mainComponent(True)
+    comp = calendar.mainComponent()
     return comp
 
 data = (

Added: CalendarServer/trunk/txdav/caldav/datastore/scheduling/icalsplitter.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/icalsplitter.py	                        (rev 0)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/icalsplitter.py	2013-06-21 21:33:23 UTC (rev 11393)
@@ -0,0 +1,120 @@
+##
+# Copyright (c) 2013 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 pycalendar.datetime import PyCalendarDateTime
+from twistedcaldav.ical import Property
+
+
+class iCalSplitter(object):
+    """
+    Class that manages the "splitting" of large iCalendar objects into two pieces so that we can keep the overall
+    size of individual calendar objects to a reasonable limit. This should only be used on Organizer events.
+    """
+
+    def __init__(self, threshold, past):
+        """
+        @param threshold: the size in bytes that will trigger a split
+        @type threshold: C{int}
+        @param past: number of days in the past where the split will occur
+        @type past: C{int}
+
+        """
+        self.threshold = threshold
+        self.past = PyCalendarDateTime.getNowUTC()
+        self.past.setHHMMSS(0, 0, 0)
+        self.past.offsetDay(-past)
+        self.now = PyCalendarDateTime.getNowUTC()
+        self.now.setHHMMSS(0, 0, 0)
+        self.now.offsetDay(-1)
+
+
+    def willSplit(self, ical):
+        """
+        Determine if the specified iCalendar object needs to be split. Our policy is
+        we can only split recurring events with past instances and future instances.
+
+        @param ical: the iCalendar object to examine
+        @type ical: L{Component}
+
+        @return: C{True} if a split is require, C{False} otherwise
+        @rtype: C{bool}
+        """
+
+        # Must be recurring
+        if not ical.isRecurring():
+            return False
+
+        # Look for past/future (cacheExpandedTimeRanges will go one year in the future by default)
+        now = self.now.duplicate()
+        now.offsetDay(1)
+        instances = ical.cacheExpandedTimeRanges(now)
+        instances = sorted(instances.instances.values(), key=lambda x: x.start)
+        if instances[0].start > self.past or instances[-1].start < self.now or len(instances) <= 1:
+            return False
+
+        # Make sure there are some overridden components in the past - as splitting only makes sense when
+        # overrides are present
+        for instance in instances:
+            if instance.start > self.past:
+                return False
+            elif instance.component.hasProperty("RECURRENCE-ID"):
+                break
+        else:
+            return False
+
+        # Now see if overall size exceeds our threshold
+        return len(str(ical)) > self.threshold
+
+
+    def split(self, ical):
+        """
+        Split the specified iCalendar object. This assumes that L{willSplit} has already
+        been called and returned C{True}. Splitting is done by carving out old instances
+        into a new L{Component} and adjusting the specified component for the on-going
+        set. A RELATED-TO property is added to link old to new.
+
+        @param ical: the iCalendar object to split
+        @type ical: L{Component}
+
+        @return: iCalendar object for the old "carved out" instances
+        @rtype: L{Component}
+        """
+
+        # Find the instance RECURRENCE-ID where a split is going to happen
+        now = self.now.duplicate()
+        now.offsetDay(1)
+        instances = ical.cacheExpandedTimeRanges(now)
+        instances = sorted(instances.instances.values(), key=lambda x: x.start)
+        rid = instances[0].rid
+        for instance in instances:
+            rid = instance.rid
+            if instance.start >= self.past:
+                break
+
+        # Create the old one with a new UID value
+        icalOld = ical.duplicate()
+        oldUID = icalOld.newUID()
+        icalOld.onlyPastInstances(rid)
+
+        # Adjust the current one
+        ical.onlyFutureInstances(rid)
+
+        # Relate them - add RELATED-TO;RELTYPE=RECURRENCE-SET if not already present
+        if not icalOld.hasPropertyWithParameterMatch("RELATED-TO", "RELTYPE", "X-CALENDARSERVER-RECURRENCE-SET"):
+            property = Property("RELATED-TO", oldUID, params={"RELTYPE": "X-CALENDARSERVER-RECURRENCE-SET"})
+            icalOld.addPropertyToAllComponents(property)
+            ical.addPropertyToAllComponents(property)
+
+        return icalOld

Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/imip/outbound.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/imip/outbound.py	2013-06-21 19:26:50 UTC (rev 11392)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/imip/outbound.py	2013-06-21 21:33:23 UTC (rev 11393)
@@ -323,10 +323,7 @@
             duration = PyCalendarDuration(days=self.suppressionDays)
             onlyAfter = PyCalendarDateTime.getNowUTC() - duration
 
-        component = calendar.masterComponent()
-        if component is None:
-            component = calendar.mainComponent(True)
-        icaluid = component.propertyValue("UID")
+        icaluid = calendar.resourceUID()
         method = calendar.propertyValue("METHOD")
 
         # Clean up the attendee list which is purely used within the human
@@ -705,9 +702,7 @@
         """
 
         # Get the most appropriate component
-        component = calendar.masterComponent()
-        if component is None:
-            component = calendar.mainComponent(True)
+        component = calendar.mainComponent()
 
         results = {}
 

Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py	2013-06-21 19:26:50 UTC (rev 11392)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py	2013-06-21 21:33:23 UTC (rev 11393)
@@ -775,8 +775,7 @@
 
         # Extract UID from primary component as we want to ignore this one if we match it
         # in any calendars.
-        comp = calendar.mainComponent(allow_multiple=True)
-        uid = comp.propertyValue("UID")
+        uid = calendar.resourceUID()
 
         # Now compare each instance time-range with the index and see if there is an overlap
         fbset = (yield self.recipient.inbox.ownerHome().loadCalendars())

Added: CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icalsplitter.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icalsplitter.py	                        (rev 0)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icalsplitter.py	2013-06-21 21:33:23 UTC (rev 11393)
@@ -0,0 +1,1646 @@
+##
+# Copyright (c) 2013 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 pycalendar.datetime import PyCalendarDateTime
+from twisted.trial import unittest
+from twistedcaldav.stdconfig import config
+from twistedcaldav.ical import Component
+from txdav.caldav.datastore.scheduling.icalsplitter import iCalSplitter
+
+
+class ICalSplitter (unittest.TestCase):
+    """
+    iCalendar support tests
+    """
+
+    def setUp(self):
+        super(ICalSplitter, self).setUp()
+
+        self.subs = {}
+
+        self.now = PyCalendarDateTime.getNowUTC()
+        self.now.setHHMMSS(0, 0, 0)
+
+        self.subs["now"] = self.now
+
+        for i in range(30):
+            attrname = "now_back%s" % (i + 1,)
+            setattr(self, attrname, self.now.duplicate())
+            getattr(self, attrname).offsetDay(-(i + 1))
+            self.subs[attrname] = getattr(self, attrname)
+
+            attrname_12h = "now_back%s_12h" % (i + 1,)
+            setattr(self, attrname_12h, getattr(self, attrname).duplicate())
+            getattr(self, attrname_12h).offsetHours(12)
+            self.subs[attrname_12h] = getattr(self, attrname_12h)
+
+            attrname_1 = "now_back%s_1" % (i + 1,)
+            setattr(self, attrname_1, getattr(self, attrname).duplicate())
+            getattr(self, attrname_1).offsetSeconds(-1)
+            self.subs[attrname_1] = getattr(self, attrname_1)
+
+        for i in range(30):
+            attrname = "now_fwd%s" % (i + 1,)
+            setattr(self, attrname, self.now.duplicate())
+            getattr(self, attrname).offsetDay(i + 1)
+            self.subs[attrname] = getattr(self, attrname)
+
+            attrname_12h = "now_fwd%s_12h" % (i + 1,)
+            setattr(self, attrname_12h, getattr(self, attrname).duplicate())
+            getattr(self, attrname_12h).offsetHours(12)
+            self.subs[attrname_12h] = getattr(self, attrname_12h)
+
+        self.patch(config, "MaxAllowedInstances", 500)
+
+
+    def test_will_split(self):
+
+        data = (
+            (
+                "#1.1 Small, old, non-recurring component",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            (
+                "#1.2 Large, old, non-recurring component",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            (
+                "#2.1 Small, old, simple recurring component",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            (
+                "#2.2 Large, old, simple recurring component",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            (
+                "#3.1 Small, old, recurring with future override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd1)s
+DTSTART:%(now_fwd1)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            (
+                "#3.2 Large, old, recurring component with future override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd1)s
+DTSTART:%(now_fwd1)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            (
+                "#4.1 Small, old, recurring with past override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            (
+                "#4.2 Large, old, recurring component with past override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                True,
+            ),
+            (
+                "#5.1 Small, old, limited recurring with past override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=20
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            (
+                "#4.2 Large, old, limited recurring component with past override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+RRULE:FREQ=DAILY;COUNT=20
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            (
+                "#6.1 Small, old, limited future recurring with past override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=50
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            (
+                "#6.2 Large, old, limited future recurring component with past override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+RRULE:FREQ=DAILY;COUNT=50
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ORGANIZER:mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                True,
+            ),
+        )
+
+        for description, calendar, result in data:
+            ical = Component.fromString(calendar % self.subs)
+
+            splitter = iCalSplitter(1024, 14)
+            will_split = splitter.willSplit(ical)
+            self.assertEqual(will_split, result, msg="Failed: %s" % (description,))
+
+
+    def test_split(self):
+
+        data = (
+            (
+                "1.1 - RRULE with override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 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:%(now_back14)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:%(relID)s
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY;UNTIL=%(now_back14_1)s
+END:VEVENT
+BEGIN:VEVENT
+UID:%(relID)s
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "1.2 - RRULE with override, RDATEs, EXDATEs",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+EXDATE:%(now_back15)s
+EXDATE:%(now_back13)s
+EXDATE:%(now_fwd10)s
+ORGANIZER:mailto:user1 at example.com
+RDATE:%(now_back15_12h)s
+RDATE:%(now_back14_12h)s
+RDATE:%(now_fwd10_12h)s
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 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:%(now_back14)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+EXDATE:%(now_back13)s
+EXDATE:%(now_fwd10)s
+ORGANIZER:mailto:user1 at example.com
+RDATE:%(now_back14_12h)s
+RDATE:%(now_fwd10_12h)s
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:%(relID)s
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+EXDATE:%(now_back15)s
+ORGANIZER:mailto:user1 at example.com
+RDATE:%(now_back15_12h)s
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY;UNTIL=%(now_back14_1)s
+END:VEVENT
+BEGIN:VEVENT
+UID:%(relID)s
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "1.3 - RRULE with override, EXDATE at split",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+EXDATE:%(now_back14)s
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 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:%(now_back13)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:%(relID)s
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+EXDATE:%(now_back14)s
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY;UNTIL=%(now_back13_1)s
+END:VEVENT
+BEGIN:VEVENT
+UID:%(relID)s
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "1.4 - RRULE with override, RDATE at split",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+ORGANIZER:mailto:user1 at example.com
+RDATE:%(now_back15)s
+RDATE:%(now_back14)s
+RDATE:%(now_back13)s
+RRULE:FREQ=DAILY;INTERVAL=20
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back30)s
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back10)s
+DTSTART:%(now_back10)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 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:%(now_back10)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+ORGANIZER:mailto:user1 at example.com
+RDATE:%(now_back14)s
+RDATE:%(now_back13)s
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY;INTERVAL=20
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back10)s
+DTSTART:%(now_back10)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:%(relID)s
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+ORGANIZER:mailto:user1 at example.com
+RDATE:%(now_back15)s
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY;UNTIL=%(now_back14_1)s;INTERVAL=20
+END:VEVENT
+BEGIN:VEVENT
+UID:%(relID)s
+RECURRENCE-ID:%(now_back30)s
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "1.5 - RDATEs with RDATE at split",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+ORGANIZER:mailto:user1 at example.com
+RDATE:%(now_back15)s
+RDATE:%(now_back14)s
+RDATE:%(now_back13)s
+RDATE:%(now)s
+RDATE:%(now_fwd1)s
+RDATE:%(now_fwd2)s
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back30)s
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back14)s
+DTSTART:%(now_back14)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd1)s
+DTSTART:%(now_fwd1)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 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:%(now_back14)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+ORGANIZER:mailto:user1 at example.com
+RDATE:%(now_back13)s
+RDATE:%(now)s
+RDATE:%(now_fwd1)s
+RDATE:%(now_fwd2)s
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back14)s
+DTSTART:%(now_back14)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd1)s
+DTSTART:%(now_fwd1)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:%(relID)s
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+ORGANIZER:mailto:user1 at example.com
+RDATE:%(now_back15)s
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+END:VEVENT
+BEGIN:VEVENT
+UID:%(relID)s
+RECURRENCE-ID:%(now_back30)s
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "1.6 - RDATEs without RDATE at split",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+ORGANIZER:mailto:user1 at example.com
+RDATE:%(now_back15)s
+RDATE:%(now_back13)s
+RDATE:%(now)s
+RDATE:%(now_fwd1)s
+RDATE:%(now_fwd2)s
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back30)s
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd1)s
+DTSTART:%(now_fwd1)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 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:%(now_back13)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+ORGANIZER:mailto:user1 at example.com
+RDATE:%(now)s
+RDATE:%(now_fwd1)s
+RDATE:%(now_fwd2)s
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd1)s
+DTSTART:%(now_fwd1)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:%(relID)s
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+ORGANIZER:mailto:user1 at example.com
+RDATE:%(now_back15)s
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+END:VEVENT
+BEGIN:VEVENT
+UID:%(relID)s
+RECURRENCE-ID:%(now_back30)s
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "1.7 - RRULE with override, per-user data",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:%(now_back25)s
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test02
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:%(now_back13)s
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test03
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back14)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:%(now_back13)s
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test03
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:%(relID)s
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user4 at example.com
+ATTENDEE:mailto:user5 at example.com
+ATTENDEE:mailto:user6 at example.com
+ATTENDEE:mailto:user7 at example.com
+ATTENDEE:mailto:user8 at example.com
+ATTENDEE:mailto:user9 at example.com
+ATTENDEE:mailto:user10 at example.com
+ATTENDEE:mailto:user11 at example.com
+ATTENDEE:mailto:user12 at example.com
+ATTENDEE:mailto:user13 at example.com
+ATTENDEE:mailto:user14 at example.com
+ATTENDEE:mailto:user15 at example.com
+ATTENDEE:mailto:user16 at example.com
+ATTENDEE:mailto:user17 at example.com
+ATTENDEE:mailto:user18 at example.com
+ATTENDEE:mailto:user19 at example.com
+ATTENDEE:mailto:user20 at example.com
+ATTENDEE:mailto:user21 at example.com
+ATTENDEE:mailto:user22 at example.com
+ATTENDEE:mailto:user23 at example.com
+ATTENDEE:mailto:user24 at example.com
+ATTENDEE:mailto:user25 at example.com
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY;UNTIL=%(now_back14_1)s
+END:VEVENT
+BEGIN:VEVENT
+UID:%(relID)s
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:%(relID)s
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:%(now_back25)s
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test02
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+            ),
+        )
+
+        for title, calendar, split_future, split_past in data:
+            ical = Component.fromString(calendar % self.subs)
+            splitter = iCalSplitter(1024, 14)
+            self.assertTrue(splitter.willSplit(ical))
+            icalOld = splitter.split(ical)
+            relsubs = dict(self.subs)
+            relsubs["relID"] = icalOld.resourceUID()
+            self.assertEqual(str(ical).replace("\r\n ", ""), split_future.replace("\n", "\r\n") % relsubs, "Failed future: %s" % (title,))
+            self.assertEqual(str(icalOld).replace("\r\n ", ""), split_past.replace("\n", "\r\n") % relsubs, "Failed past: %s" % (title,))

Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py	2013-06-21 19:26:50 UTC (rev 11392)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2013-06-21 21:33:23 UTC (rev 11393)
@@ -1707,8 +1707,8 @@
             new_attendees = calendar.getAttendees()
             old_attendees = tuple(old_calendar.getAllAttendeeProperties())
 
-            new_completed = calendar.mainComponent().hasProperty("COMPLETED")
-            old_completed = old_calendar.mainComponent().hasProperty("COMPLETED")
+            new_completed = calendar.masterComponent().hasProperty("COMPLETED")
+            old_completed = old_calendar.masterComponent().hasProperty("COMPLETED")
 
             if old_organizer and not new_organizer and len(old_attendees) > 0 and len(new_attendees) == 0:
                 # Transfer old organizer and attendees to new calendar
@@ -1857,7 +1857,7 @@
         vevent = mtype == "VEVENT"
 
         # Check timed or all-day
-        start, _ignore_end = component.mainComponent(allow_multiple=True).getEffectiveStartEnd()
+        start, _ignore_end = component.mainComponent().getEffectiveStartEnd()
         if start is None:
             # Yes VTODOs might have no DTSTART or DUE - in this case we do not add a default
             return

Modified: CalendarServer/trunk/txdav/caldav/datastore/util.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/util.py	2013-06-21 19:26:50 UTC (rev 11392)
+++ CalendarServer/trunk/txdav/caldav/datastore/util.py	2013-06-21 21:33:23 UTC (rev 11393)
@@ -38,8 +38,8 @@
 from twext.python.vcomponent import VComponent
 
 from twistedcaldav.datafilters.hiddeninstance import HiddenInstanceFilter
-from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
+from twistedcaldav.ical import PERUSER_UID
 
 from txdav.common.icommondatastore import (
     InvalidObjectResourceError, NoSuchObjectResourceError,
@@ -636,7 +636,7 @@
     fixes = 0
     for calprop in component.properties():
         if calprop.name() in (
-            "ATTENDEE", "ORGANIZER", PerUserDataFilter.PERUSER_UID
+            "ATTENDEE", "ORGANIZER", PERUSER_UID
         ):
             preval = calprop.value()
             postval = normalizeUUIDOrNot(preval)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130621/fdaf02ae/attachment-0001.html>


More information about the calendarserver-changes mailing list