]> err.no Git - scalable-opengroupware.org/blob - SOPE/NGCards/iCalRecurrenceCalculator.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1229 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / SOPE / NGCards / 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 #import <NGExtensions/NSCalendarDate+misc.h>
23 #import <NGExtensions/NGCalendarDateRange.h>
24 #import <NGExtensions/NSNull+misc.h>
25 #import <NGExtensions/NSObject+Logs.h>
26
27 #import "iCalRecurrenceRule.h"
28 #import "NSCalendarDate+ICal.h"
29
30
31 #import "iCalRecurrenceCalculator.h"
32
33 /* class cluster */
34
35
36 /* Private */
37
38 @interface iCalRecurrenceCalculator (PrivateAPI)
39 - (NSCalendarDate *)lastInstanceStartDate;
40
41 - (unsigned)offsetFromSundayForJulianNumber:(long)_jn;
42 - (unsigned)offsetFromSundayForWeekDay:(iCalWeekDay)_weekDay;
43 - (unsigned)offsetFromSundayForCurrentWeekStart;
44   
45 - (iCalWeekDay)weekDayForJulianNumber:(long)_jn;
46 @end
47
48 @implementation iCalRecurrenceCalculator
49
50 static Class NSCalendarDateClass     = Nil;
51 static Class iCalRecurrenceRuleClass = Nil;
52 static Class dailyCalcClass   = Nil;
53 static Class weeklyCalcClass  = Nil;
54 static Class monthlyCalcClass = Nil;
55 static Class yearlyCalcClass  = Nil;
56
57 + (void)initialize {
58   static BOOL didInit = NO;
59   
60   if (didInit) return;
61   didInit = YES;
62
63   NSCalendarDateClass     = [NSCalendarDate class];
64   iCalRecurrenceRuleClass = [iCalRecurrenceRule class];
65
66   dailyCalcClass   = NSClassFromString(@"iCalDailyRecurrenceCalculator");
67   weeklyCalcClass  = NSClassFromString(@"iCalWeeklyRecurrenceCalculator");
68   monthlyCalcClass = NSClassFromString(@"iCalMonthlyRecurrenceCalculator");
69   yearlyCalcClass  = NSClassFromString(@"iCalYearlyRecurrenceCalculator");
70 }
71
72 /* factory */
73
74 + (id)recurrenceCalculatorForRecurrenceRule:(iCalRecurrenceRule *)_rrule
75   withFirstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
76 {
77   return [[[self alloc] initWithRecurrenceRule:_rrule
78                         firstInstanceCalendarDateRange:_range] autorelease];
79 }
80
81 /* complex calculation convenience */
82
83 + (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r
84   firstInstanceCalendarDateRange:(NGCalendarDateRange *)_fir
85   recurrenceRules:(NSArray *)_rRules
86   exceptionRules:(NSArray *)_exRules
87   exceptionDates:(NSArray *)_exDates
88 {
89   id                       rule;
90   iCalRecurrenceCalculator *calc;
91   NSMutableArray           *ranges;
92   NSMutableArray           *exDates;
93   unsigned                 i, count, rCount;
94   
95   ranges = [NSMutableArray arrayWithCapacity:64];
96   
97   for (i = 0, count  = [_rRules count]; i < count; i++) {
98     NSArray *rs;
99
100     rule = [_rRules objectAtIndex:i];
101     if (![rule isKindOfClass:iCalRecurrenceRuleClass])
102       rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:rule];
103         
104     calc = [self recurrenceCalculatorForRecurrenceRule:rule
105                  withFirstInstanceCalendarDateRange:_fir];
106
107     rs   = [calc recurrenceRangesWithinCalendarDateRange:_r];
108     [ranges addObjectsFromArray:rs];
109   }
110   
111   if ([ranges count] == 0)
112     return nil;
113   
114   /* test if any exceptions do match */
115   
116   for (i = 0, count = [_exRules count]; i < count; i++) {
117     NSArray *rs;
118
119     rule = [_exRules objectAtIndex:i];
120     if (![rule isKindOfClass:iCalRecurrenceRuleClass])
121       rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:rule];
122
123     calc = [self recurrenceCalculatorForRecurrenceRule:rule
124                  withFirstInstanceCalendarDateRange:_fir];
125     rs   = [calc recurrenceRangesWithinCalendarDateRange:_r];
126     [ranges removeObjectsInArray:rs];
127   }
128   
129   if (![ranges isNotEmpty])
130     return nil;
131   
132   /* exception dates */
133
134   if ((count = [_exDates count]) == 0)
135     return ranges;
136   
137   /* sort out exDates not within range */
138
139   exDates = [NSMutableArray arrayWithCapacity:count];
140   for (i = 0; i < count; i++) {
141     id exDate;
142
143     exDate = [_exDates objectAtIndex:i];
144     if (![exDate isKindOfClass:NSCalendarDateClass])
145       exDate = [NSCalendarDate calendarDateWithICalRepresentation:exDate];
146     
147     if ([_r containsDate:exDate])
148       [exDates addObject:exDate];
149   }
150
151   /* remove matching exDates from ranges */
152
153   if ((count = [exDates count]) == 0)
154     return ranges;
155   
156   for (i = 0, rCount = [ranges count]; i < count; i++) {
157     NSCalendarDate      *exDate;
158     NGCalendarDateRange *r;
159     unsigned            k;
160
161     exDate = [exDates objectAtIndex:i];
162     
163     for (k = 0; k < rCount; k++) {
164       unsigned rIdx;
165       
166       rIdx = (rCount - k) - 1;
167       r    = [ranges objectAtIndex:rIdx];
168       if ([r containsDate:exDate]) {
169         [ranges removeObjectAtIndex:rIdx];
170         rCount--;
171         break; /* this is safe because we know that ranges don't overlap */
172       }
173     }
174   }
175   return ranges;
176 }
177
178
179 /* init */
180
181 - (id)initWithRecurrenceRule:(iCalRecurrenceRule *)_rrule
182   firstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
183 {
184   iCalRecurrenceFrequency freq;
185   Class calcClass = Nil;
186
187   freq = [_rrule frequency];
188   if (freq == iCalRecurrenceFrequenceDaily)
189     calcClass = dailyCalcClass;
190   else if (freq == iCalRecurrenceFrequenceWeekly)
191     calcClass = weeklyCalcClass;
192   else if (freq == iCalRecurrenceFrequenceMonthly)
193     calcClass = monthlyCalcClass;
194   else if (freq == iCalRecurrenceFrequenceYearly)
195     calcClass = yearlyCalcClass;
196   else {
197     [self errorWithFormat:@"unsupported rrule frequency: %@", _rrule];
198     calcClass = Nil;
199     [self release];
200     return nil;
201   }
202   
203   [self autorelease]; // TODO: why autorelease?
204   if (calcClass == Nil)
205     return nil;
206   
207   if ((self = [[calcClass alloc] init]) != nil) {
208     self->rrule      = [_rrule retain];
209     self->firstRange = [_range retain];
210   }
211   return self;  
212 }
213
214 - (void)dealloc {
215   [self->firstRange release];
216   [self->rrule      release];
217   [super dealloc];
218 }
219
220 /* helpers */
221
222 - (unsigned)offsetFromSundayForJulianNumber:(long)_jn {
223   return (unsigned)((int)(_jn + 1.5)) % 7;
224 }
225
226 - (unsigned)offsetFromSundayForWeekDay:(iCalWeekDay)_weekDay {
227   unsigned offset;
228   
229   switch (_weekDay) {
230     case iCalWeekDaySunday:    offset = 0; break;
231     case iCalWeekDayMonday:    offset = 1; break;
232     case iCalWeekDayTuesday:   offset = 2; break;
233     case iCalWeekDayWednesday: offset = 3; break;
234     case iCalWeekDayThursday:  offset = 4; break;
235     case iCalWeekDayFriday:    offset = 5; break;
236     case iCalWeekDaySaturday:  offset = 6; break;
237     default:                   offset = 0; break;
238   }
239   return offset;
240 }
241
242 - (unsigned)offsetFromSundayForCurrentWeekStart {
243   return [self offsetFromSundayForWeekDay:[self->rrule weekStart]];
244 }
245
246 - (iCalWeekDay)weekDayForJulianNumber:(long)_jn {
247   unsigned    day;
248   iCalWeekDay weekDay;
249
250   day = [self offsetFromSundayForJulianNumber:_jn];
251   switch (day) {
252     case 0:  weekDay = iCalWeekDaySunday;    break;
253     case 1:  weekDay = iCalWeekDayMonday;    break;
254     case 2:  weekDay = iCalWeekDayTuesday;   break;
255     case 3:  weekDay = iCalWeekDayWednesday; break;
256     case 4:  weekDay = iCalWeekDayThursday;  break;
257     case 5:  weekDay = iCalWeekDayFriday;    break;
258     case 6:  weekDay = iCalWeekDaySaturday;  break;
259     default: 
260       [self errorWithFormat:@"got unexpected weekday: %d", day];
261       weekDay = iCalWeekDaySunday;
262       break; /* keep compiler happy */
263   }
264   return weekDay;
265 }
266
267 /* calculation */
268
269 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r{
270   return nil; /* subclass responsibility */
271 }
272 - (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
273   NSArray *ranges;
274
275   ranges = [self recurrenceRangesWithinCalendarDateRange:_range];
276   return (ranges == nil || [ranges count] == 0) ? NO : YES;
277 }
278
279 - (NGCalendarDateRange *)firstInstanceCalendarDateRange {
280   return self->firstRange;
281 }
282
283 - (NGCalendarDateRange *)lastInstanceCalendarDateRange {
284   NSCalendarDate *start, *end;
285
286   if ((start = [self lastInstanceStartDate]) == nil)
287     return nil;
288   
289   end   = [start addTimeInterval:[self->firstRange duration]];
290   return [NGCalendarDateRange calendarDateRangeWithStartDate:start
291                               endDate:end];
292 }
293
294 - (NSCalendarDate *)lastInstanceStartDate {
295   NSCalendarDate *until;
296   
297   /* 
298      NOTE: this is horribly inaccurate and doesn't even consider the use
299            of repeatCount. It MUST be implemented by subclasses properly!
300            However, it does the trick for SOGo 1.0 - that's why it's left here.
301   */
302   if ((until = [self->rrule untilDate]) != nil)
303     return until;
304   
305   return nil;
306 }
307
308 @end /* iCalRecurrenceCalculator */