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 #import <NGExtensions/NSCalendarDate+misc.h>
23 #import <NGExtensions/NGCalendarDateRange.h>
24 #import <NGExtensions/NSNull+misc.h>
25 #import <NGExtensions/NSObject+Logs.h>
27 #import "iCalRecurrenceRule.h"
28 #import "NSCalendarDate+ICal.h"
31 #import "iCalRecurrenceCalculator.h"
38 @interface iCalRecurrenceCalculator (PrivateAPI)
39 - (NSCalendarDate *)lastInstanceStartDate;
41 - (unsigned)offsetFromSundayForJulianNumber:(long)_jn;
42 - (unsigned)offsetFromSundayForWeekDay:(iCalWeekDay)_weekDay;
43 - (unsigned)offsetFromSundayForCurrentWeekStart;
45 - (iCalWeekDay)weekDayForJulianNumber:(long)_jn;
48 @implementation iCalRecurrenceCalculator
50 static Class NSCalendarDateClass = Nil;
51 static Class iCalRecurrenceRuleClass = Nil;
52 static Class dailyCalcClass = Nil;
53 static Class weeklyCalcClass = Nil;
54 static Class monthlyCalcClass = Nil;
55 static Class yearlyCalcClass = Nil;
58 static BOOL didInit = NO;
63 NSCalendarDateClass = [NSCalendarDate class];
64 iCalRecurrenceRuleClass = [iCalRecurrenceRule class];
66 dailyCalcClass = NSClassFromString(@"iCalDailyRecurrenceCalculator");
67 weeklyCalcClass = NSClassFromString(@"iCalWeeklyRecurrenceCalculator");
68 monthlyCalcClass = NSClassFromString(@"iCalMonthlyRecurrenceCalculator");
69 yearlyCalcClass = NSClassFromString(@"iCalYearlyRecurrenceCalculator");
74 + (id)recurrenceCalculatorForRecurrenceRule:(iCalRecurrenceRule *)_rrule
75 withFirstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
77 return [[[self alloc] initWithRecurrenceRule:_rrule
78 firstInstanceCalendarDateRange:_range] autorelease];
81 /* complex calculation convenience */
83 + (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r
84 firstInstanceCalendarDateRange:(NGCalendarDateRange *)_fir
85 recurrenceRules:(NSArray *)_rRules
86 exceptionRules:(NSArray *)_exRules
87 exceptionDates:(NSArray *)_exDates
90 iCalRecurrenceCalculator *calc;
91 NSMutableArray *ranges;
92 NSMutableArray *exDates;
93 unsigned i, count, rCount;
95 ranges = [NSMutableArray arrayWithCapacity:64];
97 for (i = 0, count = [_rRules count]; i < count; i++) {
100 rule = [_rRules objectAtIndex:i];
101 if (![rule isKindOfClass:iCalRecurrenceRuleClass])
102 rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:rule];
104 calc = [self recurrenceCalculatorForRecurrenceRule:rule
105 withFirstInstanceCalendarDateRange:_fir];
107 rs = [calc recurrenceRangesWithinCalendarDateRange:_r];
108 [ranges addObjectsFromArray:rs];
111 if ([ranges count] == 0)
114 /* test if any exceptions do match */
116 for (i = 0, count = [_exRules count]; i < count; i++) {
119 rule = [_exRules objectAtIndex:i];
120 if (![rule isKindOfClass:iCalRecurrenceRuleClass])
121 rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:rule];
123 calc = [self recurrenceCalculatorForRecurrenceRule:rule
124 withFirstInstanceCalendarDateRange:_fir];
125 rs = [calc recurrenceRangesWithinCalendarDateRange:_r];
126 [ranges removeObjectsInArray:rs];
129 if (![ranges isNotEmpty])
132 /* exception dates */
134 if ((count = [_exDates count]) == 0)
137 /* sort out exDates not within range */
139 exDates = [NSMutableArray arrayWithCapacity:count];
140 for (i = 0; i < count; i++) {
143 exDate = [_exDates objectAtIndex:i];
144 if (![exDate isKindOfClass:NSCalendarDateClass])
145 exDate = [NSCalendarDate calendarDateWithICalRepresentation:exDate];
147 if ([_r containsDate:exDate])
148 [exDates addObject:exDate];
151 /* remove matching exDates from ranges */
153 if ((count = [exDates count]) == 0)
156 for (i = 0, rCount = [ranges count]; i < count; i++) {
157 NSCalendarDate *exDate;
158 NGCalendarDateRange *r;
161 exDate = [exDates objectAtIndex:i];
163 for (k = 0; k < rCount; k++) {
166 rIdx = (rCount - k) - 1;
167 r = [ranges objectAtIndex:rIdx];
168 if ([r containsDate:exDate]) {
169 [ranges removeObjectAtIndex:rIdx];
171 break; /* this is safe because we know that ranges don't overlap */
181 - (id)initWithRecurrenceRule:(iCalRecurrenceRule *)_rrule
182 firstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
184 iCalRecurrenceFrequency freq;
185 Class calcClass = Nil;
187 freq = [_rrule frequency];
188 if (freq == iCalRecurrenceFrequenceDaily)
189 calcClass = dailyCalcClass;
190 else if (freq == iCalRecurrenceFrequenceWeekly)
191 calcClass = weeklyCalcClass;
192 else if (freq == iCalRecurrenceFrequenceMonthly)
193 calcClass = monthlyCalcClass;
194 else if (freq == iCalRecurrenceFrequenceYearly)
195 calcClass = yearlyCalcClass;
197 [self errorWithFormat:@"unsupported rrule frequency: %@", _rrule];
203 [self autorelease]; // TODO: why autorelease?
204 if (calcClass == Nil)
207 if ((self = [[calcClass alloc] init]) != nil) {
208 self->rrule = [_rrule retain];
209 self->firstRange = [_range retain];
215 [self->firstRange release];
216 [self->rrule release];
222 - (unsigned)offsetFromSundayForJulianNumber:(long)_jn {
223 return (unsigned)((int)(_jn + 1.5)) % 7;
226 - (unsigned)offsetFromSundayForWeekDay:(iCalWeekDay)_weekDay {
230 case iCalWeekDaySunday: offset = 0; break;
231 case iCalWeekDayMonday: offset = 1; break;
232 case iCalWeekDayTuesday: offset = 2; break;
233 case iCalWeekDayWednesday: offset = 3; break;
234 case iCalWeekDayThursday: offset = 4; break;
235 case iCalWeekDayFriday: offset = 5; break;
236 case iCalWeekDaySaturday: offset = 6; break;
237 default: offset = 0; break;
242 - (unsigned)offsetFromSundayForCurrentWeekStart {
243 return [self offsetFromSundayForWeekDay:[self->rrule weekStart]];
246 - (iCalWeekDay)weekDayForJulianNumber:(long)_jn {
250 day = [self offsetFromSundayForJulianNumber:_jn];
252 case 0: weekDay = iCalWeekDaySunday; break;
253 case 1: weekDay = iCalWeekDayMonday; break;
254 case 2: weekDay = iCalWeekDayTuesday; break;
255 case 3: weekDay = iCalWeekDayWednesday; break;
256 case 4: weekDay = iCalWeekDayThursday; break;
257 case 5: weekDay = iCalWeekDayFriday; break;
258 case 6: weekDay = iCalWeekDaySaturday; break;
260 [self errorWithFormat:@"got unexpected weekday: %d", day];
261 weekDay = iCalWeekDaySunday;
262 break; /* keep compiler happy */
269 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r{
270 return nil; /* subclass responsibility */
272 - (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
275 ranges = [self recurrenceRangesWithinCalendarDateRange:_range];
276 return (ranges == nil || [ranges count] == 0) ? NO : YES;
279 - (NGCalendarDateRange *)firstInstanceCalendarDateRange {
280 return self->firstRange;
283 - (NGCalendarDateRange *)lastInstanceCalendarDateRange {
284 NSCalendarDate *start, *end;
286 if ((start = [self lastInstanceStartDate]) == nil)
289 end = [start addTimeInterval:[self->firstRange duration]];
290 return [NGCalendarDateRange calendarDateRangeWithStartDate:start
294 - (NSCalendarDate *)lastInstanceStartDate {
295 NSCalendarDate *until;
298 NOTE: this is horribly inaccurate and doesn't even consider the use
299 of repeatCount. It MUST be implemented by subclasses properly!
300 However, it does the trick for SOGo 1.0 - that's why it's left here.
302 if ((until = [self->rrule untilDate]) != nil)
308 @end /* iCalRecurrenceCalculator */