+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.
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
#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>
#include <NGiCal/iCalEventChanges.h>
+#include <NGiCal/iCalRecurrenceRule.h>
+#include <NGiCal/iCalRecurrenceCalculator.h>
+
#endif /* __NGiCal_H__ */
};
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;
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 );
};
};
class = "iCalAlarm";
attributes = {
- rrule = recurrenceRule;
+ rrule = recurrenceRule;
};
};
class = "iCalFreeBusy";
ToManyRelationships = {
- "entries" = ( freebusy );
+ "entries" = ( freebusy );
};
};
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;
# $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.
MAJOR_VERSION=4
MINOR_VERSION=5
-SUBMINOR_VERSION:=38
+SUBMINOR_VERSION:=39
# v4.5.37 requires NGExtensions v4.5.140
#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 */
- (BOOL)hasDuration;
- (NSTimeInterval)durationAsTimeInterval;
-- (void)setRecurrenceRule:(NSString *)_recurrenceRule;
-- (NSString *)recurrenceRule;
-
- (void)setTransparency:(NSString *)_transparency;
- (NSString *)transparency;
- (BOOL)isOpaque;
- (BOOL)isAllDay;
-- (BOOL)isRecurrent;
- (BOOL)isWithinCalendarDateRange:(NGCalendarDateRange *)_range;
#include "iCalEvent.h"
#include "iCalPerson.h"
#include "iCalEventChanges.h"
+#include "iCalRecurrenceRule.h"
#include "iCalRenderer.h"
#include <NGExtensions/NGCalendarDateRange.h>
#include "common.h"
@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;
}
--- /dev/null
+/*
+ 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_ */
--- /dev/null
+/*
+ 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 */
--- /dev/null
+/*
+ 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_ */
--- /dev/null
+/*
+ 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
--- /dev/null
+/*
+ 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_ */
--- /dev/null
+/*
+ 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
#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;
- (void)setCompleted:(NSCalendarDate *)_date;
- (NSCalendarDate *)completed;
-- (void)setRecurrenceRule:(NSString *)_recurrenceRule;
-- (NSString *)recurrenceRule;
-
@end
#endif /* __NGiCal_iCalToDo_H__ */
*/
#include "iCalToDo.h"
+#include "iCalRecurrenceRule.h"
#include "common.h"
@implementation iCalToDo
[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 {
if (self->summary)
[ms appendFormat:@" summary=%@", self->summary];
- if (self->recurrenceRule)
- [ms appendFormat:@" recurrenceRule=%@", self->recurrenceRule];
-
[ms appendString:@">"];
return ms;
}
--- /dev/null
+<?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>
--- /dev/null
+$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
--- /dev/null
+/*
+ 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__ */
--- /dev/null
+/*
+ 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