]> err.no Git - sope/blob - sope-ical/NGiCal/iCalRecurrenceRule.m
improved API, bugfix for recurrent dates
[sope] / sope-ical / NGiCal / iCalRecurrenceRule.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 "iCalRecurrenceRule.h"
23 #include "NSCalendarDate+ICal.h"
24 #include "common.h"
25
26 /*
27  freq       = rrFreq;
28  until      = rrUntil;
29  count      = rrCount;
30  interval   = rrInterval;
31  bysecond   = rrBySecondList;
32  byminute   = rrByMinuteList;
33  byhour     = rrByHourList;
34  byday      = rrByDayList;
35  bymonthday = rrByMonthDayList;
36  byyearday  = rrByYearDayList;
37  byweekno   = rrByWeekNumberList;
38  bymonth    = rrByMonthList;
39  bysetpos   = rrBySetPosList;
40  wkst       = rrWeekStart;
41  */
42
43 @interface iCalRecurrenceRule (PrivateAPI)
44 - (iCalWeekDay)weekDayFromICalRepresentation:(NSString *)_day;
45 - (NSString *)iCalRepresentationForWeekDay:(iCalWeekDay)_weekDay;
46 - (NSString *)freq;
47 - (NSString *)wkst;
48 - (NSString *)byDayList;
49
50 - (void)_processRule;
51 - (void)setRrule:(NSString *)_rrule;
52 @end
53
54 @implementation iCalRecurrenceRule
55
56 + (void)initialize {
57   static BOOL didInit = NO;
58   
59   if (didInit) return;
60   didInit = YES;
61 }
62
63 + (id)recurrenceRuleWithICalRepresentation:(NSString *)_iCalRep {
64   iCalRecurrenceRule *r;
65   
66   r = [[[self alloc] init] autorelease];
67   [r setRrule:_iCalRep];
68   return r;
69 }
70
71 - (id)init {
72   self = [super init];
73   if (self) {
74     self->byDay.weekStart = iCalWeekDayMonday;
75     self->interval        = 1;
76   }
77   return self;
78 }
79
80 - (void)dealloc {
81   [self->untilDate release];
82   [self->rrule     release];
83   [super dealloc];
84 }
85
86
87 /* Accessors */
88
89 - (void)setFrequency:(iCalRecurrenceFrequency)_frequency {
90   self->frequency = _frequency;
91 }
92 - (iCalRecurrenceFrequency)frequency {
93   return self->frequency;
94 }
95
96 - (void)setRepeatCount:(unsigned)_repeatCount {
97   self->repeatCount = _repeatCount;
98 }
99 - (unsigned)repeatCount {
100   return self->repeatCount;
101 }
102
103 - (void)setUntilDate:(NSCalendarDate *)_untilDate {
104   ASSIGN(self->untilDate, _untilDate);
105 }
106 - (NSCalendarDate *)untilDate {
107   return self->untilDate;
108 }
109
110 - (void)setRepeatInterval:(int)_repeatInterval {
111   self->interval = _repeatInterval;
112 }
113 - (int)repeatInterval {
114   return self->interval;
115 }
116
117 - (void)setWeekStart:(iCalWeekDay)_weekStart {
118   self->byDay.weekStart = _weekStart;
119 }
120 - (iCalWeekDay)weekStart {
121   return self->byDay.weekStart;
122 }
123
124 - (void)setByDayMask:(unsigned)_mask {
125   self->byDay.mask = _mask;
126 }
127 - (unsigned)byDayMask {
128   return self->byDay.mask;
129 }
130
131 - (BOOL)isInfinite {
132   return (self->repeatCount != 0 || self->untilDate) ? NO : YES;
133 }
134
135
136 /* Private */
137
138 - (iCalWeekDay)weekDayFromICalRepresentation:(NSString *)_day {
139   _day = [_day uppercaseString];
140   if ([_day isEqualToString:@"MO"])
141     return iCalWeekDayMonday;
142   else if ([_day isEqualToString:@"TU"])
143     return iCalWeekDayTuesday;
144   else if ([_day isEqualToString:@"WE"])
145     return iCalWeekDayWednesday;
146   else if ([_day isEqualToString:@"TH"])
147     return iCalWeekDayThursday;
148   else if ([_day isEqualToString:@"FR"])
149     return iCalWeekDayFriday;
150   else if ([_day isEqualToString:@"SA"])
151     return iCalWeekDaySaturday;
152   else if ([_day isEqualToString:@"SU"])
153     return iCalWeekDaySunday;
154   else
155     [NSException raise:NSGenericException
156                  format:@"Incorrect weekDay '%@' specified!", _day];
157   return iCalWeekDayMonday; /* keep compiler happy */
158 }
159
160 - (NSString *)iCalRepresentationForWeekDay:(iCalWeekDay)_weekDay {
161   switch (_weekDay) {
162     case iCalWeekDayMonday:
163       return @"MO";
164     case iCalWeekDayTuesday:
165       return @"TU";
166     case iCalWeekDayWednesday:
167       return @"WE";
168     case iCalWeekDayThursday:
169       return @"TH";
170     case iCalWeekDayFriday:
171       return @"FR";
172     case iCalWeekDaySaturday:
173       return @"SA";
174     case iCalWeekDaySunday:
175       return @"SU";
176     default:
177       return @"MO";
178   }
179 }
180
181 - (NSString *)freq {
182   switch (self->frequency) {
183     case iCalRecurrenceFrequenceWeekly:
184       return @"WEEKLY";
185     case iCalRecurrenceFrequenceMonthly:
186       return @"MONTHLY";
187     case iCalRecurrenceFrequenceDaily:
188       return @"DAILY";
189     case iCalRecurrenceFrequenceYearly:
190       return @"YEARLY";
191     case iCalRecurrenceFrequenceHourly:
192       return @"HOURLY";
193     case iCalRecurrenceFrequenceMinutely:
194       return @"MINUTELY";
195     case iCalRecurrenceFrequenceSecondly:
196       return @"SECONDLY";
197     default:
198       return @"UNDEFINED?";
199   }
200 }
201
202 - (NSString *)wkst {
203   return [self iCalRepresentationForWeekDay:self->byDay.weekStart];
204 }
205
206 /*
207  TODO:
208  Each BYDAY value can also be preceded by a positive (+n) or negative
209  (-n) integer. If present, this indicates the nth occurrence of the
210  specific day within the MONTHLY or YEARLY RRULE. For example, within
211  a MONTHLY rule, +1MO (or simply 1MO) represents the first Monday
212  within the month, whereas -1MO represents the last Monday of the
213  month. If an integer modifier is not present, it means all days of
214  this type within the specified frequency. For example, within a
215  MONTHLY rule, MO represents all Mondays within the month.
216 */
217 - (NSString *)byDayList {
218   NSMutableString *s;
219   unsigned        i, mask, day;
220   BOOL            needsComma;
221
222   s          = [NSMutableString stringWithCapacity:20];
223   needsComma = NO;
224   mask       = self->byDay.mask;
225   day        = iCalWeekDayMonday;
226   for (i = 0; i < 7; i++) {
227     if (mask & day) {
228       if (needsComma)
229         [s appendString:@","];
230       [s appendString:[self iCalRepresentationForWeekDay:day]];
231       needsComma = YES;
232     }
233     day = (day << 1);
234   }
235   return s;
236 }
237
238 /* Rule */
239
240 - (void)setRrule:(NSString *)_rrule {
241   ASSIGN(self->rrule, _rrule);
242   [self _processRule];
243 }
244
245 /* Processing existing rrule */
246
247 - (void)_processRule {
248   NSArray  *props;
249   unsigned i, count;
250   
251   props = [self->rrule componentsSeparatedByString:@";"];
252   count = [props count];
253   for (i = 0; i < count; i++) {
254     NSString *prop, *key, *value;
255     NSRange  r;
256     
257     prop = [props objectAtIndex:i];
258     r    = [prop rangeOfString:@"="];
259     if (r.length) {
260       key   = [prop substringToIndex:r.location];
261       value = [prop substringFromIndex:NSMaxRange(r)];
262     }
263     else {
264       key   = prop;
265       value = nil;
266     }
267     [self takeValue:value forKey:[key lowercaseString]];
268   }
269 }
270
271
272 /* properties */
273
274 - (void)setFreq:(NSString *)_freq {
275   _freq = [_freq uppercaseString];
276   if ([_freq isEqualToString:@"WEEKLY"])
277     self->frequency = iCalRecurrenceFrequenceWeekly;
278   else if ([_freq isEqualToString:@"MONTHLY"])
279     self->frequency = iCalRecurrenceFrequenceMonthly;
280   else if ([_freq isEqualToString:@"DAILY"])
281     self->frequency = iCalRecurrenceFrequenceDaily;
282   else if ([_freq isEqualToString:@"YEARLY"])
283     self->frequency = iCalRecurrenceFrequenceYearly;
284   else if ([_freq isEqualToString:@"HOURLY"])
285     self->frequency = iCalRecurrenceFrequenceHourly;
286   else if ([_freq isEqualToString:@"MINUTELY"])
287     self->frequency = iCalRecurrenceFrequenceMinutely;
288   else if ([_freq isEqualToString:@"SECONDLY"])
289     self->frequency = iCalRecurrenceFrequenceSecondly;
290   else
291     [NSException raise:NSGenericException
292                  format:@"Incorrect frequency '%@' specified!", _freq];
293 }
294
295 - (void)setInterval:(NSString *)_interval {
296   self->interval = [_interval intValue];
297 }
298 - (void)setCount:(NSString *)_count {
299   self->repeatCount = [_count unsignedIntValue];
300 }
301 - (void)setUntil:(NSString *)_until {
302   NSCalendarDate *date;
303
304   date = [NSCalendarDate calendarDateWithICalRepresentation:_until];
305   ASSIGN(self->untilDate, date);
306 }
307
308 - (void)setWkst:(NSString *)_weekStart {
309   self->byDay.weekStart = [self weekDayFromICalRepresentation:_weekStart];
310 }
311
312 - (void)setByday:(NSString *)_byDayList {
313   NSArray  *days;
314   unsigned i, count;
315
316   self->byDay.mask = 0;
317   days  = [_byDayList componentsSeparatedByString:@","];
318   count = [days count];
319   for (i = 0; i < count; i++) {
320     NSString    *iCalDay;
321     iCalWeekDay day;
322     
323     iCalDay = [days objectAtIndex:i];
324     day     = [self weekDayFromICalRepresentation:iCalDay];
325     self->byDay.mask |= day;
326   }
327 }
328
329 /* key/value coding */
330
331 - (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
332   [self warnWithFormat:@"Don't know how to process '%@'!", _key];
333 }
334
335
336 /* Description */
337
338 - (NSString *)iCalRepresentation {
339   NSMutableString *s;
340   
341   s = [NSMutableString stringWithCapacity:80];
342   [s appendString:@"FREQ="];
343   [s appendString:[self freq]];
344   if ([self repeatInterval] != 1) {
345     [s appendFormat:@";INTERVAL=%d", [self repeatInterval]];
346   }
347   if (![self isInfinite]) {
348     if ([self repeatCount] > 0) {
349       [s appendFormat:@";COUNT=%d", [self repeatCount]];
350     }
351     else {
352       [s appendString:@";UNTIL="];
353       [s appendString:[[self untilDate] icalString]];
354     }
355   }
356   if (self->byDay.weekStart != iCalWeekDayMonday) {
357     [s appendString:@";WKST="];
358     [s appendString:[self iCalRepresentationForWeekDay:self->byDay.weekStart]];
359   }
360   if (self->byDay.mask != 0) {
361     [s appendString:@";BYDAY="];
362     [s appendString:[self byDayList]];
363   }
364   return s;
365 }
366
367
368 @end