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