]> err.no Git - sope/commitdiff
first sketch of recurrence rules and associated functionality
authorznek <znek@e4a50df8-12e2-0310-a44c-efbce7f8a7e3>
Thu, 10 Feb 2005 23:14:41 +0000 (23:14 +0000)
committerznek <znek@e4a50df8-12e2-0310-a44c-efbce7f8a7e3>
Thu, 10 Feb 2005 23:14:41 +0000 (23:14 +0000)
git-svn-id: http://svn.opengroupware.org/SOPE/trunk@549 e4a50df8-12e2-0310-a44c-efbce7f8a7e3

21 files changed:
sope-ical/NGiCal/ChangeLog
sope-ical/NGiCal/GNUmakefile
sope-ical/NGiCal/NGiCal.h
sope-ical/NGiCal/NGiCal.xcode/project.pbxproj
sope-ical/NGiCal/NGiCal.xmap
sope-ical/NGiCal/README
sope-ical/NGiCal/Version
sope-ical/NGiCal/iCalEvent.h
sope-ical/NGiCal/iCalEvent.m
sope-ical/NGiCal/iCalRecurrenceCalculator.h [new file with mode: 0644]
sope-ical/NGiCal/iCalRecurrenceCalculator.m [new file with mode: 0644]
sope-ical/NGiCal/iCalRecurrenceRule.h [new file with mode: 0644]
sope-ical/NGiCal/iCalRecurrenceRule.m [new file with mode: 0644]
sope-ical/NGiCal/iCalRepeatableEntityObject.h [new file with mode: 0644]
sope-ical/NGiCal/iCalRepeatableEntityObject.m [new file with mode: 0644]
sope-ical/NGiCal/iCalToDo.h
sope-ical/NGiCal/iCalToDo.m
sope-ical/NGiCal/tests/NGiCalTests-Info.plist [new file with mode: 0644]
sope-ical/NGiCal/tests/README [new file with mode: 0644]
sope-ical/NGiCal/tests/common.h [new file with mode: 0644]
sope-ical/NGiCal/tests/iCalRecurrenceCalculatorTests.m [new file with mode: 0644]

index 40dfc469aba95df260e893694c80d21562d0ffb2..f79d637bb4c022d14b3ba18afc8a9b0b2abcfa30 100644 (file)
@@ -1,3 +1,31 @@
+2005-02-11  Marcus Mueller  <znek@mulle-kybernetik.com>
+
+       * v4.5.39
+
+       * README: updated
+
+       * iCalRepeatableEntityObject.[hm]: new base class for all other
+         repeatable entity objects. Offers a convenience API for generating
+         recurrence ranges and tests, taking all exceptions into account.
+
+       * iCalRecurrenceRule.[hm]: an iCal recurrence rule, modeled as closely
+         as possible to RFC2445. Please note that this is work in progress
+         and far from being complete, yet.
+
+       * iCalRecurrenceCalculator.[hm]: a controller implementing RFC2445
+         to properly generate recurrence ranges and accompanied functionality.
+
+       * iCalEvent.[hm], iCalToDo.[hm]: now subclasses from
+         iCalRepeatableEntityObject, thus removed code dealing with
+         recurrences
+
+       * NGiCal.h: added new headers
+
+       * NGiCal.xmap: changed recurrenceRule mappings due to model change
+
+       * tests/*: contains unit tests for stuff dealing with recurrences. See
+         accompanied README for details
+
 2004-12-17  Marcus Mueller  <znek@mulle-kybernetik.com>
 
        * iCalPerson.[hm]: formalized participationStatus according to RFC2445.
index cbcb84dc6e328378ad89dbeea3c1b28b51f89afb..68633a8f8943ea3bcdc9a21f8fc1e4c5d692a715 100644 (file)
@@ -11,47 +11,53 @@ libNGiCal_HEADER_FILES_INSTALL_DIR = /NGiCal
 libNGiCal_SOVERSION=$(MAJOR_VERSION).$(MINOR_VERSION)
 libNGiCal_VERSION=$(MAJOR_VERSION).$(MINOR_VERSION).$(SUBMINOR_VERSION)
 
-libNGiCal_HEADER_FILES =       \
-       NGiCal.h                \
-       iCalAttachment.h        \
-       iCalObject.h            \
-       iCalEntityObject.h      \
-       iCalCalendar.h          \
-       iCalToDo.h              \
-       iCalJournal.h           \
-       iCalEvent.h             \
-       iCalFreeBusy.h          \
-       iCalPerson.h            \
-       iCalAlarm.h             \
-       iCalDuration.h          \
-       iCalTrigger.h           \
-       iCalDataSource.h        \
-       iCalEventChanges.h      \
-       iCalRenderer.h          \
-
-#      IcalResponse.h          \
-
-libNGiCal_OBJC_FILES =         \
-       NSCalendarDate+ICal.m   \
-       iCalDateHolder.m        \
+libNGiCal_HEADER_FILES =               \
+       NGiCal.h                        \
+       iCalAttachment.h                \
+       iCalObject.h                    \
+       iCalEntityObject.h              \
+       iCalRepeatableEntityObject.h    \
+       iCalCalendar.h                  \
+       iCalToDo.h                      \
+       iCalJournal.h                   \
+       iCalEvent.h                     \
+       iCalFreeBusy.h                  \
+       iCalPerson.h                    \
+       iCalAlarm.h                     \
+       iCalDuration.h                  \
+       iCalTrigger.h                   \
+       iCalDataSource.h                \
+       iCalEventChanges.h              \
+       iCalRenderer.h                  \
+       iCalRecurrenceRule.h            \
+       iCalRecurrenceCalculator.h      \
+
+#      IcalResponse.h                  \
+
+libNGiCal_OBJC_FILES =                 \
+       NSCalendarDate+ICal.m           \
+       iCalDateHolder.m                \
        \
-       iCalAttachment.m        \
-       iCalObject.m            \
-       iCalEntityObject.m      \
-       iCalCalendar.m          \
-       iCalToDo.m              \
-       iCalJournal.m           \
-       iCalEvent.m             \
-       iCalFreeBusy.m          \
-       iCalPerson.m            \
-       iCalAlarm.m             \
-       iCalDuration.m          \
-       iCalTrigger.m           \
-       iCalDataSource.m        \
-       iCalEventChanges.m      \
-       iCalRenderer.m          \
-
-#      NSString+ICal.m         \
+       iCalAttachment.m                \
+       iCalObject.m                    \
+       iCalEntityObject.m              \
+       iCalRepeatableEntityObject.m    \
+       iCalCalendar.m                  \
+       iCalToDo.m                      \
+       iCalJournal.m                   \
+       iCalEvent.m                     \
+       iCalFreeBusy.m                  \
+       iCalPerson.m                    \
+       iCalAlarm.m                     \
+       iCalDuration.m                  \
+       iCalTrigger.m                   \
+       iCalDataSource.m                \
+       iCalEventChanges.m              \
+       iCalRenderer.m                  \
+       iCalRecurrenceRule.m            \
+       iCalRecurrenceCalculator.m      \
+
+#      NSString+ICal.m                 \
 #      IcalElements.m
 #      IcalResponse.m
 
index 35b383bec4c9d4b24ad27fe459f8fbbd9ba6006e..55728e65da61ea225ee118ff3613164c1abe6521 100644 (file)
@@ -24,6 +24,8 @@
 
 #include <NGiCal/iCalAttachment.h>
 #include <NGiCal/iCalObject.h>
+#include <NGiCal/iCalEntityObject.h>
+#include <NGiCal/iCalRepeatableEntityObject.h>
 #include <NGiCal/iCalCalendar.h>
 #include <NGiCal/iCalToDo.h>
 #include <NGiCal/iCalJournal.h>
@@ -36,4 +38,7 @@
 
 #include <NGiCal/iCalEventChanges.h>
 
+#include <NGiCal/iCalRecurrenceRule.h>
+#include <NGiCal/iCalRecurrenceCalculator.h>
+
 #endif /* __NGiCal_H__ */
index 295b30d3211b68e31470287bb437bc41cc0147e8..ec2b5a23b339b79fb04b0599f65e3dc0ff57c92f 100644 (file)
@@ -5,6 +5,64 @@
        };
        objectVersion = 39;
        objects = {
+               AD770E6707AE627500F5C7A1 = {
+                       fileEncoding = 5;
+                       indentWidth = 2;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.c.h;
+                       path = iCalRecurrenceRule.h;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               AD770E6807AE627500F5C7A1 = {
+                       fileEncoding = 12;
+                       indentWidth = 2;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.c.objc;
+                       path = iCalRecurrenceRule.m;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               AD770E6907AE627500F5C7A1 = {
+                       fileRef = AD770E6707AE627500F5C7A1;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               AD770E6A07AE627500F5C7A1 = {
+                       fileRef = AD770E6807AE627500F5C7A1;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               AD77103C07AE8F8500F5C7A1 = {
+                       fileEncoding = 4;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.c.h;
+                       path = iCalRepeatableEntityObject.h;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               AD77103D07AE8F8500F5C7A1 = {
+                       fileEncoding = 4;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.c.objc;
+                       path = iCalRepeatableEntityObject.m;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               AD77103E07AE8F8500F5C7A1 = {
+                       fileRef = AD77103C07AE8F8500F5C7A1;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               AD77103F07AE8F8500F5C7A1 = {
+                       fileRef = AD77103D07AE8F8500F5C7A1;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
                AD8BF0F70701902800EC239A = {
                        isa = PBXFileReference;
                        lastKnownFileType = wrapper.framework;
                        settings = {
                        };
                };
+               ADAACE6607B3973900FC48D6 = {
+                       fileEncoding = 4;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.c.h;
+                       path = iCalRecurrenceCalculator.h;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               ADAACE6707B3973900FC48D6 = {
+                       fileEncoding = 4;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.c.objc;
+                       path = iCalRecurrenceCalculator.m;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               ADAACE6807B3973900FC48D6 = {
+                       fileRef = ADAACE6607B3973900FC48D6;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               ADAACE6907B3973900FC48D6 = {
+                       fileRef = ADAACE6707B3973900FC48D6;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               ADAACFE507B6AEB500FC48D6 = {
+                       buildActionMask = 2147483647;
+                       files = (
+                               ADAAD04207B6B05000FC48D6,
+                       );
+                       isa = PBXHeadersBuildPhase;
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               ADAACFE607B6AEB500FC48D6 = {
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       isa = PBXResourcesBuildPhase;
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               ADAACFE707B6AEB500FC48D6 = {
+                       buildActionMask = 2147483647;
+                       files = (
+                               ADAAD02307B6AFD700FC48D6,
+                       );
+                       isa = PBXSourcesBuildPhase;
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               ADAACFE807B6AEB500FC48D6 = {
+                       buildActionMask = 2147483647;
+                       files = (
+                               ADAAD0A307B6B74F00FC48D6,
+                               ADAAD11107B6B75300FC48D6,
+                               ADAAD00707B6AF7700FC48D6,
+                               ADAAD11207B6B7A100FC48D6,
+                       );
+                       isa = PBXFrameworksBuildPhase;
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               ADAACFE907B6AEB500FC48D6 = {
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       inputPaths = (
+                       );
+                       isa = PBXShellScriptBuildPhase;
+                       outputPaths = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+                       shellPath = /bin/sh;
+                       shellScript = /Developer/Tools/RunTargetUnitTests;
+               };
+               ADAACFEA07B6AEB500FC48D6 = {
+                       buildPhases = (
+                               ADAACFE507B6AEB500FC48D6,
+                               ADAACFE607B6AEB500FC48D6,
+                               ADAACFE707B6AEB500FC48D6,
+                               ADAACFE807B6AEB500FC48D6,
+                               ADAACFE907B6AEB500FC48D6,
+                       );
+                       buildRules = (
+                       );
+                       buildSettings = {
+                               DYLIB_COMPATIBILITY_VERSION = 1;
+                               DYLIB_CURRENT_VERSION = 4.5.39;
+                               FRAMEWORK_SEARCH_PATHS = "\"$(USER_LIBRARY_DIR)/EmbeddedFrameworks\"";
+                               FRAMEWORK_VERSION = A;
+                               GCC_PRECOMPILE_PREFIX_HEADER = NO;
+                               GCC_WARN_FOUR_CHARACTER_CONSTANTS = NO;
+                               GCC_WARN_UNKNOWN_PRAGMAS = NO;
+                               INFOPLIST_FILE = "tests/NGiCalTests-Info.plist";
+                               INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+                               OTHER_CFLAGS = "-fobjc-exceptions";
+                               OTHER_LDFLAGS = "";
+                               OTHER_REZFLAGS = "";
+                               PRODUCT_NAME = NGiCalTests;
+                               SECTORDER_FLAGS = "";
+                               TEST_AFTER_BUILD = YES;
+                               WARNING_CFLAGS = "-Wmost";
+                       };
+                       dependencies = (
+                               ADAACFEE07B6AEBF00FC48D6,
+                       );
+                       isa = PBXNativeTarget;
+                       name = NGiCalTests;
+                       productName = NGiCalTests;
+                       productReference = ADAACFEB07B6AEB500FC48D6;
+                       productSettingsXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
+<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
+<plist version=\"1.0\">
+<dict>
+       <key>CFBundleDevelopmentRegion</key>
+       <string>English</string>
+       <key>CFBundleExecutable</key>
+       <string>NGiCalTests</string>
+       <key>CFBundleGetInfoString</key>
+       <string></string>
+       <key>CFBundleIdentifier</key>
+       <string>com.MySoftwareCompany.NGiCalTests</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundlePackageType</key>
+       <string>FMWK</string>
+       <key>CFBundleShortVersionString</key>
+       <string></string>
+       <key>CFBundleSignature</key>
+       <string>????</string>
+       <key>CFBundleVersion</key>
+       <string>1.0.0d1</string>
+</dict>
+</plist>
+";
+                       productType = "com.apple.product-type.framework";
+               };
+               ADAACFEB07B6AEB500FC48D6 = {
+                       explicitFileType = wrapper.framework;
+                       includeInIndex = 0;
+                       isa = PBXFileReference;
+                       path = NGiCalTests.framework;
+                       refType = 3;
+                       sourceTree = BUILT_PRODUCTS_DIR;
+               };
+               ADAACFEC07B6AEB500FC48D6 = {
+                       indentWidth = 2;
+                       isa = PBXFileReference;
+                       lastKnownFileType = text.xml;
+                       path = "NGiCalTests-Info.plist";
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               ADAACFED07B6AEBF00FC48D6 = {
+                       containerPortal = ADDF4E5506DE4FC800C4E7F8;
+                       isa = PBXContainerItemProxy;
+                       proxyType = 1;
+                       remoteGlobalIDString = ADDF4E5F06DE4FF200C4E7F8;
+                       remoteInfo = NGiCal;
+               };
+               ADAACFEE07B6AEBF00FC48D6 = {
+                       isa = PBXTargetDependency;
+                       target = ADDF4E5F06DE4FF200C4E7F8;
+                       targetProxy = ADAACFED07B6AEBF00FC48D6;
+               };
+               ADAACFFF07B6AF0E00FC48D6 = {
+                       children = (
+                               ADAAD04007B6B02400FC48D6,
+                               ADAAD01F07B6AFA600FC48D6,
+                               ADAAD05C07B6B0CB00FC48D6,
+                       );
+                       fileEncoding = 5;
+                       indentWidth = 2;
+                       isa = PBXGroup;
+                       name = "Unit Tests";
+                       path = tests;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               ADAAD00607B6AF7700FC48D6 = {
+                       isa = PBXFileReference;
+                       lastKnownFileType = wrapper.framework;
+                       name = SenTestingKit.framework;
+                       path = /Library/Frameworks/SenTestingKit.framework;
+                       refType = 0;
+                       sourceTree = "<absolute>";
+               };
+               ADAAD00707B6AF7700FC48D6 = {
+                       fileRef = ADAAD00607B6AF7700FC48D6;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               ADAAD01F07B6AFA600FC48D6 = {
+                       children = (
+                               ADAAD04107B6B05000FC48D6,
+                               ADAAD02107B6AFD700FC48D6,
+                       );
+                       isa = PBXGroup;
+                       name = Classes;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               ADAAD02107B6AFD700FC48D6 = {
+                       fileEncoding = 4;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.c.objc;
+                       path = iCalRecurrenceCalculatorTests.m;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               ADAAD02307B6AFD700FC48D6 = {
+                       fileRef = ADAAD02107B6AFD700FC48D6;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               ADAAD04007B6B02400FC48D6 = {
+                       fileEncoding = 4;
+                       isa = PBXFileReference;
+                       lastKnownFileType = text;
+                       path = README;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               ADAAD04107B6B05000FC48D6 = {
+                       fileEncoding = 4;
+                       isa = PBXFileReference;
+                       lastKnownFileType = sourcecode.c.h;
+                       path = common.h;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               ADAAD04207B6B05000FC48D6 = {
+                       fileRef = ADAAD04107B6B05000FC48D6;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               ADAAD05C07B6B0CB00FC48D6 = {
+                       children = (
+                               ADAACFEC07B6AEB500FC48D6,
+                       );
+                       isa = PBXGroup;
+                       name = Resources;
+                       refType = 4;
+                       sourceTree = "<group>";
+               };
+               ADAAD0A307B6B74F00FC48D6 = {
+                       fileRef = ADDF503B06DE528200C4E7F8;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               ADAAD11107B6B75300FC48D6 = {
+                       fileRef = ADDF503F06DE52F600C4E7F8;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
+               ADAAD11207B6B7A100FC48D6 = {
+                       fileRef = ADDF4E6006DE4FF200C4E7F8;
+                       isa = PBXBuildFile;
+                       settings = {
+                       };
+               };
                ADBE3DF7072713AF000FEA6A = {
                        fileEncoding = 5;
                        indentWidth = 2;
                                ADDF4F7306DE516A00C4E7F8,
                                ADDF4FD406DE517900C4E7F8,
                                ADDF503706DE520000C4E7F8,
+                               ADAACFFF07B6AF0E00FC48D6,
                                ADDF4E6506DE500200C4E7F8,
                                ADDF4E6106DE4FF200C4E7F8,
                                ADDF503A06DE524200C4E7F8,
                        projectDirPath = "";
                        targets = (
                                ADDF4E5F06DE4FF200C4E7F8,
+                               ADAACFEA07B6AEB500FC48D6,
                        );
                };
                ADDF4E5B06DE4FF200C4E7F8 = {
                                ADDF4F6806DE513D00C4E7F8,
                                ADD1FC9B06E4D6D400E387F0,
                                ADBE3DF8072713AF000FEA6A,
+                               AD770E6907AE627500F5C7A1,
+                               AD77103E07AE8F8500F5C7A1,
+                               ADAACE6807B3973900FC48D6,
                        );
                        isa = PBXHeadersBuildPhase;
                        runOnlyForDeploymentPostprocessing = 0;
                                ADDF4F6906DE513D00C4E7F8,
                                ADD1FC9C06E4D6D400E387F0,
                                ADBE3DFA072713C2000FEA6A,
+                               AD770E6A07AE627500F5C7A1,
+                               AD77103F07AE8F8500F5C7A1,
+                               ADAACE6907B3973900FC48D6,
                        );
                        isa = PBXSourcesBuildPhase;
                        runOnlyForDeploymentPostprocessing = 0;
                        );
                        buildSettings = {
                                DYLIB_COMPATIBILITY_VERSION = 1;
-                               DYLIB_CURRENT_VERSION = 4.5.38;
+                               DYLIB_CURRENT_VERSION = 4.5.39;
                                FRAMEWORK_SEARCH_PATHS = "$(LOCAL_LIBRARY_DIR)/Frameworks";
                                FRAMEWORK_VERSION = A;
                                GCC_PRECOMPILE_PREFIX_HEADER = YES;
                ADDF4E6106DE4FF200C4E7F8 = {
                        children = (
                                ADDF4E6006DE4FF200C4E7F8,
+                               ADAACFEB07B6AEB500FC48D6,
                        );
                        isa = PBXGroup;
                        name = Products;
                };
                ADDF4F3906DE513D00C4E7F8 = {
                        fileEncoding = 4;
+                       indentWidth = 2;
                        isa = PBXFileReference;
                        lastKnownFileType = text.xml;
                        path = NGiCal.xmap;
                                ADDF4F2106DE513D00C4E7F8,
                                ADDF4F2306DE513D00C4E7F8,
                                ADDF4F2606DE513D00C4E7F8,
+                               AD77103C07AE8F8500F5C7A1,
                                ADDF4F2806DE513D00C4E7F8,
                                ADDF4F2A06DE513D00C4E7F8,
                                ADDF4F2C06DE513D00C4E7F8,
                                ADDF4F3606DE513D00C4E7F8,
                                ADD1FC9906E4D6D400E387F0,
                                ADBE3DF7072713AF000FEA6A,
+                               AD770E6707AE627500F5C7A1,
+                               ADAACE6607B3973900FC48D6,
                                ADDF4F3A06DE513D00C4E7F8,
                        );
+                       fileEncoding = 5;
+                       indentWidth = 2;
                        isa = PBXGroup;
                        name = Headers;
                        refType = 4;
                                ADDF4F2206DE513D00C4E7F8,
                                ADDF4F2406DE513D00C4E7F8,
                                ADDF4F2706DE513D00C4E7F8,
+                               AD77103D07AE8F8500F5C7A1,
                                ADDF4F2906DE513D00C4E7F8,
                                ADDF4F2B06DE513D00C4E7F8,
                                ADDF4F2D06DE513D00C4E7F8,
                                ADDF4F3706DE513D00C4E7F8,
                                ADD1FC9A06E4D6D400E387F0,
                                ADBE3DF9072713C2000FEA6A,
+                               AD770E6807AE627500F5C7A1,
+                               ADAACE6707B3973900FC48D6,
                                ADDF4F3B06DE513D00C4E7F8,
                        );
+                       fileEncoding = 5;
+                       indentWidth = 2;
                        isa = PBXGroup;
                        name = Classes;
                        refType = 4;
                                ADDF503B06DE528200C4E7F8,
                                ADDF503E06DE52F600C4E7F8,
                                ADDF503F06DE52F600C4E7F8,
+                               ADAAD00607B6AF7700FC48D6,
                                ADDF510206DE54BE00C4E7F8,
                        );
                        isa = PBXGroup;
index 61521b5fa45c44b5fc4fbe97236aaaa027db1ddc..6ddf3f1a971835cdfd3b278256f4f40c23869aa4 100644 (file)
@@ -8,7 +8,7 @@
       tagKey = "tag";
       
       ToManyRelationships = {
-       "subcomponents" = ( vcalendar );
+        "subcomponents" = ( vcalendar );
       };
     };
     
       };
       
       ToManyRelationships = {
-       events    = ( vevent    );
-       todos     = ( vtodo     );
-       journals  = ( journals  );
-       freeBusys = ( vfreebusy );
-       timezones = ( vtimezone );
+        events    = ( vevent    );
+        todos     = ( vtodo     );
+        journals  = ( journals  );
+        freeBusys = ( vfreebusy );
+        timezones = ( vtimezone );
       };
     };
     
       class  = iCalEvent;
 
       attributes = {
-       rrule       = recurrenceRule;
       };
 
       ToManyRelationships = {
-       "alarms"    = ( valarm   );
-       "attendees" = ( attendee );
+        "alarms"          = ( valarm   );
+        "attendees"       = ( attendee );
+        "recurrenceRules" = ( rrule    );
+        "exceptionRules"  = ( exrule   );
+        "exceptionDates"  = ( exdate   );
       };
     };
     
       class = "iCalToDo";
 
       attributes = {
-       rrule       = recurrenceRule;
       };
 
       ToManyRelationships = {
-       "alarms"    = ( valarm   );
-       "attendees" = ( attendee );
+        "alarms"    = ( valarm   );
+        "attendees" = ( attendee );
       };
     };
     
@@ -60,7 +61,7 @@
       class = "iCalAlarm";
 
       attributes = {
-       rrule        = recurrenceRule;
+        rrule = recurrenceRule;
       };
     };
     
@@ -68,7 +69,7 @@
       class = "iCalFreeBusy";
       
       ToManyRelationships = {
-       "entries" = ( freebusy );
+        "entries" = ( freebusy );
       };
     };
     
@@ -76,7 +77,7 @@
       class = NSMutableDictionary;
       
       attributes = {
-       tzid             = timeZoneID;
+        tzid             = timeZoneID;
         "X-LIC-LOCATION" = location;
         daylight         = daylightInfo;
         standard         = standardInfo;
       class = NSMutableDictionary;
       
       attributes = {
-       tzoffsetfrom = tzOffsetFrom;
-       tzoffsetto   = tzOffsetTo;
-       tzname       = tzName;
-       dtstart      = startDate;
-       rrule        = recurrenceRule;
+        tzoffsetfrom = tzOffsetFrom;
+        tzoffsetto   = tzOffsetTo;
+        tzname       = tzName;
+        dtstart      = startDate;
+        rrule        = recurrenceRule;
       };
     };
     standard = {
       class = NSMutableDictionary;
       
       attributes = {
-       tzoffsetfrom = tzOffsetFrom;
-       tzoffsetto   = tzOffsetTo;
-       tzname       = tzName;
-       dtstart      = startDate;
-       rrule        = recurrenceRule;
+        tzoffsetfrom = tzOffsetFrom;
+        tzoffsetto   = tzOffsetTo;
+        tzname       = tzName;
+        dtstart      = startDate;
+        rrule        = recurrenceRule;
       };
     };
     
       tagKey     = "tag";
       contentKey = "string";
     };
+    exdate = {
+      class = iCalDateHolder;
+      key   = "recurrenceRuleExceptionDate";
+      attributes = {
+        tzid = tzid;
+      };
+      tagKey     = "tag";
+      contentKey = "string";
+    };
     due = {
       class = iCalDateHolder;
       attributes = {
       class = NSString;
     };
     rrule = {
-      class = NSString;
+      class = iCalRecurrenceRule;
+      /*
+      attributes = {
+        freq       = rrFreq;
+        until      = rrUntil;
+        count      = rrCount;
+        interval   = rrInterval;
+        bysecond   = rrBySecondList;
+        byminute   = rrByMinuteList;
+        byhour     = rrByHourList;
+        byday      = rrByDayList;
+        bymonthday = rrByMonthDayList;
+        byyearday  = rrByYearDayList;
+        byweekno   = rrByWeekNumberList;
+        bymonth    = rrByMonthList;
+        bysetpos   = rrBySetPosList;
+        wkst       = rrWeekStart;
+      };
+      */
+      contentKey = "rrule";
+      key        = "recurrenceRule"
     };
     location = {
       class = NSString;
index df050b779effd1d4cc5ad1b1d031c784df4f78fe..dc6dc64b79bba22e52a11a5a5feebaf683acbcf6 100644 (file)
@@ -1,27 +1,21 @@
 # $Id$
 
-TODO: update text
+TODO: improve text
 
 Objective-C classes for representing iCalendar entities as objects. To
-actually parse iCalendar entities the skyrix-xml iCalSaxDriver is used.
-Note that this library doesn't directly link against libical but rather
-relies on the SAX interface (SaxObjectDecoder is used).
+actually parse iCalendar entities the sope-xml versitSaxDriver is used.
+Note that this library doesn't make any use of the now deprecated libical but
+rather relies on the SAX interface (SaxObjectDecoder is used).
 
-OPEN: should we add the "ical" generation tags to this library or to
-NGObjWeb or to ... ?? => iCal rendering should probably be a separate 
-product-bundle for SOPE
+Recurrences
+===========
 
-Class Hierarchy
+Recurrences are modeled via iCalRecurrenceRules and an iCalRecurrenceCalculator
+which contains all the necessary logic according to RFC2445 to interpret
+iCalRecurrenceRules. The calculator needs a referrence date for the first
+instance of a recurrence which is usually provided by any of the repeatable
+entity objects (i.e. iCalEvent).
 
-  NSObject
-    ICalComponent
-      ICalVCalendar
-      ICalVEvent
-      ICalVFreeBusy
-      ICalXRoot
-    ICalProperty
-      ICalFreeBusy
-    ICalParser
-
-  EODataSource
-    iCalDataSource
+Please note that recurrences are work in progress and far from being
+complete/compliant with RFC2445. So far only the most simple cases are done
+properly.
index 177d90a94b33616d86d66a9c58097e7b4a7302a3..c2c1f65e679caf7fb72d0039366e602e8ef12dd9 100644 (file)
@@ -2,6 +2,6 @@
 
 MAJOR_VERSION=4
 MINOR_VERSION=5
-SUBMINOR_VERSION:=38
+SUBMINOR_VERSION:=39
 
 # v4.5.37 requires NGExtensions v4.5.140
index 4c8303a272b05dcb6b640bf6e826bdb85b4e1d30..2c7489a27d3aff32447ea8226d189b76535fee7a 100644 (file)
@@ -22,7 +22,7 @@
 #ifndef __NGiCal_iCalEvent_H__
 #define __NGiCal_iCalEvent_H__
 
-#include <NGiCal/iCalEntityObject.h>
+#include <NGiCal/iCalRepeatableEntityObject.h>
 #import <Foundation/NSDate.h>
 
 /*
 */
 
 @class NSString, NSMutableArray, NSCalendarDate, NGCalendarDateRange;
-@class iCalPerson, iCalEventChanges;
+@class iCalPerson, iCalEventChanges, iCalRecurrenceRule;
 
-@interface iCalEvent : iCalEntityObject
+@interface iCalEvent : iCalRepeatableEntityObject
 {
-  NSCalendarDate *endDate;
-  NSString       *duration;
-  NSString       *recurrenceRule;
-  NSString       *transparency;
+  NSCalendarDate     *endDate;
+  NSString           *duration;
+  NSString           *transparency;
 }
 
 /* accessors */
@@ -54,9 +53,6 @@
 - (BOOL)hasDuration;
 - (NSTimeInterval)durationAsTimeInterval;
 
-- (void)setRecurrenceRule:(NSString *)_recurrenceRule;
-- (NSString *)recurrenceRule;
-
 - (void)setTransparency:(NSString *)_transparency;
 - (NSString *)transparency;
 
@@ -64,7 +60,6 @@
 
 - (BOOL)isOpaque;
 - (BOOL)isAllDay;
-- (BOOL)isRecurrent;
 
 - (BOOL)isWithinCalendarDateRange:(NGCalendarDateRange *)_range;
 
index 91b4ec8d780c08f61898a9de82c2d4a73813235a..bb60b10b62e1498ebb05bd0a2fe5bd6518c9908f 100644 (file)
@@ -22,6 +22,7 @@
 #include "iCalEvent.h"
 #include "iCalPerson.h"
 #include "iCalEventChanges.h"
+#include "iCalRecurrenceRule.h"
 #include "iCalRenderer.h"
 #include <NGExtensions/NGCalendarDateRange.h>
 #include "common.h"
@@ -33,9 +34,9 @@
 @implementation iCalEvent
 
 - (void)dealloc {
-  [self->duration       release];
-  [self->endDate        release];
-  [self->recurrenceRule release];
+  [self->endDate      release];
+  [self->duration     release];
+  [self->transparency release];
   [super dealloc];
 }
 
   return 0.0;
 }
 
-- (void)setRecurrenceRule:(NSString *)_recurrenceRule {
-  ASSIGNCOPY(self->recurrenceRule, _recurrenceRule);
-}
-- (NSString *)recurrenceRule {
-  return self->recurrenceRule;
-}
-
 - (void)setTransparency:(NSString *)_transparency {
   ASSIGNCOPY(self->transparency, _transparency);
 }
 }
 
 /* TODO: FIX THIS!
-   NOTE: How do we find out if appointment is all day? Is this
-   0:00 - 23:59 GMT??? How do other's handle this?
+   The problem is, that startDate/endDate are inappropriately modelled here.
+   We'd need to have a special iCalDate in order to fix all the mess.
+   For the time being, we chose allday to mean 00:00 - 23:59 in startDate's
+   timezone.
 */
 - (BOOL)isAllDay {
+  NSCalendarDate *ed;
+
   if (![self hasEndDate])
     return NO;
+  
+  ed = [[[self endDate] copy] autorelease];
+  [ed setTimeZone:[self->startDate timeZone]];
+  if (([self->startDate hourOfDay]    ==  0) &&
+      ([self->startDate minuteOfHour] ==  0) &&
+      ([ed hourOfDay]                 == 23) &&
+      ([ed minuteOfHour]              == 59))
+      return YES;
   return NO;
 }
 
-- (BOOL)isRecurrent {
-  if ([self recurrenceRule])
-    return YES;
-  return NO;
-}
-
-/* TODO: FIX THIS FOR RECURRENCY! */
 - (BOOL)isWithinCalendarDateRange:(NGCalendarDateRange *)_range {
   if (![self isRecurrent]) {
     if (self->startDate && self->endDate) {
       return [_range containsDate:self->startDate];
     }
   }
+  else {
+    NGCalendarDateRange *fir;
+
+    fir = [NGCalendarDateRange calendarDateRangeWithStartDate:self->startDate
+                               endDate:self->endDate];
+    
+    return [self isWithinCalendarDateRange:_range
+                 firstInstanceCalendarDateRange:fir];
+  }
   return NO;
 }
 
   if ([self hasAlarms])
     [ms appendFormat:@" alarms=%@", self->alarms];
   
-  if (self->recurrenceRule)
-    [ms appendFormat:@" recurrenceRule=%@", self->recurrenceRule];
-
   [ms appendString:@">"];
   return ms;
 }
diff --git a/sope-ical/NGiCal/iCalRecurrenceCalculator.h b/sope-ical/NGiCal/iCalRecurrenceCalculator.h
new file mode 100644 (file)
index 0000000..49ca6b7
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of SOPE.
+
+  SOPE is free software; you can redistribute it and/or modify it under
+  the terms of the GNU Lesser General Public License as published by the
+  Free Software Foundation; either version 2, or (at your option) any
+  later version.
+
+  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+  License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with SOPE; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef        __NGiCal_iCalRecurrenceCalculator_H_
+#define        __NGiCal_iCalRecurrenceCalculator_H_
+
+#import <Foundation/NSObject.h>
+
+/*
+ iCalRecurrenceCalculator
+ Provides an API for performing common calculations performed in conjunction
+ with iCalRecurrenceRule objects.
+*/
+
+@class iCalRecurrenceRule, NGCalendarDateRange;
+
+@interface iCalRecurrenceCalculator : NSObject
+{
+  NGCalendarDateRange *firstRange;
+  iCalRecurrenceRule  *rrule;
+}
+
++ (id)recurrenceCalculatorForRecurrenceRule:(iCalRecurrenceRule *)_rrule
+  withFirstInstanceCalendarDateRange:(NGCalendarDateRange *)_range;
+
+- (id)initWithRecurrenceRule:(iCalRecurrenceRule *)_rrule
+  firstInstanceCalendarDateRange:(NGCalendarDateRange *)_range;
+
+- (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r;
+- (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range;
+
+@end
+
+#endif /* __NGiCal_iCalRecurrenceCalculator_H_ */
diff --git a/sope-ical/NGiCal/iCalRecurrenceCalculator.m b/sope-ical/NGiCal/iCalRecurrenceCalculator.m
new file mode 100644 (file)
index 0000000..63e7b91
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ Copyright (C) 2004-2005 SKYRIX Software AG
+ This file is part of SOPE.
+ SOPE is free software; you can redistribute it and/or modify it under
+ the terms of the GNU Lesser General Public License as published by the
+ Free Software Foundation; either version 2, or (at your option) any
+ later version.
+ SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ License for more details.
+ You should have received a copy of the GNU Lesser General Public
+ License along with SOPE; see the file COPYING.  If not, write to the
+ Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+ 02111-1307, USA.
+*/
+
+#include "iCalRecurrenceCalculator.h"
+#include <NGExtensions/NGCalendarDateRange.h>
+#include "iCalRecurrenceRule.h"
+#include "common.h"
+
+
+/* class cluster */
+
+@interface iCalDailyRecurrenceCalculator : iCalRecurrenceCalculator
+{
+}
+- (unsigned)factor;
+@end
+
+@interface iCalWeeklyRecurrenceCalculator : iCalDailyRecurrenceCalculator
+{
+}
+@end
+
+@interface iCalMonthlyRecurrenceCalculator : iCalRecurrenceCalculator
+{
+}
+@end
+
+@interface iCalYearlyRecurrenceCalculator : iCalRecurrenceCalculator
+{
+}
+@end
+
+/* superclass */
+
+@implementation iCalRecurrenceCalculator
+
+static Class dailyCalcClass   = Nil;
+static Class weeklyCalcClass  = Nil;
+static Class monthlyCalcClass = Nil;
+static Class yearlyCalcClass  = Nil;
+
++ (void)initialize {
+  static BOOL didInit = NO;
+  
+  if (didInit) return;
+  didInit = YES;
+
+  dailyCalcClass   = [iCalDailyRecurrenceCalculator   class];
+  weeklyCalcClass  = [iCalWeeklyRecurrenceCalculator  class];
+  monthlyCalcClass = [iCalMonthlyRecurrenceCalculator class];
+  yearlyCalcClass  = [iCalYearlyRecurrenceCalculator  class];
+}
+
++ (id)recurrenceCalculatorForRecurrenceRule:(iCalRecurrenceRule *)_rrule
+         withFirstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
+{
+  return [[[self alloc] initWithRecurrenceRule:_rrule
+                        firstInstanceCalendarDateRange:_range] autorelease];
+}
+
+- (id)initWithRecurrenceRule:(iCalRecurrenceRule *)_rrule
+  firstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
+{
+  iCalRecurrenceFrequency freq;
+  Class calcClass = Nil;
+
+  freq = [_rrule frequency];
+  if (freq == iCalRecurrenceFrequenceDaily)
+    calcClass = dailyCalcClass;
+  else if (freq == iCalRecurrenceFrequenceWeekly)
+    calcClass = weeklyCalcClass;
+  else if (freq == iCalRecurrenceFrequenceMonthly)
+    calcClass = monthlyCalcClass;
+  else if (freq == iCalRecurrenceFrequenceYearly)
+    calcClass = yearlyCalcClass;
+
+  [self autorelease];
+  if (calcClass == Nil)
+    return nil;
+
+  self = [[calcClass alloc] init];
+  ASSIGN(self->rrule, _rrule);
+  ASSIGN(self->firstRange, _range);
+  return self;  
+}
+
+
+/* calculation */
+
+- (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
+  return nil; /* subclass responsibility */
+}
+- (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
+  return NO; /* subclass responsibility */
+}
+
+@end /* iCalRecurrenceCalculator */
+
+
+/* ZNeK: NOTE
+taeglich:
+alle events
+fuer all diese jeweils
+differenz der julian numbers durch interval teilen, wenn ok, ...
+*/
+@implementation iCalDailyRecurrenceCalculator
+
+- (unsigned)factor {
+  return 1;
+}
+
+- (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
+  long     i, jnFirst, jnStart, jnEnd, startEndCount;
+  unsigned interval;
+
+  jnFirst  = [[self->firstRange startDate] julianNumber];
+  jnEnd    = [[_range endDate] julianNumber];
+
+  if (jnFirst > jnEnd)
+    return NO;
+
+  jnStart  = [[_range startDate] julianNumber];
+  interval = [self->rrule repeatInterval];
+
+  /* if rule is bound, check the bounds */
+  if (![self->rrule isInfinite]) {
+    NSCalendarDate *until;
+    long           jnRuleLast;
+
+    until = [self->rrule untilDate];
+    if (until) {
+      if ([until compare:[_range startDate]] == NSOrderedAscending)
+        return NO;
+      jnRuleLast = [until julianNumber];
+    }
+    else {
+      jnRuleLast = (interval * [self->rrule repeatCount] * [self factor])
+                   + jnFirst;
+      if (jnRuleLast < jnStart)
+        return NO;
+    }
+    /* jnStart < jnRuleLast < jnEnd ? */
+    if (jnEnd > jnRuleLast)
+      jnEnd = jnRuleLast;
+  }
+
+  startEndCount = (jnEnd - jnStart) + 1;
+  for (i = 0 ; i < startEndCount; i++) {
+    long jnTest;
+
+    jnTest = (jnStart + i) - jnFirst;
+    if ((jnTest % interval) == 0)
+      return YES;
+  }
+  return NO;
+}
+
+@end /* iCalDailyRecurrenceCalculator */
+
+/*
+  ZNeK: NOTE
+  woechentlich:
+  wie taeglich, aber teilen durch interval*7
+*/
+@implementation iCalWeeklyRecurrenceCalculator
+
+- (unsigned)factor {
+  return 7;
+}
+
+@end /* iCalWeeklyRecurrenceCalculator */
+
+/*
+  ZNeK: NOTE
+  monatlich:
+  monatlich: differenz der monate / interval
+  alle events mit korrektem tag.
+  dann differenz der monate durch interval teilen, wenn ok, ...
+  (diffrenz der monate = 12 * 'volle' jahre plus monate im start jahr und monate im ziel jahr
+ */
+@implementation iCalMonthlyRecurrenceCalculator
+
+- (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
+  return YES;
+}
+
+@end /* iCalMonthlyRecurrenceCalculator */
+
+/*
+  ZNeK: NOTE
+  jaehrlich:
+  alle events mit dem korrekten tag und monat.
+  fuer all diese jeweils
+  differenz in jahren durch interval teilen, wenn ok, dann event fuer den tag.
+*/
+@implementation iCalYearlyRecurrenceCalculator
+
+- (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
+  return YES;
+}
+
+@end /* iCalYearlyRecurrenceCalculator */
diff --git a/sope-ical/NGiCal/iCalRecurrenceRule.h b/sope-ical/NGiCal/iCalRecurrenceRule.h
new file mode 100644 (file)
index 0000000..eb1a5bf
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of SOPE.
+
+  SOPE is free software; you can redistribute it and/or modify it under
+  the terms of the GNU Lesser General Public License as published by the
+  Free Software Foundation; either version 2, or (at your option) any
+  later version.
+
+  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+  License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with SOPE; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef        __NGiCal_iCalRecurrenceRule_H_
+#define        __NGiCal_iCalRecurrenceRule_H_
+
+#import <Foundation/NSObject.h>
+
+/*
+ iCalRecurrenceRule
+ Encapsulates a (probably complex) recurrence rule by offering
+ a high level API.
+ NOTE: as of now, only a very limited subset of RFC2445 is implemented.
+ Please see the unit tests for what is covered.
+*/
+
+typedef enum {
+  iCalRecurrenceFrequenceSecondly = 1,
+  iCalRecurrenceFrequenceMinutely = 2,
+  iCalRecurrenceFrequenceHourly   = 3,
+  iCalRecurrenceFrequenceDaily    = 4,
+  iCalRecurrenceFrequenceWeekly   = 5,
+  iCalRecurrenceFrequenceMonthly  = 6,
+  iCalRecurrenceFrequenceYearly   = 7,
+} iCalRecurrenceFrequency;
+
+typedef enum {
+  iCalWeekDayMonday    = 1,
+  iCalWeekDayTuesday   = 2,
+  iCalWeekDayWednesday = 4,
+  iCalWeekDayThursday  = 8,
+  iCalWeekDayFriday    = 16,
+  iCalWeekDaySaturday  = 32,
+  iCalWeekDaySunday    = 64,
+} iCalWeekDay;
+
+@class NSString, NSCalendarDate, NGCalendarDateRange;
+
+@interface iCalRecurrenceRule : NSObject
+{
+  iCalRecurrenceFrequency frequency;
+  int            interval;
+  unsigned       repeatCount;
+  NSCalendarDate *untilDate;
+  struct {
+    unsigned weekStart: 7;
+    unsigned mask:      7;
+  } byDay;
+
+  NSString       *rrule;
+}
+
++ (id)recurrenceRuleWithICalRepresentation:(NSString *)_iCalRep;
+
+- (void)setFrequency:(iCalRecurrenceFrequency)_frequency;
+- (iCalRecurrenceFrequency)frequency;
+- (void)setRepeatInterval:(int)_repeatInterval;
+- (int)repeatInterval;
+
+/* count and untilDate are mutually exclusive */
+- (void)setRepeatCount:(unsigned)_repeatCount;
+- (unsigned)repeatCount;
+- (void)setUntilDate:(NSCalendarDate *)_untilDate;
+- (NSCalendarDate *)untilDate;
+
+- (BOOL)isInfinite;
+
+/* parse complete iCal RRULE */
+- (void)setRrule:(NSString *)_rrule;
+
+- (NSString *)iCalRepresentation;
+
+@end
+
+#endif /* __NGiCal_iCalRecurrenceRule_H_ */
diff --git a/sope-ical/NGiCal/iCalRecurrenceRule.m b/sope-ical/NGiCal/iCalRecurrenceRule.m
new file mode 100644 (file)
index 0000000..9278888
--- /dev/null
@@ -0,0 +1,371 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of SOPE.
+
+  SOPE is free software; you can redistribute it and/or modify it under
+  the terms of the GNU Lesser General Public License as published by the
+  Free Software Foundation; either version 2, or (at your option) any
+  later version.
+
+  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+  License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with SOPE; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "iCalRecurrenceRule.h"
+#include "iCalDateHolder.h"
+#include "NSCalendarDate+ICal.h"
+#include "common.h"
+
+/*
+ freq       = rrFreq;
+ until      = rrUntil;
+ count      = rrCount;
+ interval   = rrInterval;
+ bysecond   = rrBySecondList;
+ byminute   = rrByMinuteList;
+ byhour     = rrByHourList;
+ byday      = rrByDayList;
+ bymonthday = rrByMonthDayList;
+ byyearday  = rrByYearDayList;
+ byweekno   = rrByWeekNumberList;
+ bymonth    = rrByMonthList;
+ bysetpos   = rrBySetPosList;
+ wkst       = rrWeekStart;
+ */
+
+@interface iCalDateHolder (PrivateAPI)
+- (void)setString:(NSString *)_value;
+- (id)awakeAfterUsingSaxDecoder:(id)_decoder;
+@end
+
+@interface iCalRecurrenceRule (PrivateAPI)
+- (iCalWeekDay)weekDayFromICalRepresentation:(NSString *)_day;
+- (NSString *)iCalRepresentationForWeekDay:(iCalWeekDay)_weekDay;
+- (NSString *)freq;
+- (NSString *)wkst;
+- (NSString *)byDayList;
+
+- (void)_processRule;
+- (void)setRrule:(NSString *)_rrule;
+@end
+
+@implementation iCalRecurrenceRule
+
++ (void)initialize {
+  static BOOL didInit = NO;
+  
+  if (didInit) return;
+  didInit = YES;
+}
+
++ (id)recurrenceRuleWithICalRepresentation:(NSString *)_iCalRep {
+  iCalRecurrenceRule *r;
+  
+  r = [[[self alloc] init] autorelease];
+  [r setRrule:_iCalRep];
+  return r;
+}
+
+- (id)init {
+  self = [super init];
+  if (self) {
+    self->byDay.weekStart = iCalWeekDayMonday;
+    self->interval        = 1;
+  }
+  return self;
+}
+
+- (void)dealloc {
+  [self->untilDate release];
+  [self->rrule     release];
+  [super dealloc];
+}
+
+
+/* Accessors */
+
+- (void)setFrequency:(iCalRecurrenceFrequency)_frequency {
+  self->frequency = _frequency;
+}
+- (iCalRecurrenceFrequency)frequency {
+  return self->frequency;
+}
+
+- (void)setRepeatCount:(unsigned)_repeatCount {
+  self->repeatCount = _repeatCount;
+}
+- (unsigned)repeatCount {
+  return self->repeatCount;
+}
+
+- (void)setUntilDate:(NSCalendarDate *)_untilDate {
+  ASSIGN(self->untilDate, _untilDate);
+}
+- (NSCalendarDate *)untilDate {
+  return self->untilDate;
+}
+
+- (void)setRepeatInterval:(int)_repeatInterval {
+  self->interval = _repeatInterval;
+}
+- (int)repeatInterval {
+  return self->interval;
+}
+
+- (void)setWeekStart:(iCalWeekDay)_weekStart {
+  self->byDay.weekStart = _weekStart;
+}
+- (iCalWeekDay)weekStart {
+  return self->byDay.weekStart;
+}
+
+- (BOOL)isInfinite {
+  return (self->repeatCount != 0 || self->untilDate) ? NO : YES;
+}
+
+
+/* Private */
+
+- (iCalWeekDay)weekDayFromICalRepresentation:(NSString *)_day {
+  _day = [_day uppercaseString];
+  if ([_day isEqualToString:@"MO"])
+    return iCalWeekDayMonday;
+  else if ([_day isEqualToString:@"TU"])
+    return iCalWeekDayTuesday;
+  else if ([_day isEqualToString:@"WE"])
+    return iCalWeekDayWednesday;
+  else if ([_day isEqualToString:@"TH"])
+    return iCalWeekDayThursday;
+  else if ([_day isEqualToString:@"FR"])
+    return iCalWeekDayFriday;
+  else if ([_day isEqualToString:@"SA"])
+    return iCalWeekDaySaturday;
+  else if ([_day isEqualToString:@"SU"])
+    return iCalWeekDaySunday;
+  else
+    [NSException raise:NSGenericException
+                 format:@"Incorrect weekDay '%@' specified!", _day];
+  return iCalWeekDayMonday; /* keep compiler happy */
+}
+
+- (NSString *)iCalRepresentationForWeekDay:(iCalWeekDay)_weekDay {
+  switch (self->byDay.weekStart) {
+    case iCalWeekDayMonday:
+      return @"MO";
+    case iCalWeekDayTuesday:
+      return @"TU";
+    case iCalWeekDayWednesday:
+      return @"WE";
+    case iCalWeekDayThursday:
+      return @"TH";
+    case iCalWeekDayFriday:
+      return @"FR";
+    case iCalWeekDaySaturday:
+      return @"SA";
+    case iCalWeekDaySunday:
+      return @"SU";
+    default:
+      return @"MO";
+  }
+}
+
+- (NSString *)freq {
+  switch (self->frequency) {
+    case iCalRecurrenceFrequenceWeekly:
+      return @"WEEKLY";
+    case iCalRecurrenceFrequenceMonthly:
+      return @"MONTHLY";
+    case iCalRecurrenceFrequenceDaily:
+      return @"DAILY";
+    case iCalRecurrenceFrequenceYearly:
+      return @"YEARLY";
+    case iCalRecurrenceFrequenceHourly:
+      return @"HOURLY";
+    case iCalRecurrenceFrequenceMinutely:
+      return @"MINUTELY";
+    case iCalRecurrenceFrequenceSecondly:
+      return @"SECONDLY";
+    default:
+      return @"UNDEFINED?";
+  }
+}
+
+- (NSString *)wkst {
+  return [self iCalRepresentationForWeekDay:self->byDay.weekStart];
+}
+
+/*
+ TODO:
+ Each BYDAY value can also be preceded by a positive (+n) or negative
+ (-n) integer. If present, this indicates the nth occurrence of the
+ specific day within the MONTHLY or YEARLY RRULE. For example, within
+ a MONTHLY rule, +1MO (or simply 1MO) represents the first Monday
+ within the month, whereas -1MO represents the last Monday of the
+ month. If an integer modifier is not present, it means all days of
+ this type within the specified frequency. For example, within a
+ MONTHLY rule, MO represents all Mondays within the month.
+*/
+- (NSString *)byDayList {
+  NSMutableString *s;
+  unsigned        i, day;
+  BOOL            needsComma;
+
+  s          = [NSMutableString stringWithCapacity:20];
+  needsComma = NO;
+  day        = iCalWeekDayMonday;
+
+  for (i = 0; i < 7; i++) {
+    if (self->byDay.mask && day) {
+      if (needsComma)
+        [s appendString:@","];
+      [s appendString:[self iCalRepresentationForWeekDay:day]];
+      needsComma = YES;
+    }
+    day = day << 1;
+  }
+  return s;
+}
+
+/* Rule */
+
+- (void)setRrule:(NSString *)_rrule {
+  ASSIGN(self->rrule, _rrule);
+  [self _processRule];
+}
+
+/* Processing existing rrule */
+
+- (void)_processRule {
+  NSArray  *props;
+  unsigned i, count;
+  
+  props = [self->rrule componentsSeparatedByString:@";"];
+  count = [props count];
+  for (i = 0; i < count; i++) {
+    NSString *prop, *key, *value;
+    NSRange  r;
+    
+    prop = [props objectAtIndex:i];
+    r    = [prop rangeOfString:@"="];
+    if (r.length) {
+      key   = [prop substringToIndex:r.location];
+      value = [prop substringFromIndex:NSMaxRange(r)];
+    }
+    else {
+      key   = prop;
+      value = nil;
+    }
+    [self takeValue:value forKey:[key lowercaseString]];
+  }
+}
+
+
+/* properties */
+
+- (void)setFreq:(NSString *)_freq {
+  _freq = [_freq uppercaseString];
+  if ([_freq isEqualToString:@"WEEKLY"])
+    self->frequency = iCalRecurrenceFrequenceWeekly;
+  else if ([_freq isEqualToString:@"MONTHLY"])
+    self->frequency = iCalRecurrenceFrequenceMonthly;
+  else if ([_freq isEqualToString:@"DAILY"])
+    self->frequency = iCalRecurrenceFrequenceDaily;
+  else if ([_freq isEqualToString:@"YEARLY"])
+    self->frequency = iCalRecurrenceFrequenceYearly;
+  else if ([_freq isEqualToString:@"HOURLY"])
+    self->frequency = iCalRecurrenceFrequenceHourly;
+  else if ([_freq isEqualToString:@"MINUTELY"])
+    self->frequency = iCalRecurrenceFrequenceMinutely;
+  else if ([_freq isEqualToString:@"SECONDLY"])
+    self->frequency = iCalRecurrenceFrequenceSecondly;
+  else
+    [NSException raise:NSGenericException
+                 format:@"Incorrect frequency '%@' specified!", _freq];
+}
+
+- (void)setInterval:(NSString *)_interval {
+  self->interval = [_interval intValue];
+}
+- (void)setCount:(NSString *)_count {
+  self->repeatCount = [_count unsignedIntValue];
+}
+- (void)setUntil:(NSString *)_until {
+  iCalDateHolder *dh;
+  NSCalendarDate *date;
+
+  dh = [[iCalDateHolder alloc] init];
+  [dh setString:_until];
+  date = [dh awakeAfterUsingSaxDecoder:nil];
+  ASSIGN(self->untilDate, date);
+  [dh release];
+}
+
+- (void)setWkst:(NSString *)_weekStart {
+  self->byDay.weekStart = [self weekDayFromICalRepresentation:_weekStart];
+}
+
+- (void)setByday:(NSString *)_byDayList {
+  NSArray  *days;
+  unsigned i, count;
+
+  self->byDay.mask = 0;
+  days  = [_byDayList componentsSeparatedByString:@","];
+  count = [days count];
+  for (i = 0; i < count; i++) {
+    NSString    *iCalDay;
+    iCalWeekDay day;
+    
+    iCalDay = [days objectAtIndex:i];
+    day     = [self weekDayFromICalRepresentation:iCalDay];
+    self->byDay.mask |= day;
+  }
+}
+
+/* key/value coding */
+
+- (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
+  [self warnWithFormat:@"Don't know how to process '%@'!", _key];
+}
+
+
+/* Description */
+
+- (NSString *)iCalRepresentation {
+  NSMutableString *s;
+  
+  s = [NSMutableString stringWithCapacity:80];
+  [s appendString:@"FREQ="];
+  [s appendString:[self freq]];
+  if ([self repeatInterval] != 1) {
+    [s appendFormat:@";INTERVAL=%d", [self repeatInterval]];
+  }
+  if (![self isInfinite]) {
+    if ([self repeatCount] > 0) {
+      [s appendFormat:@";COUNT=%d", [self repeatCount]];
+    }
+    else {
+      [s appendString:@";UNTIL="];
+      [s appendString:[[self untilDate] icalString]];
+    }
+  }
+  if (self->byDay.weekStart != iCalWeekDayMonday) {
+    [s appendString:@";WKST="];
+    [s appendString:[self iCalRepresentationForWeekDay:self->byDay.weekStart]];
+  }
+  if (self->byDay.mask != 0) {
+    [s appendString:@";BYDAY="];
+    [s appendString:[self byDayList]];
+  }
+  return s;
+}
+
+
+@end
diff --git a/sope-ical/NGiCal/iCalRepeatableEntityObject.h b/sope-ical/NGiCal/iCalRepeatableEntityObject.h
new file mode 100644 (file)
index 0000000..33509a0
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of SOPE.
+
+  SOPE is free software; you can redistribute it and/or modify it under
+  the terms of the GNU Lesser General Public License as published by the
+  Free Software Foundation; either version 2, or (at your option) any
+  later version.
+
+  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+  License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with SOPE; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef        __NGiCal_iCalRepeatableEntityObject_H_
+#define        __NGiCal_iCalRepeatableEntityObject_H_
+
+#include <NGiCal/iCalEntityObject.h>
+
+/*
+ iCalRepeatableEntityObject
+
+ Specifies an iCal entity object which can bear a (possibly complex) set
+ of recurrence rules and exceptions thereof. According to RFC 2445 these
+ are VEVENT, VTODO and VJOURNAL.
+*/
+
+@class NSMutableArray, NGCalendarDateRange;
+
+@interface iCalRepeatableEntityObject : iCalEntityObject
+{
+  NSMutableArray *rRules;
+  NSMutableArray *exRules;
+  NSMutableArray *exDates;
+}
+
+- (void)removeAllRecurrenceRules;
+- (void)addToRecurrenceRules:(id)_rrule;
+- (BOOL)hasRecurrenceRules;
+- (NSArray *)recurrenceRules;
+
+- (void)removeAllExceptionRules;
+- (void)addToExceptionRules:(id)_rrule;
+- (BOOL)hasExceptionRules;
+- (NSArray *)exceptionRules;
+
+- (void)removeAllExceptionDates;
+- (void)addToExceptionDates:(id)_date;
+- (NSArray *)exceptionDates;
+
+- (BOOL)isRecurrent;
+- (BOOL)isWithinCalendarDateRange:(NGCalendarDateRange *)_range
+  firstInstanceCalendarDateRange:(NGCalendarDateRange *)_fir;
+
+@end
+
+#endif /* __NGiCal_iCalRepeatableEntityObject_H_ */
diff --git a/sope-ical/NGiCal/iCalRepeatableEntityObject.m b/sope-ical/NGiCal/iCalRepeatableEntityObject.m
new file mode 100644 (file)
index 0000000..3b4345e
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of SOPE.
+
+  SOPE is free software; you can redistribute it and/or modify it under
+  the terms of the GNU Lesser General Public License as published by the
+  Free Software Foundation; either version 2, or (at your option) any
+  later version.
+
+  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+  License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with SOPE; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "iCalRepeatableEntityObject.h"
+#include <NGExtensions/NGCalendarDateRange.h>
+#include "iCalRecurrenceRule.h"
+#include "iCalRecurrenceCalculator.h"
+#include "common.h"
+
+@implementation iCalRepeatableEntityObject
+
+- (void)dealloc {
+  [self->rRules  release];
+  [self->exRules release];
+  [self->exDates release];
+  [super dealloc];
+}
+
+/* Accessors */
+
+- (void)removeAllRecurrenceRules {
+  [self->rRules removeAllObjects];
+}
+- (void)addToRecurrenceRules:(id)_rrule {
+  if (_rrule == nil) return;
+  if (self->rRules == nil)
+    self->rRules = [[NSMutableArray alloc] initWithCapacity:1];
+  [self->rRules addObject:_rrule];
+}
+- (void)setRecurrenceRules:(NSArray *)_rrules {
+  if (_rrules == self->rRules)
+    return;
+  [self->rRules release];
+  self->rRules = [_rrules mutableCopy];
+}
+- (BOOL)hasRecurrenceRules {
+  return [self->rRules count] > 0 ? YES : NO;
+}
+- (NSArray *)recurrenceRules {
+  return self->rRules;
+}
+
+- (void)removeAllExceptionRules {
+  [self->exRules removeAllObjects];
+}
+- (void)addToExceptionRules:(id)_rrule {
+  if (_rrule == nil) return;
+  if (self->exRules == nil)
+    self->exRules = [[NSMutableArray alloc] initWithCapacity:1];
+  [self->exRules addObject:_rrule];
+}
+- (void)setExceptionRules:(NSArray *)_rrules {
+  if (_rrules == self->exRules)
+    return;
+  [self->exRules release];
+  self->exRules = [_rrules mutableCopy];
+}
+- (BOOL)hasExceptionRules {
+  return [self->exRules count] > 0 ? YES : NO;
+}
+- (NSArray *)exceptionRules {
+  return self->exRules;
+}
+
+- (void)removeAllExceptionDates {
+  [self->exDates removeAllObjects];
+}
+- (void)setExceptionDates:(NSArray *)_exDates {
+  if (_exDates == self->exDates)
+    return;
+  [self->exDates release];
+  self->exDates = [_exDates mutableCopy];
+}
+- (void)addToExceptionDates:(id)_date {
+  if (_date == nil) return;
+  if (self->exDates == nil)
+    self->exDates = [[NSMutableArray alloc] initWithCapacity:4];
+  [self->exDates addObject:_date];
+}
+- (NSArray *)exceptionDates {
+  return self->exDates;
+}
+
+/* Convenience */
+
+- (BOOL)isRecurrent {
+  return [self hasRecurrenceRules] ? YES : NO;
+}
+
+/* Matching */
+
+/* ZNeK: NOTE
+   Exception dates give me a headache. I believe the test below is improper,
+   we probably really need to calculate all recurrence ranges and then
+   test whether exception dates are contained in any of the required ranges
+   and remove those which were found.
+*/
+- (BOOL)isWithinCalendarDateRange:(NGCalendarDateRange *)_range
+  firstInstanceCalendarDateRange:(NGCalendarDateRange *)_fir
+{
+  iCalRecurrenceRule       *rule;
+  iCalRecurrenceCalculator *calc;
+  BOOL                     didMatch = NO;
+  unsigned                 i, count;
+
+  count = [self->rRules count];
+  for (i = 0; i < count; i++) {
+    rule = [self->rRules objectAtIndex:i];
+    calc = [iCalRecurrenceCalculator recurrenceCalculatorForRecurrenceRule:rule
+                                     withFirstInstanceCalendarDateRange:_fir];
+    if ([calc doesRecurrWithinCalendarDateRange:_range]) {
+      didMatch = YES;
+      break;
+    }
+  }
+  
+  if (!didMatch)
+    return NO;
+  
+  /* test if any exceptions do match */
+  count = [self->exRules count];
+  for (i = 0; i < count; i++) {
+    
+    rule = [self->exRules objectAtIndex:i];
+    calc = [iCalRecurrenceCalculator recurrenceCalculatorForRecurrenceRule:rule
+                                     withFirstInstanceCalendarDateRange:_fir];
+    if ([calc doesRecurrWithinCalendarDateRange:_range])
+      return NO; /* exception does match, so _range is not a candidate */
+  }
+
+  /* exception dates are also possible */
+  count = [self->exDates count];
+  for (i = 0; i < count; i++) {
+    NSCalendarDate *exDate;
+    
+    exDate = [self->exDates objectAtIndex:i];
+    /* ZNeK: is _this_ correct? */
+    if ([exDate isEqualToDate:[_range startDate]])
+      return NO;
+  }
+  return YES;
+}
+
+@end
index cd04d9dd7641fc9e9a92207de2657553e6a9f30b..936c6263467c9134245450a3f0e01b31c513b84b 100644 (file)
@@ -22,7 +22,7 @@
 #ifndef __NGiCal_iCalToDo_H__
 #define __NGiCal_iCalToDo_H__
 
-#include <NGiCal/iCalEntityObject.h>
+#include <NGiCal/iCalRepeatableEntityObject.h>
 
 /*
   iCalToDo
 
 @class NSString, NSCalendarDate;
 
-@interface iCalToDo : iCalEntityObject
+@interface iCalToDo : iCalRepeatableEntityObject
 {
   NSCalendarDate *due;
   NSString       *percentComplete;
   NSCalendarDate *completed;
-  NSString       *recurrenceRule;
 }
 
 - (void)setPercentComplete:(NSString *)_value;
@@ -49,9 +48,6 @@
 - (void)setCompleted:(NSCalendarDate *)_date;
 - (NSCalendarDate *)completed;
 
-- (void)setRecurrenceRule:(NSString *)_recurrenceRule;
-- (NSString *)recurrenceRule;
-
 @end
 
 #endif /* __NGiCal_iCalToDo_H__ */
index cf7a4b3e6fba11123f9b85cd60fddbb2b0a2fc41..bda4e84f8e49bdb657fca2b87782741373971ce2 100644 (file)
@@ -20,6 +20,7 @@
 */
 
 #include "iCalToDo.h"
+#include "iCalRecurrenceRule.h"
 #include "common.h"
 
 @implementation iCalToDo
@@ -34,7 +35,6 @@
   [self->startDate      release];
   [self->due            release];
   [self->priority       release];
-  [self->recurrenceRule release];
   [super dealloc];
 }
 
   return self->completed;
 }
 
-- (void)setRecurrenceRule:(NSString *)_recurrenceRule {
-  ASSIGN(self->recurrenceRule, _recurrenceRule);
-}
-- (NSString *)recurrenceRule {
-  return self->recurrenceRule;
-}
-
 /* ical typing */
 
 - (NSString *)entityName {
@@ -97,9 +90,6 @@
   if (self->summary)
     [ms appendFormat:@" summary=%@", self->summary];
 
-  if (self->recurrenceRule)
-    [ms appendFormat:@" recurrenceRule=%@", self->recurrenceRule];
-
   [ms appendString:@">"];
   return ms;
 }
diff --git a/sope-ical/NGiCal/tests/NGiCalTests-Info.plist b/sope-ical/NGiCal/tests/NGiCalTests-Info.plist
new file mode 100644 (file)
index 0000000..643cb20
--- /dev/null
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>CFBundleDevelopmentRegion</key>
+       <string>English</string>
+       <key>CFBundleExecutable</key>
+       <string>NGiCalTests</string>
+       <key>CFBundleGetInfoString</key>
+       <string></string>
+       <key>CFBundleIdentifier</key>
+       <string>org.OpenGroupware.SOPE.ical.NGiCalTests</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundlePackageType</key>
+       <string>FMWK</string>
+       <key>CFBundleShortVersionString</key>
+       <string></string>
+       <key>CFBundleSignature</key>
+       <string>????</string>
+       <key>CFBundleVersion</key>
+       <string>4.5</string>
+</dict>
+</plist>
diff --git a/sope-ical/NGiCal/tests/README b/sope-ical/NGiCal/tests/README
new file mode 100644 (file)
index 0000000..cff29bb
--- /dev/null
@@ -0,0 +1,10 @@
+$Id$
+
+This folder contains unit tests for the NGiCal project.
+
+It uses SEN:TE's OCUnit project which can be found at
+http://www.sente.ch/software/ocunit/
+
+I used OCUnitRoot v38, later versions might work as well.
+
+NOTE: This is currently provided in Xcode only.
\ No newline at end of file
diff --git a/sope-ical/NGiCal/tests/common.h b/sope-ical/NGiCal/tests/common.h
new file mode 100644 (file)
index 0000000..ae2f086
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+  Copyright (C) 2000-2005 SKYRIX Software AG
+
+  This file is part of SOPE.
+
+  SOPE is free software; you can redistribute it and/or modify it under
+  the terms of the GNU Lesser General Public License as published by the
+  Free Software Foundation; either version 2, or (at your option) any
+  later version.
+
+  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+  License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with SOPE; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef __NGiCalTests_common_H__
+#define __NGiCalTests_common_H__
+
+#import <Foundation/Foundation.h>
+#import <SenTestingKit/SenTestingKit.h>
+#include <NGExtensions/NGExtensions.h>
+#include <NGExtensions/NGCalendarDateRange.h>
+
+#endif /* __NGiCalTests_common_H__ */
diff --git a/sope-ical/NGiCal/tests/iCalRecurrenceCalculatorTests.m b/sope-ical/NGiCal/tests/iCalRecurrenceCalculatorTests.m
new file mode 100644 (file)
index 0000000..a690503
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+  Copyright (C) 2000-2005 SKYRIX Software AG
+
+  This file is part of SOPE.
+
+  SOPE is free software; you can redistribute it and/or modify it under
+  the terms of the GNU Lesser General Public License as published by the
+  Free Software Foundation; either version 2, or (at your option) any
+  later version.
+
+  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+  License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with SOPE; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "common.h"
+
+@class NGCalendarDateRange;
+@class iCalRecurrenceRule;
+
+@interface iCalRecurrenceCalculatorTests : SenTestCase
+{
+  NSTimeZone          *gmt;
+  NGCalendarDateRange *fir;
+  NGCalendarDateRange *tr1;
+}
+
+- (iCalRecurrenceRule *)ruleWithICalString:(NSString *)_rule;
+
+@end
+
+#include "iCalRecurrenceRule.h"
+#include "iCalRecurrenceCalculator.h"
+
+@implementation iCalRecurrenceCalculatorTests
+
+/* Setup / Teardown */
+
+- (void)setUp {
+  NSCalendarDate *sd, *ed;
+
+  gmt = [[NSTimeZone timeZoneForSecondsFromGMT:0] retain];
+
+  sd = [NSCalendarDate dateWithYear:2005
+                       month:2
+                       day:6
+                       hour:12
+                       minute:0
+                       second:0
+                       timeZone:self->gmt];
+  ed = [NSCalendarDate dateWithYear:2005
+                       month:2
+                       day:6
+                       hour:15
+                       minute:30
+                       second:0
+                       timeZone:self->gmt];
+
+  self->fir = [[NGCalendarDateRange calendarDateRangeWithStartDate:sd
+                                    endDate:ed] retain];
+
+  sd = [NSCalendarDate dateWithYear:2005
+                       month:2
+                       day:11
+                       hour:0
+                       minute:0
+                       second:0
+                       timeZone:self->gmt];
+  ed = [NSCalendarDate dateWithYear:2005
+                       month:2
+                       day:13
+                       hour:23
+                       minute:59
+                       second:59
+                       timeZone:self->gmt];
+  
+  self->tr1 = [[NGCalendarDateRange calendarDateRangeWithStartDate:sd
+                                    endDate:ed] retain];
+  
+}
+
+- (void)tearDown {
+  [self->gmt release];
+  [self->fir release];
+  [self->tr1 release];
+}
+
+/* Private Helper */
+
+- (iCalRecurrenceRule *)ruleWithICalString:(NSString *)_rule {
+  iCalRecurrenceRule *rule;
+  
+  rule = [[[iCalRecurrenceRule alloc] init] autorelease];
+  [rule setRrule:_rule];
+  return rule;
+}
+
+- (void)testUnboundDailyRecurrence {
+  iCalRecurrenceRule       *rule;
+  iCalRecurrenceCalculator *calc;
+  BOOL                     result;
+  
+  /* recurrence occurs within range, 02/14/2005 */
+  rule = [self ruleWithICalString:@"FREQ=DAILY;INTERVAL=2"];
+  calc = [iCalRecurrenceCalculator recurrenceCalculatorForRecurrenceRule:rule
+                                   withFirstInstanceCalendarDateRange:fir];
+  result = [calc doesRecurrWithinCalendarDateRange:self->tr1];
+  STAssertTrue(result, @"missed recurrence!");
+  
+  /* recurrence outside of range */
+  rule = [self ruleWithICalString:@"FREQ=DAILY;INTERVAL=4"];
+  calc = [iCalRecurrenceCalculator recurrenceCalculatorForRecurrenceRule:rule
+                                   withFirstInstanceCalendarDateRange:fir];
+  result = [calc doesRecurrWithinCalendarDateRange:self->tr1];
+  
+  STAssertFalse(result, @"recurrence unexpected!");
+}
+
+- (void)testBoundDailyRecurrence {
+  iCalRecurrenceRule       *rule;
+  iCalRecurrenceCalculator *calc;
+  BOOL                     result;
+  
+  /* recurrence outside of range */
+  rule = [self ruleWithICalString:@"FREQ=DAILY;INTERVAL=2;COUNT=2"];
+  calc = [iCalRecurrenceCalculator recurrenceCalculatorForRecurrenceRule:rule
+                                      withFirstInstanceCalendarDateRange:fir];
+  result = [calc doesRecurrWithinCalendarDateRange:self->tr1];
+  STAssertFalse(result, @"recurrence!");
+  
+  /* recurrence within range */
+  rule = [self ruleWithICalString:@"FREQ=DAILY;INTERVAL=2;UNTIL=20050212T120000Z"];
+  calc = [iCalRecurrenceCalculator recurrenceCalculatorForRecurrenceRule:rule
+                                      withFirstInstanceCalendarDateRange:fir];
+  result = [calc doesRecurrWithinCalendarDateRange:self->tr1];
+  STAssertTrue(result, @"didn't spot expected recurrence!");
+  
+}
+
+
+- (void)testBoundWeeklyRecurrence {
+  iCalRecurrenceRule       *rule;
+  iCalRecurrenceCalculator *calc;
+  BOOL                     result;
+  
+  /* recurrence outside of range */
+  rule = [self ruleWithICalString:@"FREQ=WEEKLY;INTERVAL=1;UNTIL=20050210T225959Z;BYDAY=WE;WKST=MO"];
+  calc = [iCalRecurrenceCalculator recurrenceCalculatorForRecurrenceRule:rule
+                                   withFirstInstanceCalendarDateRange:fir];
+  result = [calc doesRecurrWithinCalendarDateRange:self->tr1];
+  STAssertFalse(result, @"recurrence!");
+  
+  /* recurrence outside of range */
+  rule = [self ruleWithICalString:@"FREQ=WEEKLY;INTERVAL=1;COUNT=3;BYDAY=WE"];
+  calc = [iCalRecurrenceCalculator recurrenceCalculatorForRecurrenceRule:rule
+                                   withFirstInstanceCalendarDateRange:fir];
+  result = [calc doesRecurrWithinCalendarDateRange:self->tr1];
+  STAssertFalse(result, @"recurrence!");
+}
+
+@end