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"
23 #include <NGExtensions/NGCalendarDateRange.h>
24 #include "iCalRecurrenceRule.h"
25 #include "NSCalendarDate+ICal.h"
33 @interface iCalRecurrenceCalculator (PrivateAPI)
34 - (NSCalendarDate *)lastInstanceStartDate;
36 - (unsigned)offsetFromSundayForJulianNumber:(long)_jn;
37 - (unsigned)offsetFromSundayForWeekDay:(iCalWeekDay)_weekDay;
38 - (unsigned)offsetFromSundayForCurrentWeekStart;
40 - (iCalWeekDay)weekDayForJulianNumber:(long)_jn;
43 @implementation iCalRecurrenceCalculator
45 static Class NSCalendarDateClass = Nil;
46 static Class iCalRecurrenceRuleClass = Nil;
47 static Class dailyCalcClass = Nil;
48 static Class weeklyCalcClass = Nil;
49 static Class monthlyCalcClass = Nil;
50 static Class yearlyCalcClass = Nil;
53 static BOOL didInit = NO;
58 NSCalendarDateClass = [NSCalendarDate class];
59 iCalRecurrenceRuleClass = [iCalRecurrenceRule class];
61 dailyCalcClass = NSClassFromString(@"iCalDailyRecurrenceCalculator");
62 weeklyCalcClass = NSClassFromString(@"iCalWeeklyRecurrenceCalculator");
63 monthlyCalcClass = NSClassFromString(@"iCalMonthlyRecurrenceCalculator");
64 yearlyCalcClass = NSClassFromString(@"iCalYearlyRecurrenceCalculator");
69 + (id)recurrenceCalculatorForRecurrenceRule:(iCalRecurrenceRule *)_rrule
70 withFirstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
72 return [[[self alloc] initWithRecurrenceRule:_rrule
73 firstInstanceCalendarDateRange:_range] autorelease];
76 /* complex calculation convenience */
78 + (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r
79 firstInstanceCalendarDateRange:(NGCalendarDateRange *)_fir
80 recurrenceRules:(NSArray *)_rRules
81 exceptionRules:(NSArray *)_exRules
82 exceptionDates:(NSArray *)_exDates
85 iCalRecurrenceCalculator *calc;
86 NSMutableArray *ranges;
87 NSMutableArray *exDates;
88 unsigned i, count, rCount;
90 ranges = [NSMutableArray arrayWithCapacity:64];
92 for (i = 0, count = [_rRules count]; i < count; i++) {
95 rule = [_rRules objectAtIndex:i];
96 if (![rule isKindOfClass:iCalRecurrenceRuleClass])
97 rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:rule];
99 calc = [self recurrenceCalculatorForRecurrenceRule:rule
100 withFirstInstanceCalendarDateRange:_fir];
101 rs = [calc recurrenceRangesWithinCalendarDateRange:_r];
102 [ranges addObjectsFromArray:rs];
105 if ([ranges count] == 0)
108 /* test if any exceptions do match */
110 for (i = 0, count = [_exRules count]; i < count; i++) {
113 rule = [_exRules objectAtIndex:i];
114 if (![rule isKindOfClass:iCalRecurrenceRuleClass])
115 rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:rule];
117 calc = [self recurrenceCalculatorForRecurrenceRule:rule
118 withFirstInstanceCalendarDateRange:_fir];
119 rs = [calc recurrenceRangesWithinCalendarDateRange:_r];
120 [ranges removeObjectsInArray:rs];
123 if (![ranges isNotEmpty])
126 /* exception dates */
128 if ((count = [_exDates count]) == 0)
131 /* sort out exDates not within range */
133 exDates = [NSMutableArray arrayWithCapacity:count];
134 for (i = 0; i < count; i++) {
137 exDate = [_exDates objectAtIndex:i];
138 if (![exDate isKindOfClass:NSCalendarDateClass])
139 exDate = [NSCalendarDate calendarDateWithICalRepresentation:exDate];
141 if ([_r containsDate:exDate])
142 [exDates addObject:exDate];
145 /* remove matching exDates from ranges */
147 if ((count = [exDates count]) == 0)
150 for (i = 0, rCount = [ranges count]; i < count; i++) {
151 NSCalendarDate *exDate;
152 NGCalendarDateRange *r;
155 exDate = [exDates objectAtIndex:i];
156 for (k = 0; k < rCount; k++) {
159 rIdx = (rCount - k) - 1;
160 r = [ranges objectAtIndex:rIdx];
161 if ([r containsDate:exDate]) {
162 [ranges removeObjectAtIndex:rIdx];
164 break; /* this is safe because we know that ranges don't overlap */
174 - (id)initWithRecurrenceRule:(iCalRecurrenceRule *)_rrule
175 firstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
177 iCalRecurrenceFrequency freq;
178 Class calcClass = Nil;
180 freq = [_rrule frequency];
181 if (freq == iCalRecurrenceFrequenceDaily)
182 calcClass = dailyCalcClass;
183 else if (freq == iCalRecurrenceFrequenceWeekly)
184 calcClass = weeklyCalcClass;
185 else if (freq == iCalRecurrenceFrequenceMonthly)
186 calcClass = monthlyCalcClass;
187 else if (freq == iCalRecurrenceFrequenceYearly)
188 calcClass = yearlyCalcClass;
190 [self errorWithFormat:@"unsupported rrule frequency: %@", _rrule];
196 [self autorelease]; // TODO: why autorelease?
197 if (calcClass == Nil)
200 if ((self = [[calcClass alloc] init]) != nil) {
201 self->rrule = [_rrule retain];
202 self->firstRange = [_range retain];
208 [self->firstRange release];
209 [self->rrule release];
215 - (unsigned)offsetFromSundayForJulianNumber:(long)_jn {
216 return (unsigned)((int)(_jn + 1.5)) % 7;
219 - (unsigned)offsetFromSundayForWeekDay:(iCalWeekDay)_weekDay {
223 case iCalWeekDaySunday: offset = 0; break;
224 case iCalWeekDayMonday: offset = 1; break;
225 case iCalWeekDayTuesday: offset = 2; break;
226 case iCalWeekDayWednesday: offset = 3; break;
227 case iCalWeekDayThursday: offset = 4; break;
228 case iCalWeekDayFriday: offset = 5; break;
229 case iCalWeekDaySaturday: offset = 6; break;
230 default: offset = 0; break;
235 - (unsigned)offsetFromSundayForCurrentWeekStart {
236 return [self offsetFromSundayForWeekDay:[self->rrule weekStart]];
239 - (iCalWeekDay)weekDayForJulianNumber:(long)_jn {
243 day = [self offsetFromSundayForJulianNumber:_jn];
245 case 0: weekDay = iCalWeekDaySunday; break;
246 case 1: weekDay = iCalWeekDayMonday; break;
247 case 2: weekDay = iCalWeekDayTuesday; break;
248 case 3: weekDay = iCalWeekDayWednesday; break;
249 case 4: weekDay = iCalWeekDayThursday; break;
250 case 5: weekDay = iCalWeekDayFriday; break;
251 case 6: weekDay = iCalWeekDaySaturday; break;
253 [self errorWithFormat:@"got unexpected weekday: %d", day];
254 weekDay = iCalWeekDaySunday;
255 break; /* keep compiler happy */
262 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r{
263 return nil; /* subclass responsibility */
265 - (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
268 ranges = [self recurrenceRangesWithinCalendarDateRange:_range];
269 return (ranges == nil || [ranges count] == 0) ? NO : YES;
272 - (NGCalendarDateRange *)firstInstanceCalendarDateRange {
273 return self->firstRange;
276 - (NGCalendarDateRange *)lastInstanceCalendarDateRange {
277 NSCalendarDate *start, *end;
279 if ((start = [self lastInstanceStartDate]) == nil)
282 end = [start addTimeInterval:[self->firstRange duration]];
283 return [NGCalendarDateRange calendarDateRangeWithStartDate:start
287 - (NSCalendarDate *)lastInstanceStartDate {
288 NSCalendarDate *until;
291 NOTE: this is horribly inaccurate and doesn't even consider the use
292 of repeatCount. It MUST be implemented by subclasses properly!
293 However, it does the trick for SOGo 1.0 - that's why it's left here.
295 if ((until = [self->rrule untilDate]) != nil)
301 @end /* iCalRecurrenceCalculator */