2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of SOPE.
6 SOPE is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with SOPE; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include "iCalRecurrenceCalculator.h"
24 @interface iCalMonthlyRecurrenceCalculator : iCalRecurrenceCalculator
27 #include <NGExtensions/NGCalendarDateRange.h>
28 #include "iCalRecurrenceRule.h"
29 #include "NSCalendarDate+ICal.h"
32 @interface iCalRecurrenceCalculator(PrivateAPI)
33 - (NSCalendarDate *)lastInstanceStartDate;
36 @implementation iCalMonthlyRecurrenceCalculator
38 typedef BOOL NGMonthSet[12];
39 typedef BOOL NGMonthDaySet[31];
41 static void NGMonthDaySet_clear(NGMonthDaySet *daySet) {
44 for (i = 0; i < 31; i++)
48 static void NGMonthDaySet_copyOrUnion(NGMonthDaySet *base, NGMonthDaySet *new,
54 memcpy(base, new, sizeof(NGMonthDaySet));
56 for (i = 0; i < 31; i++) {
63 static void NGMonthDaySet_fillWithByMonthDay(NGMonthDaySet *daySet,
67 /* list of days in the month */
70 NGMonthDaySet_clear(daySet);
72 for (i = 0, count = [byMonthDay count]; i < count; i++) {
73 int dayInMonth; /* -31..-1 and 1..31 */
75 if ((dayInMonth = [[byMonthDay objectAtIndex:i] intValue]) == 0)
76 continue; /* invalid value */
77 if (dayInMonth > numDaysInMonth)
78 continue; /* this month has less days */
79 if (dayInMonth < -numDaysInMonth)
80 continue; /* this month has less days */
82 /* adjust negative days */
85 /* eg: -1 == last day in month, 30 days => 30 */
86 dayInMonth = 32 - dayInMonth /* because we count from 1 */;
89 (*daySet)[dayInMonth] = YES;
93 static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet,
99 NGMonthDaySet_clear(daySet);
103 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r{
105 NSMutableArray *ranges;
106 NSTimeZone *timeZone;
107 NSCalendarDate *eventStartDate, *rStart, *rEnd, *until;
108 unsigned monthIdxInRange, numberOfMonthsInRange, interval;
110 NGMonthSet byMonthList = { // TODO: fill from rrule, this is the default
111 YES, YES, YES, YES, YES, YES,
112 YES, YES, YES, YES, YES, YES
114 NSArray *byMonthDay = nil; // array of ints (-31..-1 and 1..31)
116 eventStartDate = [self->firstRange startDate];
117 timeZone = [eventStartDate timeZone];
118 rStart = [_r startDate];
120 interval = [self->rrule repeatInterval];
121 until = [self lastInstanceStartDate]; // TODO: maybe replace
123 if ([self->rrule byDayMask] != 0) {
124 [self errorWithFormat:@"cannot process byday part of rrule: %@",
129 /* check whether the range to be processed is beyond the 'until' date */
132 if ([until compare:rStart] == NSOrderedAscending) /* until before start */
134 if ([until compare:rEnd] == NSOrderedDescending) /* end before until */
135 rEnd = until; // TODO: why is that? end is _before_ until?
138 // TODO: I think the 'diff' is to skip recurrence which are before the
139 // requested range. Not sure whether this is actually possible, eg
140 // the repeatCount must be processed from the start.
141 diff = [eventStartDate monthsBetweenDate:rStart];
142 if ((diff != 0) && [rStart compare:eventStartDate] == NSOrderedAscending)
145 numberOfMonthsInRange = [rStart monthsBetweenDate:rEnd] + 1;
146 ranges = [NSMutableArray arrayWithCapacity:numberOfMonthsInRange];
149 Note: we do not add 'eventStartDate', this is intentional, the event date
150 itself is _not_ necessarily part of the sequence, eg with monthly
154 for (monthIdxInRange = 0; monthIdxInRange < numberOfMonthsInRange;
156 NSCalendarDate *cursor;
157 NSCalendarDate *start, *end;
158 NGCalendarDateRange *r;
159 unsigned numDaysInMonth;
160 int monthIdxInRecurrence;
161 NGMonthDaySet monthDays;
164 monthIdxInRecurrence = diff + monthIdxInRange;
166 if (monthIdxInRecurrence < 0)
169 /* first check whether we are in the interval */
171 if ((monthIdxInRecurrence % interval) != 0)
175 Then the sequence is:
176 - check whether the month is in the BYMONTH list
179 cursor = [eventStartDate dateByAddingYears:0
180 months:(diff + monthIdxInRange)
182 [cursor setTimeZone:timeZone];
183 numDaysInMonth = [cursor numberOfDaysInMonth];
186 /* check whether we match the bymonth specification */
188 if (!byMonthList[[cursor monthOfYear] - 1])
192 /* check 'day level' byXYZ rules */
196 if (byMonthDay != nil) { /* list of days in the month */
197 NGMonthDaySet ruleset;
199 NGMonthDaySet_fillWithByMonthDay(&ruleset, byMonthDay, numDaysInMonth);
200 NGMonthDaySet_copyOrUnion(&monthDays, &ruleset, !didByFill);
204 if ([self->rrule byDayMask] != 0) { // TODO: replace the mask with an array
205 NGMonthDaySet ruleset;
207 NGMonthDaySet_fillWithByDayX(&ruleset,
208 [self->rrule byDayMask],
209 [self->rrule byDayOccurence1]);
210 NGMonthDaySet_copyOrUnion(&monthDays, &ruleset, !didByFill);
215 // TODO: complete byday support
221 /* check whether we are still in the limits */
223 // TODO: I think we should check in here whether we succeeded the
224 // repeatCount. Currently we precalculate that info in the
225 // -lastInstanceStartDate method.
227 /* Note: the 'until' in the rrule is inclusive as per spec */
228 if ([until compare:start] == NSOrderedAscending) /* start after until */
229 break; /* Note: we assume that the algorithm is sequential */
232 /* create end date */
234 end = [start addTimeInterval:[self->firstRange duration]];
235 [end setTimeZone:timeZone];
237 /* create range and check whether its in the requested range */
239 r = [[NGCalendarDateRange alloc] initWithStartDate:start endDate:end];
240 if ([_r containsDateRange:r])
241 [ranges addObject:r];
242 [r release]; r = nil;
247 - (NSCalendarDate *)lastInstanceStartDate {
248 if ([self->rrule repeatCount] > 0) {
249 NSCalendarDate *until;
250 unsigned months, interval;
252 interval = [self->rrule repeatInterval];
253 months = [self->rrule repeatCount] - 1 /* the first counts as one! */;
258 until = [[self->firstRange startDate] dateByAddingYears:0
263 return [super lastInstanceStartDate];
266 @end /* iCalMonthlyRecurrenceCalculator */