]> err.no Git - sope/blob - sope-ical/NGiCal/iCalRecurrenceCalculator.m
added a description
[sope] / sope-ical / NGiCal / iCalRecurrenceCalculator.m
1 /*
2   Copyright (C) 2004-2007 SKYRIX Software AG
3   Copyright (C) 2007      Helge Hess
4   
5   This file is part of SOPE.
6   
7   SOPE is free software; you can redistribute it and/or modify it under
8   the terms of the GNU Lesser General Public License as published by the
9   Free Software Foundation; either version 2, or (at your option) any
10   later version.
11   
12   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
13   WARRANTY; without even the implied warranty of MERCHANTABILITY or
14   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
15   License for more details.
16   
17   You should have received a copy of the GNU Lesser General Public
18   License along with SOPE; see the file COPYING.  If not, write to the
19   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
20   02111-1307, USA.
21 */
22
23 #include "iCalRecurrenceCalculator.h"
24 #include <NGExtensions/NGCalendarDateRange.h>
25 #include "iCalRecurrenceRule.h"
26 #include "NSCalendarDate+ICal.h"
27 #include "common.h"
28
29 /* class cluster */
30
31
32 /* Private */
33
34 @interface iCalRecurrenceCalculator (PrivateAPI)
35 - (NSCalendarDate *)lastInstanceStartDate;
36
37 - (unsigned)offsetFromSundayForJulianNumber:(long)_jn;
38 - (unsigned)offsetFromSundayForWeekDay:(iCalWeekDay)_weekDay;
39 - (unsigned)offsetFromSundayForCurrentWeekStart;
40   
41 - (iCalWeekDay)weekDayForJulianNumber:(long)_jn;
42 @end
43
44 @implementation iCalRecurrenceCalculator
45
46 static Class NSCalendarDateClass     = Nil;
47 static Class iCalRecurrenceRuleClass = Nil;
48 static Class dailyCalcClass   = Nil;
49 static Class weeklyCalcClass  = Nil;
50 static Class monthlyCalcClass = Nil;
51 static Class yearlyCalcClass  = Nil;
52
53 + (void)initialize {
54   static BOOL didInit = NO;
55   
56   if (didInit) return;
57   didInit = YES;
58
59   NSCalendarDateClass     = [NSCalendarDate class];
60   iCalRecurrenceRuleClass = [iCalRecurrenceRule class];
61
62   dailyCalcClass   = NSClassFromString(@"iCalDailyRecurrenceCalculator");
63   weeklyCalcClass  = NSClassFromString(@"iCalWeeklyRecurrenceCalculator");
64   monthlyCalcClass = NSClassFromString(@"iCalMonthlyRecurrenceCalculator");
65   yearlyCalcClass  = NSClassFromString(@"iCalYearlyRecurrenceCalculator");
66 }
67
68 /* factory */
69
70 + (id)recurrenceCalculatorForRecurrenceRule:(iCalRecurrenceRule *)_rrule
71   withFirstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
72 {
73   return [[[self alloc] initWithRecurrenceRule:_rrule
74                         firstInstanceCalendarDateRange:_range] autorelease];
75 }
76
77 /* complex calculation convenience */
78
79 + (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r
80   firstInstanceCalendarDateRange:(NGCalendarDateRange *)_fir
81   recurrenceRules:(NSArray *)_rRules
82   exceptionRules:(NSArray *)_exRules
83   exceptionDates:(NSArray *)_exDates
84 {
85   id                       rule;
86   iCalRecurrenceCalculator *calc;
87   NSMutableArray           *ranges;
88   NSMutableArray           *exDates;
89   unsigned                 i, count, rCount;
90   
91   ranges = [NSMutableArray arrayWithCapacity:64];
92   
93   for (i = 0, count  = [_rRules count]; i < count; i++) {
94     NSArray *rs;
95
96     rule = [_rRules objectAtIndex:i];
97     if (![rule isKindOfClass:iCalRecurrenceRuleClass])
98       rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:rule];
99   
100     calc = [self recurrenceCalculatorForRecurrenceRule:rule
101                  withFirstInstanceCalendarDateRange:_fir];
102     rs   = [calc recurrenceRangesWithinCalendarDateRange:_r];
103     [ranges addObjectsFromArray:rs];
104   }
105   
106   if ([ranges count] == 0)
107     return nil;
108   
109   /* test if any exceptions do match */
110   
111   for (i = 0, count = [_exRules count]; i < count; i++) {
112     NSArray *rs;
113
114     rule = [_exRules objectAtIndex:i];
115     if (![rule isKindOfClass:iCalRecurrenceRuleClass])
116       rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:rule];
117
118     calc = [self recurrenceCalculatorForRecurrenceRule:rule
119                  withFirstInstanceCalendarDateRange:_fir];
120     rs   = [calc recurrenceRangesWithinCalendarDateRange:_r];
121     [ranges removeObjectsInArray:rs];
122   }
123   
124   if (![ranges isNotEmpty])
125     return nil;
126   
127   /* exception dates */
128
129   if ((count = [_exDates count]) == 0)
130     return ranges;
131   
132   /* sort out exDates not within range */
133
134   exDates = [NSMutableArray arrayWithCapacity:count];
135   for (i = 0; i < count; i++) {
136     id exDate;
137
138     exDate = [_exDates objectAtIndex:i];
139     if (![exDate isKindOfClass:NSCalendarDateClass])
140       exDate = [NSCalendarDate calendarDateWithICalRepresentation:exDate];
141     
142     if ([_r containsDate:exDate])
143       [exDates addObject:exDate];
144   }
145
146   /* remove matching exDates from ranges */
147
148   if ((count = [exDates count]) == 0)
149     return ranges;
150   
151   for (i = 0, rCount = [ranges count]; i < count; i++) {
152     NSCalendarDate      *exDate;
153     NGCalendarDateRange *r;
154     unsigned            k;
155
156     exDate = [exDates objectAtIndex:i];
157     for (k = 0; k < rCount; k++) {
158       unsigned rIdx;
159       
160       rIdx = (rCount - k) - 1;
161       r    = [ranges objectAtIndex:rIdx];
162       if ([r containsDate:exDate]) {
163         [ranges removeObjectAtIndex:rIdx];
164         rCount--;
165         break; /* this is safe because we know that ranges don't overlap */
166       }
167     }
168   }
169   return ranges;
170 }
171
172
173 /* init */
174
175 - (id)initWithRecurrenceRule:(iCalRecurrenceRule *)_rrule
176   firstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
177 {
178   iCalRecurrenceFrequency freq;
179   Class calcClass = Nil;
180
181   freq = [_rrule frequency];
182   if (freq == iCalRecurrenceFrequenceDaily)
183     calcClass = dailyCalcClass;
184   else if (freq == iCalRecurrenceFrequenceWeekly)
185     calcClass = weeklyCalcClass;
186   else if (freq == iCalRecurrenceFrequenceMonthly)
187     calcClass = monthlyCalcClass;
188   else if (freq == iCalRecurrenceFrequenceYearly)
189     calcClass = yearlyCalcClass;
190   else {
191     [self errorWithFormat:@"unsupported rrule frequency: %@", _rrule];
192     calcClass = Nil;
193     [self release];
194     return nil;
195   }
196   
197   [self autorelease]; // TODO: why autorelease?
198   if (calcClass == Nil)
199     return nil;
200   
201   if ((self = [[calcClass alloc] init]) != nil) {
202     self->rrule      = [_rrule retain];
203     self->firstRange = [_range retain];
204   }
205   return self;  
206 }
207
208 - (void)dealloc {
209   [self->firstRange release];
210   [self->rrule      release];
211   [super dealloc];
212 }
213
214 /* helpers */
215
216 - (unsigned)offsetFromSundayForJulianNumber:(long)_jn {
217   return (unsigned)((int)(_jn + 1.5)) % 7;
218 }
219
220 - (unsigned)offsetFromSundayForWeekDay:(iCalWeekDay)_weekDay {
221   unsigned offset;
222   
223   switch (_weekDay) {
224     case iCalWeekDaySunday:    offset = 0; break;
225     case iCalWeekDayMonday:    offset = 1; break;
226     case iCalWeekDayTuesday:   offset = 2; break;
227     case iCalWeekDayWednesday: offset = 3; break;
228     case iCalWeekDayThursday:  offset = 4; break;
229     case iCalWeekDayFriday:    offset = 5; break;
230     case iCalWeekDaySaturday:  offset = 6; break;
231     default:                   offset = 0; break;
232   }
233   return offset;
234 }
235
236 - (unsigned)offsetFromSundayForCurrentWeekStart {
237   return [self offsetFromSundayForWeekDay:[self->rrule weekStart]];
238 }
239
240 - (iCalWeekDay)weekDayForJulianNumber:(long)_jn {
241   unsigned    day;
242   iCalWeekDay weekDay;
243
244   day = [self offsetFromSundayForJulianNumber:_jn];
245   switch (day) {
246     case 0:  weekDay = iCalWeekDaySunday;    break;
247     case 1:  weekDay = iCalWeekDayMonday;    break;
248     case 2:  weekDay = iCalWeekDayTuesday;   break;
249     case 3:  weekDay = iCalWeekDayWednesday; break;
250     case 4:  weekDay = iCalWeekDayThursday;  break;
251     case 5:  weekDay = iCalWeekDayFriday;    break;
252     case 6:  weekDay = iCalWeekDaySaturday;  break;
253     default: 
254       [self errorWithFormat:@"got unexpected weekday: %d", day];
255       weekDay = iCalWeekDaySunday;
256       break; /* keep compiler happy */
257   }
258   return weekDay;
259 }
260
261 /* calculation */
262
263 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r{
264   return nil; /* subclass responsibility */
265 }
266 - (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
267   NSArray *ranges;
268
269   ranges = [self recurrenceRangesWithinCalendarDateRange:_range];
270   return (ranges == nil || [ranges count] == 0) ? NO : YES;
271 }
272
273 - (NGCalendarDateRange *)firstInstanceCalendarDateRange {
274   return self->firstRange;
275 }
276
277 - (NGCalendarDateRange *)lastInstanceCalendarDateRange {
278   NSCalendarDate *start, *end;
279
280   if ((start = [self lastInstanceStartDate]) == nil)
281     return nil;
282   
283   end   = [start addTimeInterval:[self->firstRange duration]];
284   return [NGCalendarDateRange calendarDateRangeWithStartDate:start
285                               endDate:end];
286 }
287
288 - (NSCalendarDate *)lastInstanceStartDate {
289   NSCalendarDate *until;
290   
291   /* 
292      NOTE: this is horribly inaccurate and doesn't even consider the use
293            of repeatCount. It MUST be implemented by subclasses properly!
294            However, it does the trick for SOGo 1.0 - that's why it's left here.
295   */
296   if ((until = [self->rrule untilDate]) != nil)
297     return until;
298   
299   return nil;
300 }
301
302
303 /* descriptions */
304
305 - (NSString *)description {
306   NSMutableString *ms;
307
308   ms = [NSMutableString stringWithCapacity:128];
309   [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
310
311   if (self->rrule != nil)
312     [ms appendFormat:@" rrule=%@", self->rrule];
313   
314   if (self->firstRange != nil)
315     [ms appendFormat:@" range1=%@", self->firstRange];
316   
317   [ms appendString:@">"];
318   return ms;
319 }
320
321 @end /* iCalRecurrenceCalculator */