From 2ffccd5afebcca7286be444b130898ec75702078 Mon Sep 17 00:00:00 2001 From: helge Date: Thu, 22 Sep 2005 10:08:24 +0000 Subject: [PATCH] implemented byday in monthly calculator git-svn-id: http://svn.opengroupware.org/SOPE/trunk@1127 e4a50df8-12e2-0310-a44c-efbce7f8a7e3 --- sope-ical/NGiCal/ChangeLog | 9 + sope-ical/NGiCal/Version | 2 +- .../NGiCal/iCalMonthlyRecurrenceCalculator.m | 209 ++++++++++++------ sope-ical/NGiCal/iCalRecurrenceRule.h | 7 +- sope-ical/NGiCal/iCalRecurrenceRule.m | 44 ++-- 5 files changed, 190 insertions(+), 81 deletions(-) diff --git a/sope-ical/NGiCal/ChangeLog b/sope-ical/NGiCal/ChangeLog index f19df928..1d1f3ae9 100644 --- a/sope-ical/NGiCal/ChangeLog +++ b/sope-ical/NGiCal/ChangeLog @@ -1,3 +1,12 @@ +2005-09-22 Helge Hess + + * v4.5.67 + + * iCalMonthlyRecurrenceCalculator.m: finished 'byday' calculation + + * iCalRecurrenceRule.m: added support for 'bymonthday', fixed handling + of occurrence1 + 2005-09-21 Helge Hess * v4.5.66 diff --git a/sope-ical/NGiCal/Version b/sope-ical/NGiCal/Version index 59e5bbe0..ead974b4 100644 --- a/sope-ical/NGiCal/Version +++ b/sope-ical/NGiCal/Version @@ -2,7 +2,7 @@ MAJOR_VERSION=4 MINOR_VERSION=5 -SUBMINOR_VERSION:=66 +SUBMINOR_VERSION:=67 # v4.5.40 requires NGExtensions v4.5.145 # v4.5.37 requires NGExtensions v4.5.140 diff --git a/sope-ical/NGiCal/iCalMonthlyRecurrenceCalculator.m b/sope-ical/NGiCal/iCalMonthlyRecurrenceCalculator.m index a9045ab3..2bfcdf5d 100644 --- a/sope-ical/NGiCal/iCalMonthlyRecurrenceCalculator.m +++ b/sope-ical/NGiCal/iCalMonthlyRecurrenceCalculator.m @@ -36,12 +36,12 @@ @implementation iCalMonthlyRecurrenceCalculator typedef BOOL NGMonthSet[12]; -typedef BOOL NGMonthDaySet[31]; +typedef BOOL NGMonthDaySet[32]; // 0 is unused static void NGMonthDaySet_clear(NGMonthDaySet *daySet) { register unsigned i; - for (i = 0; i < 31; i++) + for (i = 1; i <= 31; i++) (*daySet)[i] = NO; } @@ -53,32 +53,38 @@ static void NGMonthDaySet_copyOrUnion(NGMonthDaySet *base, NGMonthDaySet *new, if (doCopy) memcpy(base, new, sizeof(NGMonthDaySet)); else { - for (i = 0; i < 31; i++) { + for (i = 1; i <= 31; i++) { if (!(*new)[i]) (*base)[i] = NO; } } } -static void NGMonthDaySet_fillWithByMonthDay(NGMonthDaySet *daySet, - NSArray *byMonthDay, - int numDaysInMonth) +static BOOL NGMonthDaySet_fillWithByMonthDay(NGMonthDaySet *daySet, + NSArray *byMonthDay) { /* list of days in the month */ unsigned i, count; + BOOL ok; NGMonthDaySet_clear(daySet); - for (i = 0, count = [byMonthDay count]; i < count; i++) { + for (i = 0, count = [byMonthDay count], ok = YES; i < count; i++) { int dayInMonth; /* -31..-1 and 1..31 */ - if ((dayInMonth = [[byMonthDay objectAtIndex:i] intValue]) == 0) + if ((dayInMonth = [[byMonthDay objectAtIndex:i] intValue]) == 0) { + ok = NO; continue; /* invalid value */ - if (dayInMonth > numDaysInMonth) - continue; /* this month has less days */ - if (dayInMonth < -numDaysInMonth) - continue; /* this month has less days */ - + } + if (dayInMonth > 31) { + ok = NO; + continue; /* error, value to large */ + } + if (dayInMonth < -31) { + ok = NO; + continue; /* error, value to large */ + } + /* adjust negative days */ if (dayInMonth < 0) { @@ -88,23 +94,96 @@ static void NGMonthDaySet_fillWithByMonthDay(NGMonthDaySet *daySet, (*daySet)[dayInMonth] = YES; } + return ok; +} + +static inline unsigned iCalDoWForNSDoW(int dow) { + switch (dow) { + case 0: return iCalWeekDaySunday; + case 1: return iCalWeekDayMonday; + case 2: return iCalWeekDayTuesday; + case 3: return iCalWeekDayWednesday; + case 4: return iCalWeekDayThursday; + case 5: return iCalWeekDayFriday; + case 6: return iCalWeekDaySaturday; + case 7: return iCalWeekDaySunday; + default: return 0; + } } static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet, unsigned dayMask, + unsigned firstDoWInMonth, int occurrence1) { - unsigned i, count; + // TODO: this is called 'X' because the API doesn't allow for full iCalendar + // functionality. The daymask must be a list of occurence+dow + register unsigned dayInMonth; + register unsigned dow; /* current day of the week */ + int occurrences[7] = { 0, 0, 0, 0, 0, 0, 0 } ; NGMonthDaySet_clear(daySet); - // TODO: complete me + + for (dayInMonth = 1, dow = firstDoWInMonth; dayInMonth <= 31; dayInMonth++) { + // TODO: complete me + + if (dayMask & iCalDoWForNSDoW(dow)) { + if (occurrence1 == 0) + (*daySet)[dayInMonth] = YES; + else { + occurrences[dow] = occurrences[dow] + 1; + + if (occurrences[dow] == occurrence1) + (*daySet)[dayInMonth] = YES; + } + } + + dow = (dow == 6 /* Sat */) ? 0 /* Sun */ : (dow + 1); + } +} + +- (BOOL)_addInstanceWithStartDate:(NSCalendarDate *)_startDate + limitDate:(NSCalendarDate *)_until + limitRange:(NGCalendarDateRange *)_r + toArray:(NSMutableArray *)_ranges +{ + NGCalendarDateRange *r; + NSCalendarDate *end; + + /* check whether we are still in the limits */ + + // TODO: I think we should check in here whether we succeeded the + // repeatCount. Currently we precalculate that info in the + // -lastInstanceStartDate method. + if (_until != nil) { + /* Note: the 'until' in the rrule is inclusive as per spec */ + if ([_until compare:_startDate] == NSOrderedAscending) + /* start after until */ + return NO; /* Note: we assume that the algorithm is sequential */ + } + + /* create end date */ + + end = [_startDate addTimeInterval:[self->firstRange duration]]; + [end setTimeZone:[_startDate timeZone]]; + + /* create range and check whether its in the requested range */ + + r = [[NGCalendarDateRange alloc] initWithStartDate:_startDate endDate:end]; + if ([_r containsDateRange:r]) + [_ranges addObject:r]; + [r release]; r = nil; + + return YES; } - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r{ /* main entry */ + // TODO: check whether this is OK for multiday-events! NSMutableArray *ranges; NSTimeZone *timeZone; NSCalendarDate *eventStartDate, *rStart, *rEnd, *until; + int eventDayOfMonth; unsigned monthIdxInRange, numberOfMonthsInRange, interval; int diff; NGMonthSet byMonthList = { // TODO: fill from rrule, this is the default @@ -112,20 +191,18 @@ static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet, YES, YES, YES, YES, YES, YES }; NSArray *byMonthDay = nil; // array of ints (-31..-1 and 1..31) + NGMonthDaySet byMonthDaySet; - eventStartDate = [self->firstRange startDate]; - timeZone = [eventStartDate timeZone]; - rStart = [_r startDate]; - rEnd = [_r endDate]; - interval = [self->rrule repeatInterval]; - until = [self lastInstanceStartDate]; // TODO: maybe replace - - if ([self->rrule byDayMask] != 0) { - [self errorWithFormat:@"cannot process byday part of rrule: %@", - self->rrule]; - return nil; - } + eventStartDate = [self->firstRange startDate]; + eventDayOfMonth = [eventStartDate dayOfMonth]; + timeZone = [eventStartDate timeZone]; + rStart = [_r startDate]; + rEnd = [_r endDate]; + interval = [self->rrule repeatInterval]; + until = [self lastInstanceStartDate]; // TODO: maybe replace + byMonthDay = [self->rrule byMonthDay]; + /* check whether the range to be processed is beyond the 'until' date */ if (until != nil) { @@ -134,6 +211,13 @@ static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet, if ([until compare:rEnd] == NSOrderedDescending) /* end before until */ rEnd = until; // TODO: why is that? end is _before_ until? } + + + /* precalculate month days (same for all instances) */ + + if (byMonthDay != nil) + NGMonthDaySet_fillWithByMonthDay(&byMonthDaySet, byMonthDay); + // TODO: I think the 'diff' is to skip recurrence which are before the // requested range. Not sure whether this is actually possible, eg @@ -153,13 +237,11 @@ static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet, for (monthIdxInRange = 0; monthIdxInRange < numberOfMonthsInRange; monthIdxInRange++) { - NSCalendarDate *cursor; - NSCalendarDate *start, *end; - NGCalendarDateRange *r; - unsigned numDaysInMonth; - int monthIdxInRecurrence; - NGMonthDaySet monthDays; - BOOL didByFill; + NSCalendarDate *cursor; + unsigned numDaysInMonth; + int monthIdxInRecurrence, dom; + NGMonthDaySet monthDays; + BOOL didByFill, doCont; monthIdxInRecurrence = diff + monthIdxInRange; @@ -194,52 +276,51 @@ static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet, didByFill = NO; if (byMonthDay != nil) { /* list of days in the month */ - NGMonthDaySet ruleset; - - NGMonthDaySet_fillWithByMonthDay(&ruleset, byMonthDay, numDaysInMonth); - NGMonthDaySet_copyOrUnion(&monthDays, &ruleset, !didByFill); + NGMonthDaySet_copyOrUnion(&monthDays, &byMonthDaySet, !didByFill); didByFill = YES; } if ([self->rrule byDayMask] != 0) { // TODO: replace the mask with an array NGMonthDaySet ruleset; + unsigned firstDoWInMonth; + + firstDoWInMonth = [[cursor firstDayOfMonth] dayOfWeek]; NGMonthDaySet_fillWithByDayX(&ruleset, [self->rrule byDayMask], + firstDoWInMonth, [self->rrule byDayOccurence1]); NGMonthDaySet_copyOrUnion(&monthDays, &ruleset, !didByFill); didByFill = YES; } - - // TODO: complete byday support - - /* set start date */ - - start = cursor; - - /* check whether we are still in the limits */ - - // TODO: I think we should check in here whether we succeeded the - // repeatCount. Currently we precalculate that info in the - // -lastInstanceStartDate method. - if (until != nil) { - /* Note: the 'until' in the rrule is inclusive as per spec */ - if ([until compare:start] == NSOrderedAscending) /* start after until */ - break; /* Note: we assume that the algorithm is sequential */ + if (!didByFill) { + /* no rules applied, take the dayOfMonth of the startDate */ + NGMonthDaySet_clear(&monthDays); + monthDays[eventDayOfMonth] = YES; } - /* create end date */ - - end = [start addTimeInterval:[self->firstRange duration]]; - [end setTimeZone:timeZone]; + // TODO: add processing of byhour/byminute/bysecond etc - /* create range and check whether its in the requested range */ - - r = [[NGCalendarDateRange alloc] initWithStartDate:start endDate:end]; - if ([_r containsDateRange:r]) - [ranges addObject:r]; - [r release]; r = nil; + for (dom = 1, doCont = YES; dom <= numDaysInMonth && doCont; dom++) { + NSCalendarDate *start; + + if (!monthDays[dom]) + continue; + + if (eventDayOfMonth == dom) + start = cursor; + else { + start = [cursor dateByAddingYears:0 months:0 + days:(dom - eventDayOfMonth)]; + } + + doCont = [self _addInstanceWithStartDate:start + limitDate:until + limitRange:_r + toArray:ranges]; + } + if (!doCont) break; /* reached some limit */ } return ranges; } diff --git a/sope-ical/NGiCal/iCalRecurrenceRule.h b/sope-ical/NGiCal/iCalRecurrenceRule.h index a36d6753..b1ba5022 100644 --- a/sope-ical/NGiCal/iCalRecurrenceRule.h +++ b/sope-ical/NGiCal/iCalRecurrenceRule.h @@ -55,7 +55,7 @@ typedef enum { iCalWeekDaySunday = 64, } iCalWeekDay; -@class NSString, NSCalendarDate, NGCalendarDateRange; +@class NSString, NSCalendarDate, NGCalendarDateRange, NSArray; @interface iCalRecurrenceRule : NSObject { @@ -70,7 +70,8 @@ typedef enum { unsigned reserved:1; } byDay; int byDayOccurence1; - + NSArray *byMonthDay; + NSString *rrule; } @@ -91,6 +92,8 @@ typedef enum { - (void)setByDayMask:(unsigned)_mask; - (unsigned)byDayMask; - (int)byDayOccurence1; + +- (NSArray *)byMonthDay; /* count and untilDate are mutually exclusive */ diff --git a/sope-ical/NGiCal/iCalRecurrenceRule.m b/sope-ical/NGiCal/iCalRecurrenceRule.m index f437738a..a8aaec04 100644 --- a/sope-ical/NGiCal/iCalRecurrenceRule.m +++ b/sope-ical/NGiCal/iCalRecurrenceRule.m @@ -80,6 +80,7 @@ } - (void)dealloc { + [self->byMonthDay release]; [self->untilDate release]; [self->rrule release]; [super dealloc]; @@ -103,7 +104,7 @@ } - (void)setUntilDate:(NSCalendarDate *)_untilDate { - ASSIGN(self->untilDate, _untilDate); + ASSIGNCOPY(self->untilDate, _untilDate); } - (NSCalendarDate *)untilDate { return self->untilDate; @@ -133,6 +134,10 @@ return self->byDayOccurence1; } +- (NSArray *)byMonthDay { + return self->byMonthDay; +} + - (BOOL)isInfinite { return (self->repeatCount != 0 || self->untilDate) ? NO : YES; } @@ -211,7 +216,7 @@ */ - (NSString *)byDayList { NSMutableString *s; - unsigned i, mask, day; + unsigned dow, mask, day; BOOL needsComma; s = [NSMutableString stringWithCapacity:20]; @@ -219,12 +224,13 @@ mask = self->byDay.mask; day = iCalWeekDayMonday; - for (i = 0; i < 7; i++) { + for (dow = 0 /* Sun */; dow < 7; dow++) { if (mask & day) { if (needsComma) [s appendString:@","]; - else if (self->byDay.useOccurence) - // Note: we only support one occurrence currently + + if (self->byDay.useOccurence) + // Note: we only support one occurrence for all currently [s appendFormat:@"%i", self->byDayOccurence1]; [s appendString:[self iCalRepresentationForWeekDay:day]]; @@ -253,6 +259,7 @@ NSString *pUntil = nil; NSString *pCount = nil; NSString *pByday = nil; + NSString *pBymday = nil; NSString *pBysetpos = nil; props = [_rrule componentsSeparatedByString:@";"]; @@ -281,17 +288,18 @@ vHolder = NULL; switch ([key characterAtIndex:0]) { case 'b': - if ([key isEqualToString:@"byday"]) vHolder = &pByday; break; - if ([key isEqualToString:@"bysetpos"]) vHolder = &pBysetpos; break; + if ([key isEqualToString:@"byday"]) { vHolder = &pByday; break; } + if ([key isEqualToString:@"bymonthday"]) { vHolder = &pBymday; break; } + if ([key isEqualToString:@"bysetpos"]) { vHolder = &pBysetpos; break; } break; case 'c': - if ([key isEqualToString:@"count"]) vHolder = &pCount; break; + if ([key isEqualToString:@"count"]) { vHolder = &pCount; break; } break; case 'f': - if ([key isEqualToString:@"freq"]) vHolder = &pFrequency; break; + if ([key isEqualToString:@"freq"]) { vHolder = &pFrequency; break; } break; case 'u': - if ([key isEqualToString:@"until"]) vHolder = &pUntil; break; + if ([key isEqualToString:@"until"]) { vHolder = &pUntil; break; } break; default: break; @@ -306,7 +314,7 @@ else { // TODO: we should just parse known keys and put remainders into a // separate dictionary - //[self logWithFormat:@"TODO: add explicit support for key: %@", key]; + [self logWithFormat:@"TODO: add explicit support for key: %@", key]; [self takeValue:value forKey:key]; } } @@ -323,6 +331,14 @@ // TODO: we should parse byday in here if (pByday != nil) [self setByday:pByday]; [pByday release]; pByday = nil; + + if (pBymday != nil) { + NSArray *t; + + t = [pBymday componentsSeparatedByString:@","]; + ASSIGNCOPY(self->byMonthDay, t); + } + [pBymday release]; pBymday = nil; if (pBysetpos != nil) // TODO: implement @@ -440,14 +456,14 @@ int offset; occurence = [iCalDay intValue]; - - offset = 1; + + offset = 1; /* skip occurence */ while (offset < len && isdigit([iCalDay characterAtIndex:offset])) offset++; iCalDay = [iCalDay substringFromIndex:offset]; - if (self->byDay.useOccurence) { + if (self->byDay.useOccurence && (occurence != self->byDayOccurence1)) { [self errorWithFormat: @"we only supported one occurence (occ=%i,day=%@): '%@'", occurence, iCalDay, _byDayList]; -- 2.39.5