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
36 @interface iCalWeeklyRecurrenceCalculator : iCalDailyRecurrenceCalculator
41 @interface iCalMonthlyRecurrenceCalculator : iCalRecurrenceCalculator
46 @interface iCalYearlyRecurrenceCalculator : iCalRecurrenceCalculator
53 @interface iCalRecurrenceCalculator (PrivateAPI)
54 - (NSCalendarDate *)lastInstanceStartDate;
57 @implementation iCalRecurrenceCalculator
59 static Class dailyCalcClass = Nil;
60 static Class weeklyCalcClass = Nil;
61 static Class monthlyCalcClass = Nil;
62 static Class yearlyCalcClass = Nil;
65 static BOOL didInit = NO;
70 dailyCalcClass = [iCalDailyRecurrenceCalculator class];
71 weeklyCalcClass = [iCalWeeklyRecurrenceCalculator class];
72 monthlyCalcClass = [iCalMonthlyRecurrenceCalculator class];
73 yearlyCalcClass = [iCalYearlyRecurrenceCalculator class];
76 + (id)recurrenceCalculatorForRecurrenceRule:(iCalRecurrenceRule *)_rrule
77 withFirstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
79 return [[[self alloc] initWithRecurrenceRule:_rrule
80 firstInstanceCalendarDateRange:_range] autorelease];
83 - (id)initWithRecurrenceRule:(iCalRecurrenceRule *)_rrule
84 firstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
86 iCalRecurrenceFrequency freq;
87 Class calcClass = Nil;
89 freq = [_rrule frequency];
90 if (freq == iCalRecurrenceFrequenceDaily)
91 calcClass = dailyCalcClass;
92 else if (freq == iCalRecurrenceFrequenceWeekly)
93 calcClass = weeklyCalcClass;
94 else if (freq == iCalRecurrenceFrequenceMonthly)
95 calcClass = monthlyCalcClass;
96 else if (freq == iCalRecurrenceFrequenceYearly)
97 calcClass = yearlyCalcClass;
100 if (calcClass == Nil)
103 self = [[calcClass alloc] init];
104 ASSIGN(self->rrule, _rrule);
105 ASSIGN(self->firstRange, _range);
112 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
113 return nil; /* subclass responsibility */
115 - (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
118 ranges = [self recurrenceRangesWithinCalendarDateRange:_range];
119 return (ranges == nil || [ranges count] == 0) ? NO : YES;
122 - (NGCalendarDateRange *)firstInstanceCalendarDateRange {
123 return self->firstRange;
126 - (NGCalendarDateRange *)lastInstanceCalendarDateRange {
127 NSCalendarDate *start, *end;
129 start = [self lastInstanceStartDate];
132 end = [start addTimeInterval:[self->firstRange duration]];
133 return [NGCalendarDateRange calendarDateRangeWithStartDate:start
137 - (NSCalendarDate *)lastInstanceStartDate {
138 NSCalendarDate *until;
140 /* NOTE: this is horribly inaccurate and doesn't even consider the use
141 of repeatCount. It MUST be implemented by subclasses properly! However,
142 it does the trick for SOGO 1.0 - that's why it's left here.
144 if ((until = [self->rrule untilDate]) != nil)
149 @end /* iCalRecurrenceCalculator */
152 @implementation iCalDailyRecurrenceCalculator
154 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
155 NSMutableArray *ranges;
156 NSCalendarDate *firStart;
157 long i, jnFirst, jnStart, jnEnd, startEndCount;
160 firStart = [self->firstRange startDate];
161 jnFirst = [firStart julianNumber];
162 jnEnd = [[_r endDate] julianNumber];
167 jnStart = [[_r startDate] julianNumber];
168 interval = [self->rrule repeatInterval];
170 /* if rule is bound, check the bounds */
171 if (![self->rrule isInfinite]) {
172 NSCalendarDate *until;
175 until = [self->rrule untilDate];
177 if ([until compare:[_r startDate]] == NSOrderedAscending)
179 jnRuleLast = [until julianNumber];
182 jnRuleLast = (interval * [self->rrule repeatCount] * [self factor])
184 if (jnRuleLast < jnStart)
187 /* jnStart < jnRuleLast < jnEnd ? */
188 if (jnEnd > jnRuleLast)
192 ranges = [NSMutableArray arrayWithCapacity:5];
193 startEndCount = (jnEnd - jnStart) + 1;
194 for (i = 0 ; i < startEndCount; i++) {
197 jnTest = (jnStart + i) - jnFirst;
198 if ((jnTest % interval) == 0) {
199 NSCalendarDate *start, *end;
200 NGCalendarDateRange *r;
202 start = [NSCalendarDate dateForJulianNumber:jnStart + i];
203 start = [start hour: [firStart hourOfDay]
204 minute:[firStart minuteOfHour]
205 second:[firStart secondOfMinute]];
206 end = [start addTimeInterval:[self->firstRange duration]];
207 r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
209 [ranges addObject:r];
216 - (NSCalendarDate *)lastInstanceStartDate {
217 if ([self->rrule repeatCount] > 0) {
218 long jnFirst, jnRuleLast;
219 NSCalendarDate *firStart, *until;
221 firStart = [self->firstRange startDate];
222 jnFirst = [firStart julianNumber];
223 jnRuleLast = ([self->rrule repeatInterval] *
224 [self->rrule repeatCount] *
227 until = [NSCalendarDate dateForJulianNumber:jnRuleLast];
228 until = [until hour: [firStart hourOfDay]
229 minute:[firStart minuteOfHour]
230 second:[firStart secondOfMinute]];
233 return [super lastInstanceStartDate];
240 @end /* iCalDailyRecurrenceCalculator */
244 TODO: If BYDAY is specified, lastInstanceStartDate and recurrences will
245 differ significantly!
247 @implementation iCalWeeklyRecurrenceCalculator
253 @end /* iCalWeeklyRecurrenceCalculator */
255 @implementation iCalMonthlyRecurrenceCalculator
257 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
258 NSMutableArray *ranges;
259 NSCalendarDate *firStart, *rStart, *rEnd, *until;
260 unsigned i, count, interval, diff;
262 firStart = [self->firstRange startDate];
263 rStart = [_r startDate];
265 interval = [self->rrule repeatInterval];
266 until = [self lastInstanceStartDate];
269 if ([until compare:rStart] == NSOrderedAscending)
271 if ([until compare:rEnd] == NSOrderedDescending)
275 diff = [firStart monthsBetweenDate:rStart];
276 count = [rStart monthsBetweenDate:rEnd] + 1;
277 ranges = [NSMutableArray arrayWithCapacity:count];
278 for (i = 0 ; i < count; i++) {
282 if ((test % interval) == 0) {
283 NSCalendarDate *start, *end;
284 NGCalendarDateRange *r;
286 start = [firStart dateByAddingYears:0
289 end = [start addTimeInterval:[self->firstRange duration]];
290 r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
292 [ranges addObject:r];
298 - (NSCalendarDate *)lastInstanceStartDate {
299 if ([self->rrule repeatCount] > 0) {
300 NSCalendarDate *until;
301 unsigned months, interval;
303 interval = [self->rrule repeatInterval];
304 months = [self->rrule repeatCount] * interval;
305 until = [[self->firstRange startDate] dateByAddingYears:0
310 return [super lastInstanceStartDate];
313 @end /* iCalMonthlyRecurrenceCalculator */
315 @implementation iCalYearlyRecurrenceCalculator
317 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
318 NSMutableArray *ranges;
319 NSCalendarDate *firStart, *rStart, *rEnd, *until;
320 unsigned i, count, interval, diff;
322 firStart = [self->firstRange startDate];
323 rStart = [_r startDate];
325 interval = [self->rrule repeatInterval];
326 until = [self lastInstanceStartDate];
329 if ([until compare:rStart] == NSOrderedAscending)
331 if ([until compare:rEnd] == NSOrderedDescending)
335 diff = [firStart yearsBetweenDate:rStart];
336 count = [rStart yearsBetweenDate:rEnd] + 1;
337 ranges = [NSMutableArray arrayWithCapacity:count];
338 for (i = 0 ; i < count; i++) {
342 if ((test % interval) == 0) {
343 NSCalendarDate *start, *end;
344 NGCalendarDateRange *r;
346 start = [firStart dateByAddingYears:diff + i
349 end = [start addTimeInterval:[self->firstRange duration]];
350 r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
352 [ranges addObject:r];
358 - (NSCalendarDate *)lastInstanceStartDate {
359 if ([self->rrule repeatCount] > 0) {
360 NSCalendarDate *until;
361 unsigned years, interval;
363 interval = [self->rrule repeatInterval];
364 years = [self->rrule repeatCount] * interval;
365 until = [[self->firstRange startDate] dateByAddingYears:years
370 return [super lastInstanceStartDate];
373 @end /* iCalYearlyRecurrenceCalculator */