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"
30 @interface iCalDailyRecurrenceCalculator : iCalRecurrenceCalculator
35 @interface iCalWeeklyRecurrenceCalculator : iCalRecurrenceCalculator
40 @interface iCalMonthlyRecurrenceCalculator : iCalRecurrenceCalculator
45 @interface iCalYearlyRecurrenceCalculator : iCalRecurrenceCalculator
52 @interface iCalRecurrenceCalculator (PrivateAPI)
53 - (NSCalendarDate *)lastInstanceStartDate;
56 @implementation iCalRecurrenceCalculator
58 static Class dailyCalcClass = Nil;
59 static Class weeklyCalcClass = Nil;
60 static Class monthlyCalcClass = Nil;
61 static Class yearlyCalcClass = Nil;
64 static BOOL didInit = NO;
69 dailyCalcClass = [iCalDailyRecurrenceCalculator class];
70 weeklyCalcClass = [iCalWeeklyRecurrenceCalculator class];
71 monthlyCalcClass = [iCalMonthlyRecurrenceCalculator class];
72 yearlyCalcClass = [iCalYearlyRecurrenceCalculator class];
75 + (id)recurrenceCalculatorForRecurrenceRule:(iCalRecurrenceRule *)_rrule
76 withFirstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
78 return [[[self alloc] initWithRecurrenceRule:_rrule
79 firstInstanceCalendarDateRange:_range] autorelease];
82 - (id)initWithRecurrenceRule:(iCalRecurrenceRule *)_rrule
83 firstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
85 iCalRecurrenceFrequency freq;
86 Class calcClass = Nil;
88 freq = [_rrule frequency];
89 if (freq == iCalRecurrenceFrequenceDaily)
90 calcClass = dailyCalcClass;
91 else if (freq == iCalRecurrenceFrequenceWeekly)
92 calcClass = weeklyCalcClass;
93 else if (freq == iCalRecurrenceFrequenceMonthly)
94 calcClass = monthlyCalcClass;
95 else if (freq == iCalRecurrenceFrequenceYearly)
96 calcClass = yearlyCalcClass;
102 self = [[calcClass alloc] init];
103 ASSIGN(self->rrule, _rrule);
104 ASSIGN(self->firstRange, _range);
111 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
112 return nil; /* subclass responsibility */
114 - (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
117 ranges = [self recurrenceRangesWithinCalendarDateRange:_range];
118 return (ranges == nil || [ranges count] == 0) ? NO : YES;
121 - (NGCalendarDateRange *)firstInstanceCalendarDateRange {
122 return self->firstRange;
125 - (NGCalendarDateRange *)lastInstanceCalendarDateRange {
126 NSCalendarDate *start, *end;
128 start = [self lastInstanceStartDate];
131 end = [start addTimeInterval:[self->firstRange duration]];
132 return [NGCalendarDateRange calendarDateRangeWithStartDate:start
136 - (NSCalendarDate *)lastInstanceStartDate {
137 NSCalendarDate *until;
139 /* NOTE: this is horribly inaccurate and doesn't even consider the use
140 of repeatCount. It MUST be implemented by subclasses properly! However,
141 it does the trick for SOGO 1.0 - that's why it's left here.
143 if ((until = [self->rrule untilDate]) != nil)
148 @end /* iCalRecurrenceCalculator */
151 @implementation iCalDailyRecurrenceCalculator
153 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
154 NSMutableArray *ranges;
155 NSCalendarDate *firStart;
156 long i, jnFirst, jnStart, jnEnd, startEndCount;
159 firStart = [self->firstRange startDate];
160 jnFirst = [firStart julianNumber];
161 jnEnd = [[_r endDate] julianNumber];
166 jnStart = [[_r startDate] julianNumber];
167 interval = [self->rrule repeatInterval];
169 /* if rule is bound, check the bounds */
170 if (![self->rrule isInfinite]) {
171 NSCalendarDate *until;
174 until = [self->rrule untilDate];
176 if ([until compare:[_r startDate]] == NSOrderedAscending)
178 jnRuleLast = [until julianNumber];
181 jnRuleLast = (interval * [self->rrule repeatCount])
183 if (jnRuleLast < jnStart)
186 /* jnStart < jnRuleLast < jnEnd ? */
187 if (jnEnd > jnRuleLast)
191 startEndCount = (jnEnd - jnStart) + 1;
192 ranges = [NSMutableArray arrayWithCapacity:startEndCount];
193 for (i = 0 ; i < startEndCount; i++) {
196 jnCurrent = jnStart + i;
197 if (jnCurrent >= jnFirst) {
200 jnTest = jnCurrent - jnFirst;
201 if ((jnTest % interval) == 0) {
202 NSCalendarDate *start, *end;
203 NGCalendarDateRange *r;
205 start = [NSCalendarDate dateForJulianNumber:jnCurrent];
206 start = [start hour: [firStart hourOfDay]
207 minute:[firStart minuteOfHour]
208 second:[firStart secondOfMinute]];
209 end = [start addTimeInterval:[self->firstRange duration]];
210 r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
212 [ranges addObject:r];
219 - (NSCalendarDate *)lastInstanceStartDate {
220 if ([self->rrule repeatCount] > 0) {
221 long jnFirst, jnRuleLast;
222 NSCalendarDate *firStart, *until;
224 firStart = [self->firstRange startDate];
225 jnFirst = [firStart julianNumber];
226 jnRuleLast = ([self->rrule repeatInterval] *
227 [self->rrule repeatCount]) +
229 until = [NSCalendarDate dateForJulianNumber:jnRuleLast];
230 until = [until hour: [firStart hourOfDay]
231 minute:[firStart minuteOfHour]
232 second:[firStart secondOfMinute]];
235 return [super lastInstanceStartDate];
238 @end /* iCalDailyRecurrenceCalculator */
242 TODO: If BYDAY is specified, lastInstanceStartDate and recurrences will
243 differ significantly!
245 @implementation iCalWeeklyRecurrenceCalculator
247 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
248 NSMutableArray *ranges;
249 NSCalendarDate *firStart;
250 long i, jnFirst, jnStart, jnEnd, startEndCount;
251 unsigned interval, byDayMask;
253 firStart = [self->firstRange startDate];
254 jnFirst = [firStart julianNumber];
255 jnEnd = [[_r endDate] julianNumber];
260 jnStart = [[_r startDate] julianNumber];
261 interval = [self->rrule repeatInterval];
263 /* if rule is bound, check the bounds */
264 if (![self->rrule isInfinite]) {
265 NSCalendarDate *until;
268 until = [self->rrule untilDate];
270 if ([until compare:[_r startDate]] == NSOrderedAscending)
272 jnRuleLast = [until julianNumber];
275 jnRuleLast = (interval * [self->rrule repeatCount] * 7)
277 if (jnRuleLast < jnStart)
280 /* jnStart < jnRuleLast < jnEnd ? */
281 if (jnEnd > jnRuleLast)
285 startEndCount = (jnEnd - jnStart) + 1;
286 ranges = [NSMutableArray arrayWithCapacity:startEndCount];
287 byDayMask = [self->rrule byDayMask];
289 for (i = 0 ; i < startEndCount; i++) {
292 jnCurrent = jnStart + i;
293 if (jnCurrent >= jnFirst) {
296 jnDiff = jnCurrent - jnFirst; /* difference in days */
297 if ((jnDiff % (interval * 7)) == 0) {
298 NSCalendarDate *start, *end;
299 NGCalendarDateRange *r;
301 start = [NSCalendarDate dateForJulianNumber:jnCurrent];
302 start = [start hour: [firStart hourOfDay]
303 minute:[firStart minuteOfHour]
304 second:[firStart secondOfMinute]];
305 end = [start addTimeInterval:[self->firstRange duration]];
306 r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
308 [ranges addObject:r];
314 [self errorWithFormat:@"Cannot calculate rules with byDayMask, yet!"];
319 - (NSCalendarDate *)lastInstanceStartDate {
320 if ([self->rrule repeatCount] > 0) {
321 long jnFirst, jnRuleLast;
322 NSCalendarDate *firStart, *until;
324 firStart = [self->firstRange startDate];
325 jnFirst = [firStart julianNumber];
326 jnRuleLast = ([self->rrule repeatInterval] *
327 [self->rrule repeatCount] * 7) +
329 until = [NSCalendarDate dateForJulianNumber:jnRuleLast];
330 until = [until hour: [firStart hourOfDay]
331 minute:[firStart minuteOfHour]
332 second:[firStart secondOfMinute]];
335 return [super lastInstanceStartDate];
338 @end /* iCalWeeklyRecurrenceCalculator */
340 @implementation iCalMonthlyRecurrenceCalculator
342 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
343 NSMutableArray *ranges;
344 NSCalendarDate *firStart, *rStart, *rEnd, *until;
345 unsigned i, count, interval, diff;
347 firStart = [self->firstRange startDate];
348 rStart = [_r startDate];
350 interval = [self->rrule repeatInterval];
351 until = [self lastInstanceStartDate];
354 if ([until compare:rStart] == NSOrderedAscending)
356 if ([until compare:rEnd] == NSOrderedDescending)
360 diff = [firStart monthsBetweenDate:rStart];
361 count = [rStart monthsBetweenDate:rEnd] + 1;
362 ranges = [NSMutableArray arrayWithCapacity:count];
363 for (i = 0 ; i < count; i++) {
367 if ((test % interval) == 0) {
368 NSCalendarDate *start, *end;
369 NGCalendarDateRange *r;
371 start = [firStart dateByAddingYears:0
374 end = [start addTimeInterval:[self->firstRange duration]];
375 r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
377 [ranges addObject:r];
383 - (NSCalendarDate *)lastInstanceStartDate {
384 if ([self->rrule repeatCount] > 0) {
385 NSCalendarDate *until;
386 unsigned months, interval;
388 interval = [self->rrule repeatInterval];
389 months = [self->rrule repeatCount] * interval;
390 until = [[self->firstRange startDate] dateByAddingYears:0
395 return [super lastInstanceStartDate];
398 @end /* iCalMonthlyRecurrenceCalculator */
400 @implementation iCalYearlyRecurrenceCalculator
402 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
403 NSMutableArray *ranges;
404 NSCalendarDate *firStart, *rStart, *rEnd, *until;
405 unsigned i, count, interval, diff;
407 firStart = [self->firstRange startDate];
408 rStart = [_r startDate];
410 interval = [self->rrule repeatInterval];
411 until = [self lastInstanceStartDate];
414 if ([until compare:rStart] == NSOrderedAscending)
416 if ([until compare:rEnd] == NSOrderedDescending)
420 diff = [firStart yearsBetweenDate:rStart];
421 count = [rStart yearsBetweenDate:rEnd] + 1;
422 ranges = [NSMutableArray arrayWithCapacity:count];
423 for (i = 0 ; i < count; i++) {
427 if ((test % interval) == 0) {
428 NSCalendarDate *start, *end;
429 NGCalendarDateRange *r;
431 start = [firStart dateByAddingYears:diff + i
434 end = [start addTimeInterval:[self->firstRange duration]];
435 r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
437 [ranges addObject:r];
443 - (NSCalendarDate *)lastInstanceStartDate {
444 if ([self->rrule repeatCount] > 0) {
445 NSCalendarDate *until;
446 unsigned years, interval;
448 interval = [self->rrule repeatInterval];
449 years = [self->rrule repeatCount] * interval;
450 until = [[self->firstRange startDate] dateByAddingYears:years
455 return [super lastInstanceStartDate];
458 @end /* iCalYearlyRecurrenceCalculator */