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;
55 - (unsigned)offsetFromSundayForJulianNumber:(long)_jn;
56 - (unsigned)offsetFromSundayForWeekDay:(iCalWeekDay)_weekDay;
57 - (unsigned)offsetFromSundayForCurrentWeekStart;
59 - (iCalWeekDay)weekDayForJulianNumber:(long)_jn;
62 @implementation iCalRecurrenceCalculator
64 static Class dailyCalcClass = Nil;
65 static Class weeklyCalcClass = Nil;
66 static Class monthlyCalcClass = Nil;
67 static Class yearlyCalcClass = Nil;
70 static BOOL didInit = NO;
75 dailyCalcClass = [iCalDailyRecurrenceCalculator class];
76 weeklyCalcClass = [iCalWeeklyRecurrenceCalculator class];
77 monthlyCalcClass = [iCalMonthlyRecurrenceCalculator class];
78 yearlyCalcClass = [iCalYearlyRecurrenceCalculator class];
81 + (id)recurrenceCalculatorForRecurrenceRule:(iCalRecurrenceRule *)_rrule
82 withFirstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
84 return [[[self alloc] initWithRecurrenceRule:_rrule
85 firstInstanceCalendarDateRange:_range] autorelease];
88 - (id)initWithRecurrenceRule:(iCalRecurrenceRule *)_rrule
89 firstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
91 iCalRecurrenceFrequency freq;
92 Class calcClass = Nil;
94 freq = [_rrule frequency];
95 if (freq == iCalRecurrenceFrequenceDaily)
96 calcClass = dailyCalcClass;
97 else if (freq == iCalRecurrenceFrequenceWeekly)
98 calcClass = weeklyCalcClass;
99 else if (freq == iCalRecurrenceFrequenceMonthly)
100 calcClass = monthlyCalcClass;
101 else if (freq == iCalRecurrenceFrequenceYearly)
102 calcClass = yearlyCalcClass;
105 if (calcClass == Nil)
108 self = [[calcClass alloc] init];
109 ASSIGN(self->rrule, _rrule);
110 ASSIGN(self->firstRange, _range);
116 - (unsigned)offsetFromSundayForJulianNumber:(long)_jn {
117 return (unsigned)((int)(_jn + 1.5)) % 7;
120 - (unsigned)offsetFromSundayForWeekDay:(iCalWeekDay)_weekDay {
124 case iCalWeekDaySunday: offset = 0; break;
125 case iCalWeekDayMonday: offset = 1; break;
126 case iCalWeekDayTuesday: offset = 2; break;
127 case iCalWeekDayWednesday: offset = 3; break;
128 case iCalWeekDayThursday: offset = 4; break;
129 case iCalWeekDayFriday: offset = 5; break;
130 case iCalWeekDaySaturday: offset = 6; break;
131 default: offset = 0; break;
136 - (unsigned)offsetFromSundayForCurrentWeekStart {
137 return [self offsetFromSundayForWeekDay:[self->rrule weekStart]];
140 - (iCalWeekDay)weekDayForJulianNumber:(long)_jn {
144 day = [self offsetFromSundayForJulianNumber:_jn];
146 case 0: weekDay = iCalWeekDaySunday; break;
147 case 1: weekDay = iCalWeekDayMonday; break;
148 case 2: weekDay = iCalWeekDayTuesday; break;
149 case 3: weekDay = iCalWeekDayWednesday; break;
150 case 4: weekDay = iCalWeekDayThursday; break;
151 case 5: weekDay = iCalWeekDayFriday; break;
152 case 6: weekDay = iCalWeekDaySaturday; break;
153 default: weekDay = iCalWeekDaySunday; break; /* keep compiler happy */
160 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
161 return nil; /* subclass responsibility */
163 - (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
166 ranges = [self recurrenceRangesWithinCalendarDateRange:_range];
167 return (ranges == nil || [ranges count] == 0) ? NO : YES;
170 - (NGCalendarDateRange *)firstInstanceCalendarDateRange {
171 return self->firstRange;
174 - (NGCalendarDateRange *)lastInstanceCalendarDateRange {
175 NSCalendarDate *start, *end;
177 start = [self lastInstanceStartDate];
180 end = [start addTimeInterval:[self->firstRange duration]];
181 return [NGCalendarDateRange calendarDateRangeWithStartDate:start
185 - (NSCalendarDate *)lastInstanceStartDate {
186 NSCalendarDate *until;
188 /* NOTE: this is horribly inaccurate and doesn't even consider the use
189 of repeatCount. It MUST be implemented by subclasses properly! However,
190 it does the trick for SOGO 1.0 - that's why it's left here.
192 if ((until = [self->rrule untilDate]) != nil)
197 @end /* iCalRecurrenceCalculator */
200 @implementation iCalDailyRecurrenceCalculator
202 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
203 NSMutableArray *ranges;
204 NSCalendarDate *firStart;
205 long i, jnFirst, jnStart, jnEnd, startEndCount;
208 firStart = [self->firstRange startDate];
209 jnFirst = [firStart julianNumber];
210 jnEnd = [[_r endDate] julianNumber];
215 jnStart = [[_r startDate] julianNumber];
216 interval = [self->rrule repeatInterval];
218 /* if rule is bound, check the bounds */
219 if (![self->rrule isInfinite]) {
220 NSCalendarDate *until;
223 until = [self->rrule untilDate];
225 if ([until compare:[_r startDate]] == NSOrderedAscending)
227 jnRuleLast = [until julianNumber];
230 jnRuleLast = (interval * [self->rrule repeatCount])
232 if (jnRuleLast < jnStart)
235 /* jnStart < jnRuleLast < jnEnd ? */
236 if (jnEnd > jnRuleLast)
240 startEndCount = (jnEnd - jnStart) + 1;
241 ranges = [NSMutableArray arrayWithCapacity:startEndCount];
242 for (i = 0 ; i < startEndCount; i++) {
245 jnCurrent = jnStart + i;
246 if (jnCurrent >= jnFirst) {
249 jnTest = jnCurrent - jnFirst;
250 if ((jnTest % interval) == 0) {
251 NSCalendarDate *start, *end;
252 NGCalendarDateRange *r;
254 start = [NSCalendarDate dateForJulianNumber:jnCurrent];
255 start = [start hour: [firStart hourOfDay]
256 minute:[firStart minuteOfHour]
257 second:[firStart secondOfMinute]];
258 end = [start addTimeInterval:[self->firstRange duration]];
259 r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
261 [ranges addObject:r];
268 - (NSCalendarDate *)lastInstanceStartDate {
269 if ([self->rrule repeatCount] > 0) {
270 long jnFirst, jnRuleLast;
271 NSCalendarDate *firStart, *until;
273 firStart = [self->firstRange startDate];
274 jnFirst = [firStart julianNumber];
275 jnRuleLast = ([self->rrule repeatInterval] *
276 [self->rrule repeatCount]) +
278 until = [NSCalendarDate dateForJulianNumber:jnRuleLast];
279 until = [until hour: [firStart hourOfDay]
280 minute:[firStart minuteOfHour]
281 second:[firStart secondOfMinute]];
284 return [super lastInstanceStartDate];
287 @end /* iCalDailyRecurrenceCalculator */
291 TODO: If BYDAY is specified, lastInstanceStartDate and recurrences will
292 differ significantly!
294 @implementation iCalWeeklyRecurrenceCalculator
296 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
297 NSMutableArray *ranges;
298 NSCalendarDate *firStart;
299 long i, jnFirst, jnStart, jnEnd, startEndCount;
300 unsigned interval, byDayMask;
302 firStart = [self->firstRange startDate];
303 jnFirst = [firStart julianNumber];
304 jnEnd = [[_r endDate] julianNumber];
309 jnStart = [[_r startDate] julianNumber];
310 interval = [self->rrule repeatInterval];
312 /* if rule is bound, check the bounds */
313 if (![self->rrule isInfinite]) {
314 NSCalendarDate *until;
317 until = [self->rrule untilDate];
319 if ([until compare:[_r startDate]] == NSOrderedAscending)
321 jnRuleLast = [until julianNumber];
324 jnRuleLast = (interval * [self->rrule repeatCount] * 7)
326 if (jnRuleLast < jnStart)
329 /* jnStart < jnRuleLast < jnEnd ? */
330 if (jnEnd > jnRuleLast)
334 startEndCount = (jnEnd - jnStart) + 1;
335 ranges = [NSMutableArray arrayWithCapacity:startEndCount];
336 byDayMask = [self->rrule byDayMask];
338 for (i = 0 ; i < startEndCount; i++) {
341 jnCurrent = jnStart + i;
342 if (jnCurrent >= jnFirst) {
345 jnDiff = jnCurrent - jnFirst; /* difference in days */
346 if ((jnDiff % (interval * 7)) == 0) {
347 NSCalendarDate *start, *end;
348 NGCalendarDateRange *r;
350 start = [NSCalendarDate dateForJulianNumber:jnCurrent];
351 start = [start hour: [firStart hourOfDay]
352 minute:[firStart minuteOfHour]
353 second:[firStart secondOfMinute]];
354 end = [start addTimeInterval:[self->firstRange duration]];
355 r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
357 [ranges addObject:r];
363 long jnFirstWeekStart, weekStartOffset;
365 /* calculate jnFirst's week start - this depends on our setting of week
367 weekStartOffset = [self offsetFromSundayForJulianNumber:jnFirst] -
368 [self offsetFromSundayForCurrentWeekStart];
370 jnFirstWeekStart = jnFirst - weekStartOffset;
372 for (i = 0 ; i < startEndCount; i++) {
375 jnCurrent = jnStart + i;
376 if (jnCurrent >= jnFirst) {
379 /* we need to calculate a difference in weeks */
380 jnDiff = (jnCurrent - jnFirstWeekStart) % 7;
381 if ((jnDiff % interval) == 0) {
382 BOOL isRecurrence = NO;
384 if (jnCurrent == jnFirst) {
390 weekDay = [self weekDayForJulianNumber:jnCurrent];
391 isRecurrence = (weekDay & [self->rrule byDayMask]) ? YES : NO;
394 NSCalendarDate *start, *end;
395 NGCalendarDateRange *r;
397 start = [NSCalendarDate dateForJulianNumber:jnCurrent];
398 start = [start hour: [firStart hourOfDay]
399 minute:[firStart minuteOfHour]
400 second:[firStart secondOfMinute]];
401 end = [start addTimeInterval:[self->firstRange duration]];
402 r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
404 [ranges addObject:r];
413 - (NSCalendarDate *)lastInstanceStartDate {
414 if ([self->rrule repeatCount] > 0) {
415 long jnFirst, jnRuleLast;
416 NSCalendarDate *firStart, *until;
418 firStart = [self->firstRange startDate];
419 jnFirst = [firStart julianNumber];
420 jnRuleLast = ([self->rrule repeatInterval] *
421 [self->rrule repeatCount] * 7) +
423 until = [NSCalendarDate dateForJulianNumber:jnRuleLast];
424 until = [until hour: [firStart hourOfDay]
425 minute:[firStart minuteOfHour]
426 second:[firStart secondOfMinute]];
429 return [super lastInstanceStartDate];
432 @end /* iCalWeeklyRecurrenceCalculator */
434 @implementation iCalMonthlyRecurrenceCalculator
436 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
437 NSMutableArray *ranges;
438 NSCalendarDate *firStart, *rStart, *rEnd, *until;
439 unsigned i, count, interval, diff;
441 firStart = [self->firstRange startDate];
442 rStart = [_r startDate];
444 interval = [self->rrule repeatInterval];
445 until = [self lastInstanceStartDate];
448 if ([until compare:rStart] == NSOrderedAscending)
450 if ([until compare:rEnd] == NSOrderedDescending)
454 diff = [firStart monthsBetweenDate:rStart];
455 count = [rStart monthsBetweenDate:rEnd] + 1;
456 ranges = [NSMutableArray arrayWithCapacity:count];
457 for (i = 0 ; i < count; i++) {
461 if ((test % interval) == 0) {
462 NSCalendarDate *start, *end;
463 NGCalendarDateRange *r;
465 start = [firStart dateByAddingYears:0
468 end = [start addTimeInterval:[self->firstRange duration]];
469 r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
471 [ranges addObject:r];
477 - (NSCalendarDate *)lastInstanceStartDate {
478 if ([self->rrule repeatCount] > 0) {
479 NSCalendarDate *until;
480 unsigned months, interval;
482 interval = [self->rrule repeatInterval];
483 months = [self->rrule repeatCount] * interval;
484 until = [[self->firstRange startDate] dateByAddingYears:0
489 return [super lastInstanceStartDate];
492 @end /* iCalMonthlyRecurrenceCalculator */
494 @implementation iCalYearlyRecurrenceCalculator
496 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
497 NSMutableArray *ranges;
498 NSCalendarDate *firStart, *rStart, *rEnd, *until;
499 unsigned i, count, interval, diff;
501 firStart = [self->firstRange startDate];
502 rStart = [_r startDate];
504 interval = [self->rrule repeatInterval];
505 until = [self lastInstanceStartDate];
508 if ([until compare:rStart] == NSOrderedAscending)
510 if ([until compare:rEnd] == NSOrderedDescending)
514 diff = [firStart yearsBetweenDate:rStart];
515 count = [rStart yearsBetweenDate:rEnd] + 1;
516 ranges = [NSMutableArray arrayWithCapacity:count];
517 for (i = 0 ; i < count; i++) {
521 if ((test % interval) == 0) {
522 NSCalendarDate *start, *end;
523 NGCalendarDateRange *r;
525 start = [firStart dateByAddingYears:diff + i
528 end = [start addTimeInterval:[self->firstRange duration]];
529 r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
531 [ranges addObject:r];
537 - (NSCalendarDate *)lastInstanceStartDate {
538 if ([self->rrule repeatCount] > 0) {
539 NSCalendarDate *until;
540 unsigned years, interval;
542 interval = [self->rrule repeatInterval];
543 years = [self->rrule repeatCount] * interval;
544 until = [[self->firstRange startDate] dateByAddingYears:years
549 return [super lastInstanceStartDate];
552 @end /* iCalYearlyRecurrenceCalculator */