]> err.no Git - sope/blob - sope-ical/NGiCal/iCalRecurrenceCalculator.m
additions
[sope] / sope-ical / NGiCal / iCalRecurrenceCalculator.m
1 /*
2  Copyright (C) 2004-2005 SKYRIX Software AG
3  
4  This file is part of SOPE.
5  
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
9  later version.
10  
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.
15  
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
19  02111-1307, USA.
20 */
21
22 #include "iCalRecurrenceCalculator.h"
23 #include <NGExtensions/NGCalendarDateRange.h>
24 #include "iCalRecurrenceRule.h"
25 #include "NSCalendarDate+ICal.h"
26 #include "common.h"
27
28 /* class cluster */
29
30 @interface iCalDailyRecurrenceCalculator : iCalRecurrenceCalculator
31 {
32 }
33 - (unsigned)factor;
34 @end
35
36 @interface iCalWeeklyRecurrenceCalculator : iCalDailyRecurrenceCalculator
37 {
38 }
39 @end
40
41 @interface iCalMonthlyRecurrenceCalculator : iCalRecurrenceCalculator
42 {
43 }
44 @end
45
46 @interface iCalYearlyRecurrenceCalculator : iCalRecurrenceCalculator
47 {
48 }
49 @end
50
51 /* Private */
52
53 @interface iCalRecurrenceCalculator (PrivateAPI)
54 - (NSCalendarDate *)lastInstanceStartDate;
55 @end
56
57 @implementation iCalRecurrenceCalculator
58
59 static Class dailyCalcClass   = Nil;
60 static Class weeklyCalcClass  = Nil;
61 static Class monthlyCalcClass = Nil;
62 static Class yearlyCalcClass  = Nil;
63
64 + (void)initialize {
65   static BOOL didInit = NO;
66   
67   if (didInit) return;
68   didInit = YES;
69
70   dailyCalcClass   = [iCalDailyRecurrenceCalculator   class];
71   weeklyCalcClass  = [iCalWeeklyRecurrenceCalculator  class];
72   monthlyCalcClass = [iCalMonthlyRecurrenceCalculator class];
73   yearlyCalcClass  = [iCalYearlyRecurrenceCalculator  class];
74 }
75
76 + (id)recurrenceCalculatorForRecurrenceRule:(iCalRecurrenceRule *)_rrule
77          withFirstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
78 {
79   return [[[self alloc] initWithRecurrenceRule:_rrule
80                         firstInstanceCalendarDateRange:_range] autorelease];
81 }
82
83 - (id)initWithRecurrenceRule:(iCalRecurrenceRule *)_rrule
84   firstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
85 {
86   iCalRecurrenceFrequency freq;
87   Class calcClass = Nil;
88
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;
98
99   [self autorelease];
100   if (calcClass == Nil)
101     return nil;
102
103   self = [[calcClass alloc] init];
104   ASSIGN(self->rrule, _rrule);
105   ASSIGN(self->firstRange, _range);
106   return self;  
107 }
108
109
110 /* calculation */
111
112 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
113   return nil; /* subclass responsibility */
114 }
115 - (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
116   NSArray *ranges;
117
118   ranges = [self recurrenceRangesWithinCalendarDateRange:_range];
119   return (ranges == nil || [ranges count] == 0) ? NO : YES;
120 }
121
122 - (NGCalendarDateRange *)firstInstanceCalendarDateRange {
123   return self->firstRange;
124 }
125
126 - (NGCalendarDateRange *)lastInstanceCalendarDateRange {
127   NSCalendarDate *start, *end;
128
129   start = [self lastInstanceStartDate];
130   if (!start)
131     return nil;
132   end   = [start addTimeInterval:[self->firstRange duration]];
133   return [NGCalendarDateRange calendarDateRangeWithStartDate:start
134                               endDate:end];
135 }
136
137 - (NSCalendarDate *)lastInstanceStartDate {
138   NSCalendarDate *until;
139   
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.
143   */
144   if ((until = [self->rrule untilDate]) != nil)
145     return until;
146   return nil;
147 }
148
149 @end /* iCalRecurrenceCalculator */
150
151
152 @implementation iCalDailyRecurrenceCalculator
153
154 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
155   NSMutableArray *ranges;
156   NSCalendarDate *firStart;
157   long           i, jnFirst, jnStart, jnEnd, startEndCount;
158   unsigned       interval;
159
160   firStart = [self->firstRange startDate];
161   jnFirst  = [firStart julianNumber];
162   jnEnd    = [[_r endDate] julianNumber];
163   
164   if (jnFirst > jnEnd)
165     return nil;
166   
167   jnStart  = [[_r startDate] julianNumber];
168   interval = [self->rrule repeatInterval];
169   
170   /* if rule is bound, check the bounds */
171   if (![self->rrule isInfinite]) {
172     NSCalendarDate *until;
173     long           jnRuleLast;
174     
175     until = [self->rrule untilDate];
176     if (until) {
177       if ([until compare:[_r startDate]] == NSOrderedAscending)
178         return nil;
179       jnRuleLast = [until julianNumber];
180     }
181     else {
182       jnRuleLast = (interval * [self->rrule repeatCount] * [self factor])
183       + jnFirst;
184       if (jnRuleLast < jnStart)
185         return nil;
186     }
187     /* jnStart < jnRuleLast < jnEnd ? */
188     if (jnEnd > jnRuleLast)
189       jnEnd = jnRuleLast;
190   }
191
192   ranges        = [NSMutableArray arrayWithCapacity:5];
193   startEndCount = (jnEnd - jnStart) + 1;
194   for (i = 0 ; i < startEndCount; i++) {
195     long jnTest;
196
197     jnTest = (jnStart + i) - jnFirst;
198     if ((jnTest % interval) == 0) {
199       NSCalendarDate      *start, *end;
200       NGCalendarDateRange *r;
201     
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
208                                    endDate:end];
209       [ranges addObject:r];
210     }
211   }
212   
213   return ranges;
214 }
215
216 - (NSCalendarDate *)lastInstanceStartDate {
217   if ([self->rrule repeatCount] > 0) {
218     long           jnFirst, jnRuleLast;
219     NSCalendarDate *firStart, *until;
220
221     firStart   = [self->firstRange startDate];
222     jnFirst    = [firStart julianNumber];
223     jnRuleLast = ([self->rrule repeatInterval] *
224                   [self->rrule repeatCount] *
225                   [self factor]) +
226                   jnFirst;
227     until      = [NSCalendarDate dateForJulianNumber:jnRuleLast];
228     until      = [until hour:  [firStart hourOfDay]
229                         minute:[firStart minuteOfHour]
230                         second:[firStart secondOfMinute]];
231     return until;
232   }
233   return [super lastInstanceStartDate];
234 }
235
236 - (unsigned)factor {
237   return 1;
238 }
239
240 @end /* iCalDailyRecurrenceCalculator */
241
242
243 /*
244    TODO: If BYDAY is specified, lastInstanceStartDate and recurrences will
245          differ significantly!
246 */
247 @implementation iCalWeeklyRecurrenceCalculator
248
249 - (unsigned)factor {
250   return 7;
251 }
252
253 @end /* iCalWeeklyRecurrenceCalculator */
254
255 @implementation iCalMonthlyRecurrenceCalculator
256
257 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
258   NSMutableArray *ranges;
259   NSCalendarDate *firStart, *rStart, *rEnd, *until;
260   unsigned       i, count, interval, diff;
261
262   firStart = [self->firstRange startDate];
263   rStart   = [_r startDate];
264   rEnd     = [_r endDate];
265   interval = [self->rrule repeatInterval];
266   until    = [self lastInstanceStartDate];
267
268   if (until) {
269     if ([until compare:rStart] == NSOrderedAscending)
270       return nil;
271     if ([until compare:rEnd] == NSOrderedDescending)
272       rEnd = until;
273   }
274
275   diff   = [firStart monthsBetweenDate:rStart];
276   count  = [rStart monthsBetweenDate:rEnd] + 1;
277   ranges = [NSMutableArray arrayWithCapacity:count];
278   for (i = 0 ; i < count; i++) {
279     unsigned test;
280     
281     test = diff + i;
282     if ((test % interval) == 0) {
283       NSCalendarDate      *start, *end;
284       NGCalendarDateRange *r;
285       
286       start = [firStart dateByAddingYears:0
287                         months:diff + i
288                         days:0];
289       end   = [start addTimeInterval:[self->firstRange duration]];
290       r     = [NGCalendarDateRange calendarDateRangeWithStartDate:start
291                                    endDate:end];
292       [ranges addObject:r];
293     }
294   }
295   return ranges;
296 }
297
298 - (NSCalendarDate *)lastInstanceStartDate {
299   if ([self->rrule repeatCount] > 0) {
300     NSCalendarDate *until;
301     unsigned       months, interval;
302
303     interval = [self->rrule repeatInterval];
304     months   = [self->rrule repeatCount] * interval;
305     until    = [[self->firstRange startDate] dateByAddingYears:0
306                                              months:months
307                                              days:0];
308     return until;
309   }
310   return [super lastInstanceStartDate];
311 }
312
313 @end /* iCalMonthlyRecurrenceCalculator */
314
315 @implementation iCalYearlyRecurrenceCalculator
316
317 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
318   NSMutableArray *ranges;
319   NSCalendarDate *firStart, *rStart, *rEnd, *until;
320   unsigned       i, count, interval, diff;
321   
322   firStart = [self->firstRange startDate];
323   rStart   = [_r startDate];
324   rEnd     = [_r endDate];
325   interval = [self->rrule repeatInterval];
326   until    = [self lastInstanceStartDate];
327   
328   if (until) {
329     if ([until compare:rStart] == NSOrderedAscending)
330       return nil;
331     if ([until compare:rEnd] == NSOrderedDescending)
332       rEnd = until;
333   }
334   
335   diff   = [firStart yearsBetweenDate:rStart];
336   count  = [rStart yearsBetweenDate:rEnd] + 1;
337   ranges = [NSMutableArray arrayWithCapacity:count];
338   for (i = 0 ; i < count; i++) {
339     unsigned test;
340     
341     test = diff + i;
342     if ((test % interval) == 0) {
343       NSCalendarDate      *start, *end;
344       NGCalendarDateRange *r;
345       
346       start = [firStart dateByAddingYears:diff + i
347                         months:0
348                         days:0];
349       end   = [start addTimeInterval:[self->firstRange duration]];
350       r     = [NGCalendarDateRange calendarDateRangeWithStartDate:start
351                                    endDate:end];
352       [ranges addObject:r];
353     }
354   }
355   return ranges;
356 }
357
358 - (NSCalendarDate *)lastInstanceStartDate {
359   if ([self->rrule repeatCount] > 0) {
360     NSCalendarDate *until;
361     unsigned       years, interval;
362     
363     interval = [self->rrule repeatInterval];
364     years    = [self->rrule repeatCount] * interval;
365     until    = [[self->firstRange startDate] dateByAddingYears:years
366                                              months:0
367                                              days:0];
368     return until;
369   }
370   return [super lastInstanceStartDate];
371 }
372
373 @end /* iCalYearlyRecurrenceCalculator */