2 Copyright (C) 2004-2007 SKYRIX Software AG
3 Copyright (C) 2007 Helge Hess
5 This file is part of SOPE.
7 SOPE is free software; you can redistribute it and/or modify it under
8 the terms of the GNU Lesser General Public License as published by the
9 Free Software Foundation; either version 2, or (at your option) any
12 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
15 License for more details.
17 You should have received a copy of the GNU Lesser General Public
18 License along with SOPE; see the file COPYING. If not, write to the
19 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
23 #include "iCalRecurrenceCalculator.h"
24 #include <NGExtensions/NGCalendarDateRange.h>
25 #include "iCalRecurrenceRule.h"
26 #include "NSCalendarDate+ICal.h"
34 @interface iCalRecurrenceCalculator (PrivateAPI)
35 - (NSCalendarDate *)lastInstanceStartDate;
37 - (unsigned)offsetFromSundayForJulianNumber:(long)_jn;
38 - (unsigned)offsetFromSundayForWeekDay:(iCalWeekDay)_weekDay;
39 - (unsigned)offsetFromSundayForCurrentWeekStart;
41 - (iCalWeekDay)weekDayForJulianNumber:(long)_jn;
44 @implementation iCalRecurrenceCalculator
46 static Class NSCalendarDateClass = Nil;
47 static Class iCalRecurrenceRuleClass = Nil;
48 static Class dailyCalcClass = Nil;
49 static Class weeklyCalcClass = Nil;
50 static Class monthlyCalcClass = Nil;
51 static Class yearlyCalcClass = Nil;
54 static BOOL didInit = NO;
59 NSCalendarDateClass = [NSCalendarDate class];
60 iCalRecurrenceRuleClass = [iCalRecurrenceRule class];
62 dailyCalcClass = NSClassFromString(@"iCalDailyRecurrenceCalculator");
63 weeklyCalcClass = NSClassFromString(@"iCalWeeklyRecurrenceCalculator");
64 monthlyCalcClass = NSClassFromString(@"iCalMonthlyRecurrenceCalculator");
65 yearlyCalcClass = NSClassFromString(@"iCalYearlyRecurrenceCalculator");
70 + (id)recurrenceCalculatorForRecurrenceRule:(iCalRecurrenceRule *)_rrule
71 withFirstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
73 return [[[self alloc] initWithRecurrenceRule:_rrule
74 firstInstanceCalendarDateRange:_range] autorelease];
77 /* complex calculation convenience */
79 + (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r
80 firstInstanceCalendarDateRange:(NGCalendarDateRange *)_fir
81 recurrenceRules:(NSArray *)_rRules
82 exceptionRules:(NSArray *)_exRules
83 exceptionDates:(NSArray *)_exDates
86 iCalRecurrenceCalculator *calc;
87 NSMutableArray *ranges;
88 NSMutableArray *exDates;
89 unsigned i, count, rCount;
91 ranges = [NSMutableArray arrayWithCapacity:64];
93 for (i = 0, count = [_rRules count]; i < count; i++) {
96 rule = [_rRules objectAtIndex:i];
97 if (![rule isKindOfClass:iCalRecurrenceRuleClass])
98 rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:rule];
100 calc = [self recurrenceCalculatorForRecurrenceRule:rule
101 withFirstInstanceCalendarDateRange:_fir];
102 rs = [calc recurrenceRangesWithinCalendarDateRange:_r];
103 [ranges addObjectsFromArray:rs];
106 if ([ranges count] == 0)
109 /* test if any exceptions do match */
111 for (i = 0, count = [_exRules count]; i < count; i++) {
114 rule = [_exRules objectAtIndex:i];
115 if (![rule isKindOfClass:iCalRecurrenceRuleClass])
116 rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:rule];
118 calc = [self recurrenceCalculatorForRecurrenceRule:rule
119 withFirstInstanceCalendarDateRange:_fir];
120 rs = [calc recurrenceRangesWithinCalendarDateRange:_r];
121 [ranges removeObjectsInArray:rs];
124 if (![ranges isNotEmpty])
127 /* exception dates */
129 if ((count = [_exDates count]) == 0)
132 /* sort out exDates not within range */
134 exDates = [NSMutableArray arrayWithCapacity:count];
135 for (i = 0; i < count; i++) {
138 exDate = [_exDates objectAtIndex:i];
139 if (![exDate isKindOfClass:NSCalendarDateClass])
140 exDate = [NSCalendarDate calendarDateWithICalRepresentation:exDate];
142 if ([_r containsDate:exDate])
143 [exDates addObject:exDate];
146 /* remove matching exDates from ranges */
148 if ((count = [exDates count]) == 0)
151 for (i = 0, rCount = [ranges count]; i < count; i++) {
152 NSCalendarDate *exDate;
153 NGCalendarDateRange *r;
156 exDate = [exDates objectAtIndex:i];
157 for (k = 0; k < rCount; k++) {
160 rIdx = (rCount - k) - 1;
161 r = [ranges objectAtIndex:rIdx];
162 if ([r containsDate:exDate]) {
163 [ranges removeObjectAtIndex:rIdx];
165 break; /* this is safe because we know that ranges don't overlap */
175 - (id)initWithRecurrenceRule:(iCalRecurrenceRule *)_rrule
176 firstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
178 iCalRecurrenceFrequency freq;
179 Class calcClass = Nil;
181 freq = [_rrule frequency];
182 if (freq == iCalRecurrenceFrequenceDaily)
183 calcClass = dailyCalcClass;
184 else if (freq == iCalRecurrenceFrequenceWeekly)
185 calcClass = weeklyCalcClass;
186 else if (freq == iCalRecurrenceFrequenceMonthly)
187 calcClass = monthlyCalcClass;
188 else if (freq == iCalRecurrenceFrequenceYearly)
189 calcClass = yearlyCalcClass;
191 [self errorWithFormat:@"unsupported rrule frequency: %@", _rrule];
197 [self autorelease]; // TODO: why autorelease?
198 if (calcClass == Nil)
201 if ((self = [[calcClass alloc] init]) != nil) {
202 self->rrule = [_rrule retain];
203 self->firstRange = [_range retain];
209 [self->firstRange release];
210 [self->rrule release];
216 - (unsigned)offsetFromSundayForJulianNumber:(long)_jn {
217 return (unsigned)((int)(_jn + 1.5)) % 7;
220 - (unsigned)offsetFromSundayForWeekDay:(iCalWeekDay)_weekDay {
224 case iCalWeekDaySunday: offset = 0; break;
225 case iCalWeekDayMonday: offset = 1; break;
226 case iCalWeekDayTuesday: offset = 2; break;
227 case iCalWeekDayWednesday: offset = 3; break;
228 case iCalWeekDayThursday: offset = 4; break;
229 case iCalWeekDayFriday: offset = 5; break;
230 case iCalWeekDaySaturday: offset = 6; break;
231 default: offset = 0; break;
236 - (unsigned)offsetFromSundayForCurrentWeekStart {
237 return [self offsetFromSundayForWeekDay:[self->rrule weekStart]];
240 - (iCalWeekDay)weekDayForJulianNumber:(long)_jn {
244 day = [self offsetFromSundayForJulianNumber:_jn];
246 case 0: weekDay = iCalWeekDaySunday; break;
247 case 1: weekDay = iCalWeekDayMonday; break;
248 case 2: weekDay = iCalWeekDayTuesday; break;
249 case 3: weekDay = iCalWeekDayWednesday; break;
250 case 4: weekDay = iCalWeekDayThursday; break;
251 case 5: weekDay = iCalWeekDayFriday; break;
252 case 6: weekDay = iCalWeekDaySaturday; break;
254 [self errorWithFormat:@"got unexpected weekday: %d", day];
255 weekDay = iCalWeekDaySunday;
256 break; /* keep compiler happy */
263 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r{
264 return nil; /* subclass responsibility */
266 - (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
269 ranges = [self recurrenceRangesWithinCalendarDateRange:_range];
270 return (ranges == nil || [ranges count] == 0) ? NO : YES;
273 - (NGCalendarDateRange *)firstInstanceCalendarDateRange {
274 return self->firstRange;
277 - (NGCalendarDateRange *)lastInstanceCalendarDateRange {
278 NSCalendarDate *start, *end;
280 if ((start = [self lastInstanceStartDate]) == nil)
283 end = [start addTimeInterval:[self->firstRange duration]];
284 return [NGCalendarDateRange calendarDateRangeWithStartDate:start
288 - (NSCalendarDate *)lastInstanceStartDate {
289 NSCalendarDate *until;
292 NOTE: this is horribly inaccurate and doesn't even consider the use
293 of repeatCount. It MUST be implemented by subclasses properly!
294 However, it does the trick for SOGo 1.0 - that's why it's left here.
296 if ((until = [self->rrule untilDate]) != nil)
305 - (NSString *)description {
308 ms = [NSMutableString stringWithCapacity:128];
309 [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
311 if (self->rrule != nil)
312 [ms appendFormat:@" rrule=%@", self->rrule];
314 if (self->firstRange != nil)
315 [ms appendFormat:@" range1=%@", self->firstRange];
317 [ms appendString:@">"];
321 @end /* iCalRecurrenceCalculator */