[CalendarServer-changes] [11207] CalendarServer/trunk/twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Thu May 16 19:11:38 PDT 2013


Revision: 11207
          http://trac.calendarserver.org//changeset/11207
Author:   cdaboo at apple.com
Date:     2013-05-16 19:11:38 -0700 (Thu, 16 May 2013)
Log Message:
-----------
Fix for case where R-ID property is malformed with T000000. Try using the master's time value and see if
R-ID is then valid.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/instance.py
    CalendarServer/trunk/twistedcaldav/test/test_icalendar.py

Modified: CalendarServer/trunk/twistedcaldav/instance.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/instance.py	2013-05-17 02:07:21 UTC (rev 11206)
+++ CalendarServer/trunk/twistedcaldav/instance.py	2013-05-17 02:11:38 UTC (rev 11207)
@@ -139,7 +139,7 @@
         """
 
         # Look at each component type
-        got_master = False
+        master = None
         overrides = []
         for component in componentSet:
             if component.name() == "VEVENT":
@@ -147,13 +147,13 @@
                     overrides.append(component)
                 else:
                     self._addMasterEventComponent(component, lowerLimit, limit)
-                    got_master = True
+                    master = component
             elif component.name() == "VTODO":
                 if component.hasProperty("RECURRENCE-ID"):
                     overrides.append(component)
                 else:
                     self._addMasterToDoComponent(component, lowerLimit, limit)
-                    got_master = True
+                    master = component
             elif component.name() == "VJOURNAL":
                 #TODO: VJOURNAL
                 raise NotImplementedError("VJOURNAL recurrence expansion not supported yet")
@@ -167,19 +167,19 @@
                 else:
                     # AVAILABLE components are just like VEVENT components
                     self._addMasterEventComponent(component, lowerLimit, limit)
-                    got_master = True
+                    master = component
 
         for component in overrides:
             if component.name() == "VEVENT":
-                self._addOverrideEventComponent(component, lowerLimit, limit, got_master)
+                self._addOverrideEventComponent(component, lowerLimit, limit, master)
             elif component.name() == "VTODO":
-                self._addOverrideToDoComponent(component, lowerLimit, limit, got_master)
+                self._addOverrideToDoComponent(component, lowerLimit, limit, master)
             elif component.name() == "VJOURNAL":
                 #TODO: VJOURNAL
                 raise NotImplementedError("VJOURNAL recurrence expansion not supported yet")
             elif component.name() == "AVAILABLE":
                 # AVAILABLE components are just like VEVENT components
-                self._addOverrideEventComponent(component, lowerLimit, limit, got_master)
+                self._addOverrideEventComponent(component, lowerLimit, limit, master)
 
 
     def addInstance(self, instance):
@@ -260,12 +260,12 @@
         self._addMasterComponent(component, lowerLimit, upperLimit, rulestart, start, end, duration)
 
 
-    def _addOverrideEventComponent(self, component, lowerLimit, upperLimit, got_master):
+    def _addOverrideEventComponent(self, component, lowerLimit, upperLimit, master):
         """
         Add the specified overridden VEVENT Component to the instance list, replacing
         the one generated by the master component.
         @param component: the overridden Component.
-        @param got_master: whether a master component has already been expanded.
+        @param master: the master component which has already been expanded, or C{None}.
         """
 
         #TODO: This does not take into account THISANDPRIOR - only THISANDFUTURE
@@ -276,7 +276,7 @@
         _ignore_rulestart, start, end, _ignore_duration = details
 
         lowerLimit, upperLimit = self._setupLimits(start, lowerLimit, upperLimit)
-        self._addOverrideComponent(component, lowerLimit, upperLimit, start, end, got_master)
+        self._addOverrideComponent(component, lowerLimit, upperLimit, start, end, master)
 
 
     def _getMasterToDoDetails(self, component):
@@ -341,12 +341,12 @@
         self._addMasterComponent(component, lowerLimit, upperLimit, rulestart, start, end, duration)
 
 
-    def _addOverrideToDoComponent(self, component, lowerLimit, upperLimit, got_master):
+    def _addOverrideToDoComponent(self, component, lowerLimit, upperLimit, master):
         """
         Add the specified overridden VTODO Component to the instance list, replacing
         the one generated by the master component.
         @param component: the overridden Component.
-        @param got_master: whether a master component has already been expanded.
+        @param master: the master component which has already been expanded, or C{None}.
         """
 
         #TODO: This does not take into account THISANDPRIOR - only THISANDFUTURE
@@ -357,7 +357,7 @@
         _ignore_rulestart, start, end, _ignore_duration = details
 
         lowerLimit, upperLimit = self._setupLimits(start, lowerLimit, upperLimit)
-        self._addOverrideComponent(component, lowerLimit, upperLimit, start, end, got_master)
+        self._addOverrideComponent(component, lowerLimit, upperLimit, start, end, master)
 
 
     def _addMasterComponent(self, component, lowerLimit, upperlimit, rulestart, start, end, duration):
@@ -395,7 +395,7 @@
         self.master_cancelled = component.propertyValue("STATUS") == "CANCELLED"
 
 
-    def _addOverrideComponent(self, component, lowerLimit, upperlimit, start, end, got_master):
+    def _addOverrideComponent(self, component, lowerLimit, upperlimit, start, end, master):
 
         # Get the recurrence override info
         rid = component.getRecurrenceIDUTC()
@@ -414,13 +414,27 @@
 
         # Make sure override RECURRENCE-ID is a valid instance of the master
         cancelled = component.propertyValue("STATUS") == "CANCELLED"
-        if got_master:
+        if master is not None:
             if str(rid) not in self.instances and rid < upperlimit and (lowerLimit is None or rid >= lowerLimit):
                 if self.master_cancelled or cancelled:
                     # Ignore invalid overrides when either the master or override is cancelled
                     pass
                 elif self.ignoreInvalidInstances:
                     return
+                elif component.name() == "VEVENT":
+                    # Try to fix the R-ID in the case where the hour/minute/second components are all zero
+                    original_rid = component.propertyValue("RECURRENCE-ID").duplicate()
+                    if not original_rid.isDateOnly() and original_rid.mHours == 0 and original_rid.mMinutes == 0 and original_rid.mSeconds == 0:
+                        master_start = master.propertyValue("DTSTART")
+                        original_rid.setHHMMSS(master_start.mHours, master_start.mMinutes, master_start.mSeconds)
+                        rid = original_rid.duplicateAsUTC()
+                        rid = self.normalizeFunction(rid)
+                        if str(rid) not in self.instances:
+                            raise InvalidOverriddenInstanceError(str(rid))
+                        else:
+                            component.getProperty("RECURRENCE-ID").setValue(original_rid)
+                    else:
+                        raise InvalidOverriddenInstanceError(str(rid))
                 else:
                     raise InvalidOverriddenInstanceError(str(rid))
 

Modified: CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2013-05-17 02:07:21 UTC (rev 11206)
+++ CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2013-05-17 02:11:38 UTC (rev 11207)
@@ -5317,6 +5317,108 @@
             self.assertEqual(len(unfixed), result_unfixed, "Failed unfixed: %s %s" % (title, unfixed,))
 
 
+    def test_fix_invalid_recurrence_id(self):
+
+        data = (
+            (
+                "Recurring with override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=US/Pacific:20071114T120000
+DTSTAMP:20080601T120000Z
+DURATION:PT1H
+RRULE:FREQ=DAILY;COUNT=2
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID;TZID=US/Pacific:20071115T000000
+DTSTART;TZID=US/Pacific:20071115T130000
+DTSTAMP:20080601T120000Z
+DURATION:PT1H
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=US/Pacific:20071114T120000
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY;COUNT=2
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID;TZID=US/Pacific:20071115T120000
+DTSTART;TZID=US/Pacific:20071115T130000
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    (
+                        PyCalendarDateTime(2007, 11, 14, 20, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 14, 21, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 15, 21, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 15, 22, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                )
+            ),
+        )
+
+        for description, original, fixed, results in data:
+            component = Component.fromString(original)
+            instances = component.expandTimeRanges(PyCalendarDateTime(2100, 1, 1), ignoreInvalidInstances=False)
+            self.assertTrue(len(instances.instances) == len(results), "%s: wrong number of instances" % (description,))
+            periods = tuple([(instance.start, instance.end) for instance in sorted(instances.instances.values(), key=lambda x:x.start)])
+            self.assertEqual(periods, results)
+            for start, end in periods:
+                self.assertEqual(start.isDateOnly(), results[0][0].isDateOnly(), "%s: %s wrong date/time start state" % (description, start,))
+                self.assertEqual(end.isDateOnly(), results[0][1].isDateOnly(), "%s: %s wrong date/time end state" % (description, end,))
+            self.assertEqual(str(component), fixed.replace("\n", "\r\n"))
+
+
     def test_mismatched_until(self):
         invalid = (
             """BEGIN:VCALENDAR
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130516/e3ab000b/attachment-0001.html>


More information about the calendarserver-changes mailing list