]> err.no Git - scalable-opengroupware.org/blob - SOPE/NGCards/iCalRecurrenceCalculator.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1178 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     rs   = [calc recurrenceRangesWithinCalendarDateRange:_r];
107     [ranges addObjectsFromArray:rs];
108   }
109   
110   if ([ranges count] == 0)
111     return nil;
112   
113   /* test if any exceptions do match */
114   
115   for (i = 0, count = [_exRules count]; i < count; i++) {
116     NSArray *rs;
117
118     rule = [_exRules objectAtIndex:i];
119     if (![rule isKindOfClass:iCalRecurrenceRuleClass])
120       rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:rule];
121
122     calc = [self recurrenceCalculatorForRecurrenceRule:rule
123                  withFirstInstanceCalendarDateRange:_fir];
124     rs   = [calc recurrenceRangesWithinCalendarDateRange:_r];
125     [ranges removeObjectsInArray:rs];
126   }
127   
128   if (![ranges isNotEmpty])
129     return nil;
130   
131   /* exception dates */
132
133   if ((count = [_exDates count]) == 0)
134     return ranges;
135   
136   /* sort out exDates not within range */
137
138   exDates = [NSMutableArray arrayWithCapacity:count];
139   for (i = 0; i < count; i++) {
140     id exDate;
141
142     exDate = [_exDates objectAtIndex:i];
143     if (![exDate isKindOfClass:NSCalendarDateClass])
144       exDate = [NSCalendarDate calendarDateWithICalRepresentation:exDate];
145     
146     if ([_r containsDate:exDate])
147       [exDates addObject:exDate];
148   }
149
150   /* remove matching exDates from ranges */
151
152   if ((count = [exDates count]) == 0)
153     return ranges;
154   
155   for (i = 0, rCount = [ranges count]; i < count; i++) {
156     NSCalendarDate      *exDate;
157     NGCalendarDateRange *r;
158     unsigned            k;
159
160     exDate = [exDates objectAtIndex:i];
161     for (k = 0; k < rCount; k++) {
162       unsigned rIdx;
163       
164       rIdx = (rCount - k) - 1;
165       r    = [ranges objectAtIndex:rIdx];
166       if ([r containsDate:exDate]) {
167         [ranges removeObjectAtIndex:rIdx];
168         rCount--;
169         break; /* this is safe because we know that ranges don't overlap */
170       }
171     }
172   }
173   return ranges;
174 }
175
176
177 /* init */
178
179 - (id)initWithRecurrenceRule:(iCalRecurrenceRule *)_rrule
180   firstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
181 {
182   iCalRecurrenceFrequency freq;
183   Class calcClass = Nil;
184
185   freq = [_rrule frequency];
186   if (freq == iCalRecurrenceFrequenceDaily)
187     calcClass = dailyCalcClass;
188   else if (freq == iCalRecurrenceFrequenceWeekly)
189     calcClass = weeklyCalcClass;
190   else if (freq == iCalRecurrenceFrequenceMonthly)
191     calcClass = monthlyCalcClass;
192   else if (freq == iCalRecurrenceFrequenceYearly)
193     calcClass = yearlyCalcClass;
194   else {
195     [self errorWithFormat:@"unsupported rrule frequency: %@", _rrule];
196     calcClass = Nil;
197     [self release];
198     return nil;
199   }
200   
201   [self autorelease]; // TODO: why autorelease?
202   if (calcClass == Nil)
203     return nil;
204   
205   if ((self = [[calcClass alloc] init]) != nil) {
206     self->rrule      = [_rrule retain];
207     self->firstRange = [_range retain];
208   }
209   return self;  
210 }
211
212 - (void)dealloc {
213   [self->firstRange release];
214   [self->rrule      release];
215   [super dealloc];
216 }
217
218 /* helpers */
219
220 - (unsigned)offsetFromSundayForJulianNumber:(long)_jn {
221   return (unsigned)((int)(_jn + 1.5)) % 7;
222 }
223
224 - (unsigned)offsetFromSundayForWeekDay:(iCalWeekDay)_weekDay {
225   unsigned offset;
226   
227   switch (_weekDay) {
228     case iCalWeekDaySunday:    offset = 0; break;
229     case iCalWeekDayMonday:    offset = 1; break;
230     case iCalWeekDayTuesday:   offset = 2; break;
231     case iCalWeekDayWednesday: offset = 3; break;
232     case iCalWeekDayThursday:  offset = 4; break;
233     case iCalWeekDayFriday:    offset = 5; break;
234     case iCalWeekDaySaturday:  offset = 6; break;
235     default:                   offset = 0; break;
236   }
237   return offset;
238 }
239
240 - (unsigned)offsetFromSundayForCurrentWeekStart {
241   return [self offsetFromSundayForWeekDay:[self->rrule weekStart]];
242 }
243
244 - (iCalWeekDay)weekDayForJulianNumber:(long)_jn {
245   unsigned    day;
246   iCalWeekDay weekDay;
247
248   day = [self offsetFromSundayForJulianNumber:_jn];
249   switch (day) {
250     case 0:  weekDay = iCalWeekDaySunday;    break;
251     case 1:  weekDay = iCalWeekDayMonday;    break;
252     case 2:  weekDay = iCalWeekDayTuesday;   break;
253     case 3:  weekDay = iCalWeekDayWednesday; break;
254     case 4:  weekDay = iCalWeekDayThursday;  break;
255     case 5:  weekDay = iCalWeekDayFriday;    break;
256     case 6:  weekDay = iCalWeekDaySaturday;  break;
257     default: 
258       [self errorWithFormat:@"got unexpected weekday: %d", day];
259       weekDay = iCalWeekDaySunday;
260       break; /* keep compiler happy */
261   }
262   return weekDay;
263 }
264
265 /* calculation */
266
267 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r{
268   return nil; /* subclass responsibility */
269 }
270 - (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
271   NSArray *ranges;
272
273   ranges = [self recurrenceRangesWithinCalendarDateRange:_range];
274   return (ranges == nil || [ranges count] == 0) ? NO : YES;
275 }
276
277 - (NGCalendarDateRange *)firstInstanceCalendarDateRange {
278   return self->firstRange;
279 }
280
281 - (NGCalendarDateRange *)lastInstanceCalendarDateRange {
282   NSCalendarDate *start, *end;
283
284   if ((start = [self lastInstanceStartDate]) == nil)
285     return nil;
286   
287   end   = [start addTimeInterval:[self->firstRange duration]];
288   return [NGCalendarDateRange calendarDateRangeWithStartDate:start
289                               endDate:end];
290 }
291
292 - (NSCalendarDate *)lastInstanceStartDate {
293   NSCalendarDate *until;
294   
295   /* 
296      NOTE: this is horribly inaccurate and doesn't even consider the use
297            of repeatCount. It MUST be implemented by subclasses properly!
298            However, it does the trick for SOGo 1.0 - that's why it's left here.
299   */
300   if ((until = [self->rrule untilDate]) != nil)
301     return until;
302   
303   return nil;
304 }
305
306 @end /* iCalRecurrenceCalculator */