From: znek Date: Thu, 10 Feb 2005 23:14:41 +0000 (+0000) Subject: first sketch of recurrence rules and associated functionality X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=35fce177e30a2113ea2013b95347b23eb993bdb1;p=sope first sketch of recurrence rules and associated functionality git-svn-id: http://svn.opengroupware.org/SOPE/trunk@549 e4a50df8-12e2-0310-a44c-efbce7f8a7e3 --- diff --git a/sope-ical/NGiCal/ChangeLog b/sope-ical/NGiCal/ChangeLog index 40dfc469..f79d637b 100644 --- a/sope-ical/NGiCal/ChangeLog +++ b/sope-ical/NGiCal/ChangeLog @@ -1,3 +1,31 @@ +2005-02-11 Marcus Mueller + + * 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 * iCalPerson.[hm]: formalized participationStatus according to RFC2445. diff --git a/sope-ical/NGiCal/GNUmakefile b/sope-ical/NGiCal/GNUmakefile index cbcb84dc..68633a8f 100644 --- a/sope-ical/NGiCal/GNUmakefile +++ b/sope-ical/NGiCal/GNUmakefile @@ -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 diff --git a/sope-ical/NGiCal/NGiCal.h b/sope-ical/NGiCal/NGiCal.h index 35b383be..55728e65 100644 --- a/sope-ical/NGiCal/NGiCal.h +++ b/sope-ical/NGiCal/NGiCal.h @@ -24,6 +24,8 @@ #include #include +#include +#include #include #include #include @@ -36,4 +38,7 @@ #include +#include +#include + #endif /* __NGiCal_H__ */ diff --git a/sope-ical/NGiCal/NGiCal.xcode/project.pbxproj b/sope-ical/NGiCal/NGiCal.xcode/project.pbxproj index 295b30d3..ec2b5a23 100644 --- a/sope-ical/NGiCal/NGiCal.xcode/project.pbxproj +++ b/sope-ical/NGiCal/NGiCal.xcode/project.pbxproj @@ -5,6 +5,64 @@ }; objectVersion = 39; objects = { + AD770E6707AE627500F5C7A1 = { + fileEncoding = 5; + indentWidth = 2; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = iCalRecurrenceRule.h; + refType = 4; + sourceTree = ""; + }; + AD770E6807AE627500F5C7A1 = { + fileEncoding = 12; + indentWidth = 2; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.objc; + path = iCalRecurrenceRule.m; + refType = 4; + sourceTree = ""; + }; + 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 = ""; + }; + AD77103D07AE8F8500F5C7A1 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.objc; + path = iCalRepeatableEntityObject.m; + refType = 4; + sourceTree = ""; + }; + AD77103E07AE8F8500F5C7A1 = { + fileRef = AD77103C07AE8F8500F5C7A1; + isa = PBXBuildFile; + settings = { + }; + }; + AD77103F07AE8F8500F5C7A1 = { + fileRef = AD77103D07AE8F8500F5C7A1; + isa = PBXBuildFile; + settings = { + }; + }; AD8BF0F70701902800EC239A = { isa = PBXFileReference; lastKnownFileType = wrapper.framework; @@ -19,6 +77,272 @@ settings = { }; }; + ADAACE6607B3973900FC48D6 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = iCalRecurrenceCalculator.h; + refType = 4; + sourceTree = ""; + }; + ADAACE6707B3973900FC48D6 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.objc; + path = iCalRecurrenceCalculator.m; + refType = 4; + sourceTree = ""; + }; + 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 = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + NGiCalTests + CFBundleGetInfoString + + CFBundleIdentifier + com.MySoftwareCompany.NGiCalTests + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + FMWK + CFBundleShortVersionString + + CFBundleSignature + ???? + CFBundleVersion + 1.0.0d1 + + +"; + 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 = ""; + }; + 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 = ""; + }; + ADAAD00607B6AF7700FC48D6 = { + isa = PBXFileReference; + lastKnownFileType = wrapper.framework; + name = SenTestingKit.framework; + path = /Library/Frameworks/SenTestingKit.framework; + refType = 0; + sourceTree = ""; + }; + ADAAD00707B6AF7700FC48D6 = { + fileRef = ADAAD00607B6AF7700FC48D6; + isa = PBXBuildFile; + settings = { + }; + }; + ADAAD01F07B6AFA600FC48D6 = { + children = ( + ADAAD04107B6B05000FC48D6, + ADAAD02107B6AFD700FC48D6, + ); + isa = PBXGroup; + name = Classes; + refType = 4; + sourceTree = ""; + }; + ADAAD02107B6AFD700FC48D6 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.objc; + path = iCalRecurrenceCalculatorTests.m; + refType = 4; + sourceTree = ""; + }; + ADAAD02307B6AFD700FC48D6 = { + fileRef = ADAAD02107B6AFD700FC48D6; + isa = PBXBuildFile; + settings = { + }; + }; + ADAAD04007B6B02400FC48D6 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = text; + path = README; + refType = 4; + sourceTree = ""; + }; + ADAAD04107B6B05000FC48D6 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = common.h; + refType = 4; + sourceTree = ""; + }; + ADAAD04207B6B05000FC48D6 = { + fileRef = ADAAD04107B6B05000FC48D6; + isa = PBXBuildFile; + settings = { + }; + }; + ADAAD05C07B6B0CB00FC48D6 = { + children = ( + ADAACFEC07B6AEB500FC48D6, + ); + isa = PBXGroup; + name = Resources; + refType = 4; + sourceTree = ""; + }; + ADAAD0A307B6B74F00FC48D6 = { + fileRef = ADDF503B06DE528200C4E7F8; + isa = PBXBuildFile; + settings = { + }; + }; + ADAAD11107B6B75300FC48D6 = { + fileRef = ADDF503F06DE52F600C4E7F8; + isa = PBXBuildFile; + settings = { + }; + }; + ADAAD11207B6B7A100FC48D6 = { + fileRef = ADDF4E6006DE4FF200C4E7F8; + isa = PBXBuildFile; + settings = { + }; + }; ADBE3DF7072713AF000FEA6A = { fileEncoding = 5; indentWidth = 2; @@ -106,6 +430,7 @@ ADDF4F7306DE516A00C4E7F8, ADDF4FD406DE517900C4E7F8, ADDF503706DE520000C4E7F8, + ADAACFFF07B6AF0E00FC48D6, ADDF4E6506DE500200C4E7F8, ADDF4E6106DE4FF200C4E7F8, ADDF503A06DE524200C4E7F8, @@ -159,6 +484,7 @@ projectDirPath = ""; targets = ( ADDF4E5F06DE4FF200C4E7F8, + ADAACFEA07B6AEB500FC48D6, ); }; ADDF4E5B06DE4FF200C4E7F8 = { @@ -183,6 +509,9 @@ ADDF4F6806DE513D00C4E7F8, ADD1FC9B06E4D6D400E387F0, ADBE3DF8072713AF000FEA6A, + AD770E6907AE627500F5C7A1, + AD77103E07AE8F8500F5C7A1, + ADAACE6807B3973900FC48D6, ); isa = PBXHeadersBuildPhase; runOnlyForDeploymentPostprocessing = 0; @@ -217,6 +546,9 @@ ADDF4F6906DE513D00C4E7F8, ADD1FC9C06E4D6D400E387F0, ADBE3DFA072713C2000FEA6A, + AD770E6A07AE627500F5C7A1, + AD77103F07AE8F8500F5C7A1, + ADAACE6907B3973900FC48D6, ); isa = PBXSourcesBuildPhase; runOnlyForDeploymentPostprocessing = 0; @@ -246,7 +578,7 @@ ); 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; @@ -302,6 +634,7 @@ ADDF4E6106DE4FF200C4E7F8 = { children = ( ADDF4E6006DE4FF200C4E7F8, + ADAACFEB07B6AEB500FC48D6, ); isa = PBXGroup; name = Products; @@ -684,6 +1017,7 @@ }; ADDF4F3906DE513D00C4E7F8 = { fileEncoding = 4; + indentWidth = 2; isa = PBXFileReference; lastKnownFileType = text.xml; path = NGiCal.xmap; @@ -1025,6 +1359,7 @@ ADDF4F2106DE513D00C4E7F8, ADDF4F2306DE513D00C4E7F8, ADDF4F2606DE513D00C4E7F8, + AD77103C07AE8F8500F5C7A1, ADDF4F2806DE513D00C4E7F8, ADDF4F2A06DE513D00C4E7F8, ADDF4F2C06DE513D00C4E7F8, @@ -1034,8 +1369,12 @@ ADDF4F3606DE513D00C4E7F8, ADD1FC9906E4D6D400E387F0, ADBE3DF7072713AF000FEA6A, + AD770E6707AE627500F5C7A1, + ADAACE6607B3973900FC48D6, ADDF4F3A06DE513D00C4E7F8, ); + fileEncoding = 5; + indentWidth = 2; isa = PBXGroup; name = Headers; refType = 4; @@ -1051,6 +1390,7 @@ ADDF4F2206DE513D00C4E7F8, ADDF4F2406DE513D00C4E7F8, ADDF4F2706DE513D00C4E7F8, + AD77103D07AE8F8500F5C7A1, ADDF4F2906DE513D00C4E7F8, ADDF4F2B06DE513D00C4E7F8, ADDF4F2D06DE513D00C4E7F8, @@ -1060,8 +1400,12 @@ ADDF4F3706DE513D00C4E7F8, ADD1FC9A06E4D6D400E387F0, ADBE3DF9072713C2000FEA6A, + AD770E6807AE627500F5C7A1, + ADAACE6707B3973900FC48D6, ADDF4F3B06DE513D00C4E7F8, ); + fileEncoding = 5; + indentWidth = 2; isa = PBXGroup; name = Classes; refType = 4; @@ -1085,6 +1429,7 @@ ADDF503B06DE528200C4E7F8, ADDF503E06DE52F600C4E7F8, ADDF503F06DE52F600C4E7F8, + ADAAD00607B6AF7700FC48D6, ADDF510206DE54BE00C4E7F8, ); isa = PBXGroup; diff --git a/sope-ical/NGiCal/NGiCal.xmap b/sope-ical/NGiCal/NGiCal.xmap index 61521b5f..6ddf3f1a 100644 --- a/sope-ical/NGiCal/NGiCal.xmap +++ b/sope-ical/NGiCal/NGiCal.xmap @@ -8,7 +8,7 @@ tagKey = "tag"; ToManyRelationships = { - "subcomponents" = ( vcalendar ); + "subcomponents" = ( vcalendar ); }; }; @@ -22,11 +22,11 @@ }; ToManyRelationships = { - events = ( vevent ); - todos = ( vtodo ); - journals = ( journals ); - freeBusys = ( vfreebusy ); - timezones = ( vtimezone ); + events = ( vevent ); + todos = ( vtodo ); + journals = ( journals ); + freeBusys = ( vfreebusy ); + timezones = ( vtimezone ); }; }; @@ -34,12 +34,14 @@ class = iCalEvent; attributes = { - rrule = recurrenceRule; }; ToManyRelationships = { - "alarms" = ( valarm ); - "attendees" = ( attendee ); + "alarms" = ( valarm ); + "attendees" = ( attendee ); + "recurrenceRules" = ( rrule ); + "exceptionRules" = ( exrule ); + "exceptionDates" = ( exdate ); }; }; @@ -47,12 +49,11 @@ 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; @@ -86,22 +87,22 @@ 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; }; }; @@ -144,6 +145,15 @@ tagKey = "tag"; contentKey = "string"; }; + exdate = { + class = iCalDateHolder; + key = "recurrenceRuleExceptionDate"; + attributes = { + tzid = tzid; + }; + tagKey = "tag"; + contentKey = "string"; + }; due = { class = iCalDateHolder; attributes = { @@ -230,7 +240,27 @@ 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; diff --git a/sope-ical/NGiCal/README b/sope-ical/NGiCal/README index df050b77..dc6dc64b 100644 --- a/sope-ical/NGiCal/README +++ b/sope-ical/NGiCal/README @@ -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. diff --git a/sope-ical/NGiCal/Version b/sope-ical/NGiCal/Version index 177d90a9..c2c1f65e 100644 --- a/sope-ical/NGiCal/Version +++ b/sope-ical/NGiCal/Version @@ -2,6 +2,6 @@ MAJOR_VERSION=4 MINOR_VERSION=5 -SUBMINOR_VERSION:=38 +SUBMINOR_VERSION:=39 # v4.5.37 requires NGExtensions v4.5.140 diff --git a/sope-ical/NGiCal/iCalEvent.h b/sope-ical/NGiCal/iCalEvent.h index 4c8303a2..2c7489a2 100644 --- a/sope-ical/NGiCal/iCalEvent.h +++ b/sope-ical/NGiCal/iCalEvent.h @@ -22,7 +22,7 @@ #ifndef __NGiCal_iCalEvent_H__ #define __NGiCal_iCalEvent_H__ -#include +#include #import /* @@ -33,14 +33,13 @@ */ @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; diff --git a/sope-ical/NGiCal/iCalEvent.m b/sope-ical/NGiCal/iCalEvent.m index 91b4ec8d..bb60b10b 100644 --- a/sope-ical/NGiCal/iCalEvent.m +++ b/sope-ical/NGiCal/iCalEvent.m @@ -22,6 +22,7 @@ #include "iCalEvent.h" #include "iCalPerson.h" #include "iCalEventChanges.h" +#include "iCalRecurrenceRule.h" #include "iCalRenderer.h" #include #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]; } @@ -104,13 +105,6 @@ return 0.0; } -- (void)setRecurrenceRule:(NSString *)_recurrenceRule { - ASSIGNCOPY(self->recurrenceRule, _recurrenceRule); -} -- (NSString *)recurrenceRule { - return self->recurrenceRule; -} - - (void)setTransparency:(NSString *)_transparency { ASSIGNCOPY(self->transparency, _transparency); } @@ -130,22 +124,27 @@ } /* 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) { @@ -159,6 +158,15 @@ return [_range containsDate:self->startDate]; } } + else { + NGCalendarDateRange *fir; + + fir = [NGCalendarDateRange calendarDateRangeWithStartDate:self->startDate + endDate:self->endDate]; + + return [self isWithinCalendarDateRange:_range + firstInstanceCalendarDateRange:fir]; + } return NO; } @@ -189,9 +197,6 @@ 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 index 00000000..49ca6b7f --- /dev/null +++ b/sope-ical/NGiCal/iCalRecurrenceCalculator.h @@ -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 + +/* + 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 index 00000000..63e7b91b --- /dev/null +++ b/sope-ical/NGiCal/iCalRecurrenceCalculator.m @@ -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 +#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 index 00000000..eb1a5bf9 --- /dev/null +++ b/sope-ical/NGiCal/iCalRecurrenceRule.h @@ -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 + +/* + 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 index 00000000..9278888f --- /dev/null +++ b/sope-ical/NGiCal/iCalRecurrenceRule.m @@ -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 index 00000000..33509a07 --- /dev/null +++ b/sope-ical/NGiCal/iCalRepeatableEntityObject.h @@ -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 + +/* + 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 index 00000000..3b4345e3 --- /dev/null +++ b/sope-ical/NGiCal/iCalRepeatableEntityObject.m @@ -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 +#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 diff --git a/sope-ical/NGiCal/iCalToDo.h b/sope-ical/NGiCal/iCalToDo.h index cd04d9dd..936c6263 100644 --- a/sope-ical/NGiCal/iCalToDo.h +++ b/sope-ical/NGiCal/iCalToDo.h @@ -22,7 +22,7 @@ #ifndef __NGiCal_iCalToDo_H__ #define __NGiCal_iCalToDo_H__ -#include +#include /* iCalToDo @@ -32,12 +32,11 @@ @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__ */ diff --git a/sope-ical/NGiCal/iCalToDo.m b/sope-ical/NGiCal/iCalToDo.m index cf7a4b3e..bda4e84f 100644 --- a/sope-ical/NGiCal/iCalToDo.m +++ b/sope-ical/NGiCal/iCalToDo.m @@ -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]; } @@ -61,13 +61,6 @@ 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 index 00000000..643cb20d --- /dev/null +++ b/sope-ical/NGiCal/tests/NGiCalTests-Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + NGiCalTests + CFBundleGetInfoString + + CFBundleIdentifier + org.OpenGroupware.SOPE.ical.NGiCalTests + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + FMWK + CFBundleShortVersionString + + CFBundleSignature + ???? + CFBundleVersion + 4.5 + + diff --git a/sope-ical/NGiCal/tests/README b/sope-ical/NGiCal/tests/README new file mode 100644 index 00000000..cff29bb3 --- /dev/null +++ b/sope-ical/NGiCal/tests/README @@ -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 index 00000000..ae2f086c --- /dev/null +++ b/sope-ical/NGiCal/tests/common.h @@ -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 +#import +#include +#include + +#endif /* __NGiCalTests_common_H__ */ diff --git a/sope-ical/NGiCal/tests/iCalRecurrenceCalculatorTests.m b/sope-ical/NGiCal/tests/iCalRecurrenceCalculatorTests.m new file mode 100644 index 00000000..a6905032 --- /dev/null +++ b/sope-ical/NGiCal/tests/iCalRecurrenceCalculatorTests.m @@ -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