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

source_changes at macosforge.org source_changes at macosforge.org
Mon May 14 09:51:04 PDT 2012


Revision: 9245
          http://trac.macosforge.org/projects/calendarserver/changeset/9245
Author:   cdaboo at apple.com
Date:     2012-05-14 09:51:03 -0700 (Mon, 14 May 2012)
Log Message:
-----------
All-day events expanded in a report should have all-day date values.

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

Modified: CalendarServer/trunk/twistedcaldav/dateops.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/dateops.py	2012-05-14 16:50:08 UTC (rev 9244)
+++ CalendarServer/trunk/twistedcaldav/dateops.py	2012-05-14 16:51:03 UTC (rev 9245)
@@ -77,6 +77,23 @@
         dt.adjustToUTC()
         return dt
 
+def normalizeForExpand(dt):
+    """
+    Normalize a L{PyCalendarDateTime} object for use with the CalDAV expand option.
+    Convert to date-time in UTC, leave date only and floating alone.
+    @param dt: a L{PyCalendarDateTime} object to normalize
+    @return: the normalized PyCalendarDateTime
+    """
+    if not isinstance(dt, PyCalendarDateTime):
+        raise TypeError("%r is not a PyCalendarDateTime instance" % (dt,))
+    
+    dt = dt.duplicate()
+    if dt.isDateOnly() or dt.floating():
+        return dt
+    else:
+        dt.adjustToUTC()
+        return dt
+
 def floatoffset(dt, pytz):
     """
     Apply the timezone offset to the supplied time, then force tz to utc. This gives the local
@@ -122,10 +139,10 @@
 def timeRangesOverlap(start1, end1, start2, end2, defaulttz = None):
     # Can't compare date-time and date only, so normalize
     # to date only if they are mixed.
-    if (start1 is not None) and not start1.isDateOnly() and (start2 is not None) and start2.isDateOnly(): start1 = start1.setDateOnly(True)
-    if (start2 is not None) and not start2.isDateOnly() and (start1 is not None) and start1.isDateOnly(): start2 = start2.setDateOnly(True)
-    if (end1 is not None) and not end1.isDateOnly() and (end2 is not None) and end2.isDateOnly(): end1 = end1.setDateOnly(True)
-    if (end2 is not None) and not end2.isDateOnly() and (end1 is not None) and end1.isDateOnly(): end2 = end2.setDateOnly(True)
+    if (start1 is not None) and not start1.isDateOnly() and (start2 is not None) and start2.isDateOnly(): start1.setDateOnly(True)
+    if (start2 is not None) and not start2.isDateOnly() and (start1 is not None) and start1.isDateOnly(): start2.setDateOnly(True)
+    if (end1 is not None) and not end1.isDateOnly() and (end2 is not None) and end2.isDateOnly(): end1.setDateOnly(True)
+    if (end2 is not None) and not end2.isDateOnly() and (end1 is not None) and end1.isDateOnly(): end2.setDateOnly(True)
 
     # Note that start times are inclusive and end times are not.
     if start1 is not None and start2 is not None:

Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py	2012-05-14 16:50:08 UTC (rev 9244)
+++ CalendarServer/trunk/twistedcaldav/ical.py	2012-05-14 16:51:03 UTC (rev 9245)
@@ -39,7 +39,8 @@
 from twext.web2.dav.util import allDataFromStream
 
 from twistedcaldav.config import config
-from twistedcaldav.dateops import timeRangesOverlap, normalizeForIndex, differenceDateTime
+from twistedcaldav.dateops import timeRangesOverlap, normalizeForIndex, differenceDateTime,\
+    normalizeForExpand
 from twistedcaldav.instance import InstanceList
 from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
 from twistedcaldav.timezones import hasTZ, TimezoneException
@@ -953,6 +954,7 @@
         Expand the components into a set of new components, one for each
         instance in the specified range. Date-times are converted to UTC. A
         new calendar object is returned.
+
         @param start: the L{PyCalendarDateTime} for the start of the range.
         @param end: the L{PyCalendarDateTime} for the end of the range.
         @param timezone: the L{Component} the VTIMEZONE to use for floating/all-day.
@@ -969,12 +971,15 @@
         for property in self.properties():
             calendar.addProperty(property)
         
-        # Expand the instances and add each one
-        instances = self.expandTimeRanges(end)
+        # Expand the instances and add each one - use the normalizeForExpand date/time normalization method here
+        # so that all-day date/times remain that way. However, when doing the timeRangesOverlap test below, we
+        # Need to convert the all-days to floating (T000000) so that the timezone overlap calculation can be done
+        # properly.
+        instances = self.expandTimeRanges(end, normalizeFunction=normalizeForExpand)
         first = True
         for key in instances:
             instance = instances[key]
-            if timeRangesOverlap(instance.start, instance.end, start, end, pytz):
+            if timeRangesOverlap(normalizeForIndex(instance.start), normalizeForIndex(instance.end), start, end, pytz):
                 calendar.addComponent(self.expandComponent(instance, first))
             first = False
         
@@ -1038,7 +1043,7 @@
             ignoreInvalidInstances=ignoreInvalidInstances)
         return self.cachedInstances
 
-    def expandTimeRanges(self, limit, ignoreInvalidInstances=False):
+    def expandTimeRanges(self, limit, ignoreInvalidInstances=False, normalizeFunction=normalizeForIndex):
         """
         Expand the set of recurrence instances for the components
         contained within this VCALENDAR component. We will assume
@@ -1050,23 +1055,35 @@
         """
         
         componentSet = self.subcomponents()
-        return self.expandSetTimeRanges(componentSet, limit, ignoreInvalidInstances)
+        return self.expandSetTimeRanges(componentSet, limit, ignoreInvalidInstances, normalizeFunction=normalizeFunction)
     
-    def expandSetTimeRanges(self, componentSet, limit, ignoreInvalidInstances=False):
+    def expandSetTimeRanges(self, componentSet, limit, ignoreInvalidInstances=False, normalizeFunction=normalizeForIndex):
         """
         Expand the set of recurrence instances up to the specified date limit.
         What we do is first expand the master instance into the set of generate
         instances. Then we merge the overridden instances, taking into account
         THISANDFUTURE and THISANDPRIOR.
-        @param limit: L{PyCalendarDateTime} value representing the end of the expansion.
+
         @param componentSet: the set of components that are to make up the
                 recurrence set. These MUST all be components with the same UID
                 and type, forming a proper recurring set.
-        @return: a set of Instances for each recurrence in the set.
+        @param limit: L{PyCalendarDateTime} value representing the end of the expansion.
+
+        @param componentSet: the set of components that are to make up the recurrence set.
+            These MUST all be components with the same UID and type, forming a proper
+            recurring set.
+        @type componentSet: C{list}
+        @param limit: the end of the expansion
+        @type limit: L{PyCalendarDateTime}
+        @param ignoreInvalidInstances: whether or not invalid recurrences raise an exception
+        @type ignoreInvalidInstances: C{bool}
+        @param normalizeFunction: a function used to normalize date/time values in instances
+        @type normalizeFunction: C{function}
+        @return: L{InstanceList} containing expanded L{Instance} for each recurrence in the set.
         """
         
         # Set of instances to return
-        instances = InstanceList(ignoreInvalidInstances=ignoreInvalidInstances)
+        instances = InstanceList(ignoreInvalidInstances=ignoreInvalidInstances, normalizeFunction=normalizeFunction)
         instances.expandTimeRanges(componentSet, limit)
         return instances
 

Modified: CalendarServer/trunk/twistedcaldav/instance.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/instance.py	2012-05-14 16:50:08 UTC (rev 9244)
+++ CalendarServer/trunk/twistedcaldav/instance.py	2012-05-14 16:51:03 UTC (rev 9245)
@@ -85,10 +85,11 @@
 
 class InstanceList(object):
     
-    def __init__(self, ignoreInvalidInstances=False):
+    def __init__(self, ignoreInvalidInstances=False, normalizeFunction=normalizeForIndex):
         self.instances = {}
         self.limit = None
         self.ignoreInvalidInstances = ignoreInvalidInstances
+        self.normalizeFunction = normalizeFunction
         
     def __iter__(self):
         # Return keys in sorted order via iterator
@@ -310,7 +311,7 @@
             limited = rrules.expand(rulestart,
                 PyCalendarPeriod(PyCalendarDateTime(1900,1,1), limit), expanded)
             for startDate in expanded:
-                startDate = normalizeForIndex(startDate)
+                startDate = self.normalizeFunction(startDate)
                 endDate = startDate + duration
                 self.addInstance(Instance(component, startDate, endDate))
             if limited:
@@ -318,8 +319,8 @@
         else:
             # Always add main instance if included in range.
             if start < limit:
-                start = normalizeForIndex(start)
-                end = normalizeForIndex(end)
+                start = self.normalizeFunction(start)
+                end = self.normalizeFunction(end)
                 self.addInstance(Instance(component, start, end))
             else:
                 self.limit = limit
@@ -331,9 +332,9 @@
         range = component.getRange()
         
         # Now add this instance, effectively overriding the one with the matching R-ID
-        start = normalizeForIndex(start)
-        end = normalizeForIndex(end)
-        rid = normalizeForIndex(rid)
+        start = self.normalizeFunction(start)
+        end = self.normalizeFunction(end)
+        rid = self.normalizeFunction(rid)
 
         # Make sure start is within the limit
         if start > limit and rid > limit:
@@ -407,8 +408,8 @@
                 period = period.getValue()
                 if period.getStart() >= limit:
                     continue
-                start = normalizeForIndex(period.getStart())
-                end = normalizeForIndex(period.getEnd())
+                start = self.normalizeFunction(period.getStart())
+                end = self.normalizeFunction(period.getEnd())
                 self.addInstance(Instance(component, start, end))
 
     def _addAvailabilityComponent(self, component, limit):
@@ -428,11 +429,11 @@
             return
         if start is None:
             start = PyCalendarDateTime(1900, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
-        start = normalizeForIndex(start)
+        start = self.normalizeFunction(start)
 
         end = component.getEndDateUTC()
         if end is None:
             end = PyCalendarDateTime(2100, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
-        end = normalizeForIndex(end)
+        end = self.normalizeFunction(end)
 
         self.addInstance(Instance(component, start, end))

Modified: CalendarServer/trunk/twistedcaldav/test/test_dateops.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_dateops.py	2012-05-14 16:50:08 UTC (rev 9244)
+++ CalendarServer/trunk/twistedcaldav/test/test_dateops.py	2012-05-14 16:51:03 UTC (rev 9245)
@@ -18,21 +18,71 @@
 from twisted.trial.unittest import SkipTest
 from pycalendar.datetime import PyCalendarDateTime
 from twistedcaldav.dateops import parseSQLTimestampToPyCalendar,\
-    parseSQLDateToPyCalendar, parseSQLTimestamp, pyCalendarTodatetime
+    parseSQLDateToPyCalendar, parseSQLTimestamp, pyCalendarTodatetime,\
+    normalizeForExpand, normalizeForIndex, normalizeToUTC, timeRangesOverlap
 import datetime
 import dateutil
+from pycalendar.timezone import PyCalendarTimezone
+from twistedcaldav.timezones import TimezoneCache
 
 class Dateops(twistedcaldav.test.util.TestCase):
     """
     dateops.py tests
     """
 
+    def setUp(self):
+        super(Dateops, self).setUp()
+        TimezoneCache.create()
+
+
     def test_normalizeForIndex(self):
-        raise SkipTest("test unimplemented")
+        """
+        Test that dateops.normalizeForIndex works correctly on all four types of date/time: date only, floating, UTC and local time.
+        """
+        
+        data = (
+            (PyCalendarDateTime(2012, 1, 1), PyCalendarDateTime(2012, 1, 1, 0, 0, 0)),
+            (PyCalendarDateTime(2012, 1, 1, 10, 0, 0), PyCalendarDateTime(2012, 1, 1, 10, 0, 0)),
+            (PyCalendarDateTime(2012, 1, 1, 11, 0, 0, tzid=PyCalendarTimezone(utc=True)), PyCalendarDateTime(2012, 1, 1, 11, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+            (PyCalendarDateTime(2012, 1, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), PyCalendarDateTime(2012, 1, 1, 17, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+        )
+        
+        for value, result in data:
+            self.assertEqual(normalizeForIndex(value), result)
 
+
     def test_normalizeToUTC(self):
-        raise SkipTest("test unimplemented")
+        """
+        Test that dateops.normalizeToUTC works correctly on all four types of date/time: date only, floating, UTC and local time.
+        """
+        
+        data = (
+            (PyCalendarDateTime(2012, 1, 1), PyCalendarDateTime(2012, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+            (PyCalendarDateTime(2012, 1, 1, 10, 0, 0), PyCalendarDateTime(2012, 1, 1, 10, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+            (PyCalendarDateTime(2012, 1, 1, 11, 0, 0, tzid=PyCalendarTimezone(utc=True)), PyCalendarDateTime(2012, 1, 1, 11, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+            (PyCalendarDateTime(2012, 1, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), PyCalendarDateTime(2012, 1, 1, 17, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+        )
+        
+        for value, result in data:
+            self.assertEqual(normalizeToUTC(value), result)
 
+
+    def test_normalizeForExpand(self):
+        """
+        Test that dateops.normalizeForExpand works correctly on all four types of date/time: date only, floating, UTC and local time.
+        """
+        
+        data = (
+            (PyCalendarDateTime(2012, 1, 1), PyCalendarDateTime(2012, 1, 1)),
+            (PyCalendarDateTime(2012, 1, 1, 10, 0, 0), PyCalendarDateTime(2012, 1, 1, 10, 0, 0)),
+            (PyCalendarDateTime(2012, 1, 1, 11, 0, 0, tzid=PyCalendarTimezone(utc=True)), PyCalendarDateTime(2012, 1, 1, 11, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+            (PyCalendarDateTime(2012, 1, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), PyCalendarDateTime(2012, 1, 1, 17, 0, 0, tzid=PyCalendarTimezone(utc=True))),
+        )
+        
+        for value, result in data:
+            self.assertEqual(normalizeForExpand(value), result)
+
+
     def test_floatoffset(self):
         raise SkipTest("test unimplemented")
 
@@ -46,7 +96,144 @@
         raise SkipTest("test unimplemented")
 
     def test_timeRangesOverlap(self):
-        raise SkipTest("test unimplemented")
+        
+        data = (
+            # Timed
+            (
+                "Start within, end within - overlap",
+                PyCalendarDateTime(2012, 1, 1, 11, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 2, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                True,
+            ),
+            (
+                "Start before, end before - no overlap",
+                PyCalendarDateTime(2012, 1, 1, 11, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 2, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 3, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                False,
+            ),
+            (
+                "Start before, end right before - no overlap",
+                PyCalendarDateTime(2012, 1, 1, 23, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 2, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 2, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 3, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                False,
+            ),
+            (
+                "Start before, end within - overlap",
+                PyCalendarDateTime(2012, 1, 1, 11, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 2, 11, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 2, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 3, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                True,
+            ),
+            (
+                "Start after, end after - no overlap",
+                PyCalendarDateTime(2012, 1, 2, 11, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 2, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 2, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                False,
+            ),
+            (
+                "Start right after, end after - no overlap",
+                PyCalendarDateTime(2012, 1, 2, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 2, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 2, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                False,
+            ),
+            (
+                "Start within, end after - overlap",
+                PyCalendarDateTime(2012, 1, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 2, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 2, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                True,
+            ),
+            (
+                "Start before, end after - overlap",
+                PyCalendarDateTime(2012, 1, 1, 11, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 3, 11, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 2, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 3, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                True,
+            ),
+            
+            # All day
+            (
+                "All day: Start within, end within - overlap",
+                PyCalendarDateTime(2012, 1, 9),
+                PyCalendarDateTime(2012, 1, 10),
+                PyCalendarDateTime(2012, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                True,
+            ),
+            (
+                "All day: Start before, end before - no overlap",
+                PyCalendarDateTime(2012, 1, 1),
+                PyCalendarDateTime(2012, 1, 2),
+                PyCalendarDateTime(2012, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                False,
+            ),
+            (
+                "All day: Start before, end right before - no overlap",
+                PyCalendarDateTime(2012, 1, 7),
+                PyCalendarDateTime(2012, 1, 8),
+                PyCalendarDateTime(2012, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                False,
+            ),
+            (
+                "All day: Start before, end within - overlap",
+                PyCalendarDateTime(2012, 1, 7),
+                PyCalendarDateTime(2012, 1, 9),
+                PyCalendarDateTime(2012, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                True,
+            ),
+            (
+                "All day: Start after, end after - no overlap",
+                PyCalendarDateTime(2012, 1, 16),
+                PyCalendarDateTime(2012, 1, 17),
+                PyCalendarDateTime(2012, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                False,
+            ),
+            (
+                "All day: Start right after, end after - no overlap",
+                PyCalendarDateTime(2012, 1, 15),
+                PyCalendarDateTime(2012, 1, 16),
+                PyCalendarDateTime(2012, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                False,
+            ),
+            (
+                "All day: Start within, end after - overlap",
+                PyCalendarDateTime(2012, 1, 14),
+                PyCalendarDateTime(2012, 1, 16),
+                PyCalendarDateTime(2012, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                True,
+            ),
+            (
+                "All day: Start before, end after - overlap",
+                PyCalendarDateTime(2012, 1, 7),
+                PyCalendarDateTime(2012, 1, 16),
+                PyCalendarDateTime(2012, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                PyCalendarDateTime(2012, 1, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                True,
+            ),
+        )
+        
+        for title, start1, end1, start2, end2, result in data:
+            self.assertEqual(timeRangesOverlap(start1, end1, start2, end2), result, msg="Failed: %s" % (title,))
+            
 
     def test_normalizePeriodList(self):
         raise SkipTest("test unimplemented")

Modified: CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2012-05-14 16:50:08 UTC (rev 9244)
+++ CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2012-05-14 16:51:03 UTC (rev 9245)
@@ -29,6 +29,7 @@
 from pycalendar.timezone import PyCalendarTimezone
 from twistedcaldav.ical import iCalendarProductID
 from pycalendar.duration import PyCalendarDuration
+from twistedcaldav.dateops import normalizeForExpand
 
 class iCalendar (twistedcaldav.test.util.TestCase):
     """
@@ -2220,7 +2221,12 @@
 END:VCALENDAR
 """,
                 False,
-                (PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),)
+                (
+                    (
+                        PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 14, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                )
             ),
             (
                 "Simple recurring",
@@ -2238,8 +2244,14 @@
 """,
                 False,
                 (
-                    PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
-                    PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    (
+                        PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 14, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 15, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
                 )
             ),
             (
@@ -2259,9 +2271,18 @@
 """,
                 False,
                 (
-                    PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
-                    PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
-                    PyCalendarDateTime(2007, 11, 16, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    (
+                        PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 14, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 15, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 16, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 16, 2, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
                 )
             ),
             (
@@ -2281,8 +2302,14 @@
 """,
                 False,
                 (
-                    PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
-                    PyCalendarDateTime(2007, 11, 16, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    (
+                        PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 14, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 16, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 16, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
                 )
             ),
             (
@@ -2302,8 +2329,14 @@
 """,
                 False,
                 (
-                    PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
-                    PyCalendarDateTime(2007, 11, 16, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    (
+                        PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 15, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 16, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 16, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
                 )
             ),
             (
@@ -2329,8 +2362,14 @@
 """,
                 False,
                 (
-                    PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
-                    PyCalendarDateTime(2007, 11, 15, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    (
+                        PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 14, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 15, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 15, 2, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
                 )
             ),
             (
@@ -2380,8 +2419,14 @@
 """,
                 True,
                 (
-                    PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
-                    PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    (
+                        PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 14, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 15, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
                 )
             ),
         )
@@ -2393,9 +2438,354 @@
             else:
                 instances = component.expandTimeRanges(PyCalendarDateTime(2100, 1, 1), ignoreInvalidInstances)
                 self.assertTrue(len(instances.instances) == len(results), "%s: wrong number of instances" % (description,))
-                for instance in instances:
-                    self.assertTrue(instances[instance].start in results, "%s: %s missing" % (description, instance,))
+                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,))
        
+    def test_expand_instances_for_expand(self):
+        
+        data = (
+            (
+                "Non recurring utc",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
+DURATION:PT1H
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+                (
+                    (
+                        PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 14, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                )
+            ),
+            (
+                "Non recurring all-day",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;VALUE=DATE:20071115
+DTSTAMP:20080601T120000Z
+DURATION:P1D
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+                (
+                    (
+                        PyCalendarDateTime(2007, 11, 15),
+                        PyCalendarDateTime(2007, 11, 16),
+                    ),
+                )
+            ),
+            (
+                "Simple recurring utc",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
+DURATION:PT1H
+RRULE:FREQ=DAILY;COUNT=2
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+                (
+                    (
+                        PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 14, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 15, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                )
+            ),
+            (
+                "Simple recurring all day",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;VALUE=DATE:20071115
+DTSTAMP:20080601T120000Z
+DURATION:P1D
+RRULE:FREQ=DAILY;COUNT=2
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+                (
+                    (
+                        PyCalendarDateTime(2007, 11, 15),
+                        PyCalendarDateTime(2007, 11, 16),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 16),
+                        PyCalendarDateTime(2007, 11, 17),
+                    ),
+                )
+            ),
+            (
+                "Recurring with RDATE utc",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
+DURATION:PT1H
+RRULE:FREQ=DAILY;COUNT=2
+RDATE:20071116T010000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+                (
+                    (
+                        PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 14, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 15, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 16, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 16, 2, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                )
+            ),
+            (
+                "Recurring with RDATE all-day",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;VALUE=DATE:20071115
+DTSTAMP:20080601T120000Z
+DURATION:P1D
+RRULE:FREQ=DAILY;COUNT=2
+RDATE;VALUE=DATE:20071118
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+                (
+                    (
+                        PyCalendarDateTime(2007, 11, 15),
+                        PyCalendarDateTime(2007, 11, 16),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 16),
+                        PyCalendarDateTime(2007, 11, 17),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 18),
+                        PyCalendarDateTime(2007, 11, 19),
+                    ),
+                )
+            ),
+            (
+                "Recurring with EXDATE utc",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
+DURATION:PT1H
+RRULE:FREQ=DAILY;COUNT=3
+EXDATE:20071115T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+                (
+                    (
+                        PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 14, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 16, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 16, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                )
+            ),
+            (
+                "Recurring with EXDATE all-day",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;VALUE=DATE:20071115
+DTSTAMP:20080601T120000Z
+DURATION:P1D
+RRULE:FREQ=DAILY;COUNT=3
+EXDATE;VALUE=DATE:20071116
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+                (
+                    (
+                        PyCalendarDateTime(2007, 11, 15),
+                        PyCalendarDateTime(2007, 11, 16),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 17),
+                        PyCalendarDateTime(2007, 11, 18),
+                    ),
+                )
+            ),
+            (
+                "Recurring with override utc",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
+DURATION:PT1H
+RRULE:FREQ=DAILY;COUNT=2
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T000000Z
+DTSTART:20071115T010000Z
+DTSTAMP:20080601T120000Z
+DURATION:PT1H
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+                (
+                    (
+                        PyCalendarDateTime(2007, 11, 14, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 14, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 15, 1, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                        PyCalendarDateTime(2007, 11, 15, 2, 0, 0, tzid=PyCalendarTimezone(utc=True)),
+                    ),
+                )
+            ),
+            (
+                "Recurring with override all-day",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;VALUE=DATE:20071115
+DTSTAMP:20080601T120000Z
+DURATION:P1D
+RRULE:FREQ=DAILY;COUNT=2
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID;VALUE=DATE:20071116
+DTSTART;VALUE=DATE:20071116
+DTSTAMP:20080601T120000Z
+DURATION:P2D
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+                (
+                    (
+                        PyCalendarDateTime(2007, 11, 15),
+                        PyCalendarDateTime(2007, 11, 16),
+                    ),
+                    (
+                        PyCalendarDateTime(2007, 11, 16),
+                        PyCalendarDateTime(2007, 11, 18),
+                    ),
+                )
+            ),
+            (
+                "Recurring with invalid override utc",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY;COUNT=2
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T010000Z
+DTSTART:20071115T000000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+                None
+            ),
+            (
+                "Recurring with invalid override all-day",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;VALUE=DATE:20071115
+DURATION:P1D
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY;COUNT=2
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID;VALUE=DATE:20071117
+DTSTART;VALUE=DATE:20071117
+DURATION:P2D
+DTSTAMP:20080601T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+                None
+            ),
+        )
+        
+        for description, original, ignoreInvalidInstances, results in data:
+            component = Component.fromString(original)
+            if results is None:
+                self.assertRaises(InvalidOverriddenInstanceError, component.expandTimeRanges, PyCalendarDateTime(2100, 1, 1), ignoreInvalidInstances)
+            else:
+                instances = component.expandTimeRanges(PyCalendarDateTime(2100, 1, 1), ignoreInvalidInstances, normalizeFunction=normalizeForExpand)
+                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,))
+       
     def test_has_property_in_any_component(self):
         
         data = (
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120514/443d1c79/attachment-0001.html>


More information about the calendarserver-changes mailing list