- (NSString *)wkst;
- (NSString *)byDayList;
-- (void)_processRule;
+- (void)_parseRuleString:(NSString *)_rrule;
- (void)setRrule:(NSString *)_rrule; // TODO: weird name?
+/* currently used by parser, should be removed (replace with an -init..) */
+- (void)setByday:(NSString *)_byDayList;
+- (void)setFreq:(NSString *)_freq;
+
@end
@implementation iCalRecurrenceRule
if (c0 == 'f' || c0 == 'F') return iCalWeekDayFriday;
c1 = [_day characterAtIndex:1];
- if (c0 == 't' || c0 == 't') {
+ if (c0 == 't' || c0 == 'T') {
if (c1 == 'u' || c1 == 'U') return iCalWeekDayTuesday;
if (c1 == 'h' || c1 == 'H') return iCalWeekDayThursday;
}
NSMutableString *s;
unsigned i, mask, day;
BOOL needsComma;
-
+
s = [NSMutableString stringWithCapacity:20];
needsComma = NO;
mask = self->byDay.mask;
if (mask & day) {
if (needsComma)
[s appendString:@","];
+ else if (self->byDay.useOccurence)
+ // Note: we only support one occurrence currently
+ [s appendFormat:@"%i", self->byDayOccurence1];
+
[s appendString:[self iCalRepresentationForWeekDay:day]];
needsComma = YES;
}
- (void)setRrule:(NSString *)_rrule {
ASSIGNCOPY(self->rrule, _rrule);
- [self _processRule];
+ [self _parseRuleString:self->rrule];
}
-/* Processing existing rrule */
+/* parsing rrule */
-- (void)_processRule {
+- (void)_parseRuleString:(NSString *)_rrule {
+ // TODO: to be exact we would need a timezone to properly process the 'until'
+ // date
NSArray *props;
unsigned i, count;
+ NSString *pFrequency = nil;
+ NSString *pUntil = nil;
+ NSString *pCount = nil;
+ NSString *pByday = nil;
+ NSString *pBysetpos = nil;
- props = [self->rrule componentsSeparatedByString:@";"];
+ props = [_rrule componentsSeparatedByString:@";"];
for (i = 0, count = [props count]; i < count; i++) {
NSString *prop, *key, *value;
NSRange r;
+ NSString **vHolder = NULL;
prop = [props objectAtIndex:i];
r = [prop rangeOfString:@"="];
key = prop;
value = nil;
}
- [self takeValue:value forKey:[key lowercaseString]];
+
+ key = [[key stringByTrimmingSpaces] lowercaseString];
+ if (![key isNotEmpty]) {
+ [self errorWithFormat:@"empty component in rrule: %@", _rrule];
+ continue;
+ }
+
+ vHolder = NULL;
+ switch ([key characterAtIndex:0]) {
+ case 'b':
+ if ([key isEqualToString:@"byday"]) vHolder = &pByday; break;
+ if ([key isEqualToString:@"bysetpos"]) vHolder = &pBysetpos; break;
+ break;
+ case 'c':
+ if ([key isEqualToString:@"count"]) vHolder = &pCount; break;
+ break;
+ case 'f':
+ if ([key isEqualToString:@"freq"]) vHolder = &pFrequency; break;
+ break;
+ case 'u':
+ if ([key isEqualToString:@"until"]) vHolder = &pUntil; break;
+ break;
+ default:
+ break;
+ }
+
+ if (vHolder != NULL) {
+ if ([*vHolder isNotEmpty])
+ [self errorWithFormat:@"more than one '%@' in: %@", key, _rrule];
+ else
+ *vHolder = [value copy];
+ }
+ else {
+ // TODO: we should just parse known keys and put remainders into a
+ // separate dictionary
+ //[self logWithFormat:@"TODO: add explicit support for key: %@", key];
+ [self takeValue:value forKey:key];
+ }
+ }
+
+ /* parse and fill individual values */
+ // TODO: this method should be a class method and create a new rrule object
+
+ if ([pFrequency isNotEmpty])
+ [self setFreq:pFrequency];
+ else
+ [self errorWithFormat:@"rrule contains no frequency: '%@'", _rrule];
+ [pFrequency release]; pFrequency = nil;
+
+ // TODO: we should parse byday in here
+ if (pByday != nil) [self setByday:pByday];
+ [pByday release]; pByday = nil;
+
+ if (pBysetpos != nil)
+ // TODO: implement
+ [self errorWithFormat:@"rrule contains bysetpos, unsupported: %@", _rrule];
+ [pBysetpos release]; pBysetpos = nil;
+
+ if (pUntil != nil) {
+ NSCalendarDate *pUntilDate;
+
+ if (pCount != nil) {
+ [self errorWithFormat:@"rrule contains 'count' AND 'until': %@", _rrule];
+ [pCount release];
+ pCount = nil;
+ }
+
+ /*
+ The spec says:
+ "If specified as a date-time value, then it MUST be specified in an
+ UTC time format."
+ TODO: we still need some object representing a 'timeless' date.
+ */
+ if (![pUntil hasSuffix:@"Z"]) {
+ [self warnWithFormat:@"'until' date has no explicit UTC marker: '%@'",
+ _rrule];
+ }
+
+ pUntilDate = [NSCalendarDate calendarDateWithICalRepresentation:pUntil];
+ if (pUntilDate != nil)
+ [self setUntilDate:pUntilDate];
+ else {
+ [self errorWithFormat:@"could not parse 'until' in rrule: %@",
+ _rrule];
+ }
}
+ [pUntil release]; pUntil = nil;
+
+ if (pCount != nil)
+ [self setRepeatCount:[pCount intValue]];
+ [pCount release]; pCount = nil;
}
/* properties */
- (void)setFreq:(NSString *)_freq {
+ // TODO: shouldn't we preserve what the user gives us?
+ // => only used by -_parseRuleString: parser?
_freq = [_freq uppercaseString];
if ([_freq isEqualToString:@"WEEKLY"])
self->frequency = iCalRecurrenceFrequenceWeekly;
}
- (void)setByday:(NSString *)_byDayList {
+ // TODO: each day can have an associated occurence, eg:
+ // +1MO,+2TU,-9WE
+ // TODO: this should be moved to the parser
NSArray *days;
unsigned i, count;
-
+
+ /* reset mask */
self->byDay.mask = 0;
+ self->byDay.useOccurence = 0;
+ self->byDayOccurence1 = 0;
+
days = [_byDayList componentsSeparatedByString:@","];
for (i = 0, count = [days count]; i < count; i++) {
NSString *iCalDay;
iCalWeekDay day;
+ unsigned len;
+ unichar c0;
+ int occurence;
- iCalDay = [days objectAtIndex:i];
- day = [self weekDayFromICalRepresentation:iCalDay];
+ iCalDay = [days objectAtIndex:i]; // eg: MO or TU
+ if ((len = [iCalDay length]) == 0) {
+ [self errorWithFormat:@"found an empty day in byday list: '%@'",
+ _byDayList];
+ continue;
+ }
+
+ c0 = [iCalDay characterAtIndex:0];
+ if (((c0 == '+' || c0 == '-') && len > 2) || (isdigit(c0) && len > 1)) {
+ int offset;
+
+ occurence = [iCalDay intValue];
+
+ offset = 1;
+ while (offset < len && isdigit([iCalDay characterAtIndex:offset]))
+ offset++;
+
+ iCalDay = [iCalDay substringFromIndex:offset];
+
+ if (self->byDay.useOccurence) {
+ [self errorWithFormat:
+ @"we only supported one occurence (occ=%i,day=%@): '%@'",
+ occurence, iCalDay, _byDayList];
+ continue;
+ }
+
+ self->byDay.useOccurence = 1;
+ self->byDayOccurence1 = occurence;
+ }
+ else if (self->byDay.useOccurence) {
+ [self errorWithFormat:
+ @"a byday occurence was specified on one day, but not on others"
+ @" (unsupported): '%@'", _byDayList];
+ }
+
+ day = [self weekDayFromICalRepresentation:iCalDay];
self->byDay.mask |= day;
}
}
/* key/value coding */
- (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
- [self warnWithFormat:@"Don't know how to process '%@'!", _key];
+ [self warnWithFormat:@"Cannot handle unbound key: '%@'", _key];
}
return s;
}
+- (NSString *)description {
+ return [self iCalRepresentation];
+}
+
@end /* iCalRecurrenceRule */