[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