@interface iCalRecurrenceCalculator (PrivateAPI)
- (NSCalendarDate *)lastInstanceStartDate;
+
+- (unsigned)offsetFromSundayForJulianNumber:(long)_jn;
+- (unsigned)offsetFromSundayForWeekDay:(iCalWeekDay)_weekDay;
+- (unsigned)offsetFromSundayForCurrentWeekStart;
+
+- (iCalWeekDay)weekDayForJulianNumber:(long)_jn;
@end
@implementation iCalRecurrenceCalculator
+static Class NSCalendarDateClass = Nil;
+static Class iCalRecurrenceRuleClass = Nil;
static Class dailyCalcClass = Nil;
static Class weeklyCalcClass = Nil;
static Class monthlyCalcClass = Nil;
if (didInit) return;
didInit = YES;
+ NSCalendarDateClass = [NSCalendarDate class];
+ iCalRecurrenceRuleClass = [iCalRecurrenceRule class];
+
dailyCalcClass = [iCalDailyRecurrenceCalculator class];
weeklyCalcClass = [iCalWeeklyRecurrenceCalculator class];
monthlyCalcClass = [iCalMonthlyRecurrenceCalculator class];
yearlyCalcClass = [iCalYearlyRecurrenceCalculator class];
}
+/* factory */
+
+ (id)recurrenceCalculatorForRecurrenceRule:(iCalRecurrenceRule *)_rrule
withFirstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
{
firstInstanceCalendarDateRange:_range] autorelease];
}
+/* complex calculation convenience */
+
++ (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r
+ firstInstanceCalendarDateRange:(NGCalendarDateRange *)_fir
+ recurrenceRules:(NSArray *)_rRules
+ exceptionRules:(NSArray *)_exRules
+ exceptionDates:(NSArray *)_exDates
+{
+ id rule;
+ iCalRecurrenceCalculator *calc;
+ NSMutableArray *ranges;
+ NSMutableArray *exDates;
+ unsigned i, count, rCount;
+
+ ranges = [NSMutableArray array];
+ count = [_rRules count];
+ for (i = 0; i < count; i++) {
+ NSArray *rs;
+
+ rule = [_rRules objectAtIndex:i];
+ if (![rule isKindOfClass:iCalRecurrenceRuleClass])
+ rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:rule];
+
+ calc = [self recurrenceCalculatorForRecurrenceRule:rule
+ withFirstInstanceCalendarDateRange:_fir];
+ rs = [calc recurrenceRangesWithinCalendarDateRange:_r];
+ [ranges addObjectsFromArray:rs];
+ }
+
+ if (![ranges count])
+ return nil;
+
+ /* test if any exceptions do match */
+ count = [_exRules count];
+ for (i = 0; i < count; i++) {
+ NSArray *rs;
+
+ rule = [_exRules objectAtIndex:i];
+ if (![rule isKindOfClass:iCalRecurrenceRuleClass])
+ rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:rule];
+
+ calc = [self recurrenceCalculatorForRecurrenceRule:rule
+ withFirstInstanceCalendarDateRange:_fir];
+ rs = [calc recurrenceRangesWithinCalendarDateRange:_r];
+ [ranges removeObjectsInArray:rs];
+ }
+
+ if (![ranges count])
+ return nil;
+
+ /* exception dates */
+
+ count = [_exDates count];
+ if (!count) return ranges;
+
+ /* sort out exDates not within range */
+
+ exDates = [NSMutableArray arrayWithCapacity:count];
+ for (i = 0; i < count; i++) {
+ id exDate;
+
+ exDate = [_exDates objectAtIndex:i];
+ if (![exDate isKindOfClass:NSCalendarDateClass]) {
+ exDate = [NSCalendarDate calendarDateWithICalRepresentation:exDate];
+ }
+ if ([_r containsDate:exDate])
+ [exDates addObject:exDate];
+ }
+
+ /* remove matching exDates from ranges */
+
+ count = [exDates count];
+ if (!count) return ranges;
+
+ rCount = [ranges count];
+ for (i = 0; i < count; i++) {
+ NSCalendarDate *exDate;
+ NGCalendarDateRange *r;
+ unsigned k;
+
+ exDate = [exDates objectAtIndex:i];
+ for (k = 0; k < rCount; k++) {
+ unsigned rIdx;
+
+ rIdx = (rCount - k) - 1;
+ r = [ranges objectAtIndex:rIdx];
+ if ([r containsDate:exDate]) {
+ [ranges removeObjectAtIndex:rIdx];
+ rCount--;
+ break; /* this is safe because we know that ranges don't overlap */
+ }
+ }
+ }
+ return ranges;
+}
+
+
+/* init */
+
- (id)initWithRecurrenceRule:(iCalRecurrenceRule *)_rrule
firstInstanceCalendarDateRange:(NGCalendarDateRange *)_range
{
return self;
}
+/* helpers */
+
+- (unsigned)offsetFromSundayForJulianNumber:(long)_jn {
+ return (unsigned)((int)(_jn + 1.5)) % 7;
+}
+
+- (unsigned)offsetFromSundayForWeekDay:(iCalWeekDay)_weekDay {
+ unsigned offset;
+
+ switch (_weekDay) {
+ case iCalWeekDaySunday: offset = 0; break;
+ case iCalWeekDayMonday: offset = 1; break;
+ case iCalWeekDayTuesday: offset = 2; break;
+ case iCalWeekDayWednesday: offset = 3; break;
+ case iCalWeekDayThursday: offset = 4; break;
+ case iCalWeekDayFriday: offset = 5; break;
+ case iCalWeekDaySaturday: offset = 6; break;
+ default: offset = 0; break;
+ }
+ return offset;
+}
+
+- (unsigned)offsetFromSundayForCurrentWeekStart {
+ return [self offsetFromSundayForWeekDay:[self->rrule weekStart]];
+}
+
+- (iCalWeekDay)weekDayForJulianNumber:(long)_jn {
+ unsigned day;
+ iCalWeekDay weekDay;
+
+ day = [self offsetFromSundayForJulianNumber:_jn];
+ switch (day) {
+ case 0: weekDay = iCalWeekDaySunday; break;
+ case 1: weekDay = iCalWeekDayMonday; break;
+ case 2: weekDay = iCalWeekDayTuesday; break;
+ case 3: weekDay = iCalWeekDayWednesday; break;
+ case 4: weekDay = iCalWeekDayThursday; break;
+ case 5: weekDay = iCalWeekDayFriday; break;
+ case 6: weekDay = iCalWeekDaySaturday; break;
+ default: weekDay = iCalWeekDaySunday; break; /* keep compiler happy */
+ }
+ return weekDay;
+}
/* calculation */
NGCalendarDateRange *r;
start = [NSCalendarDate dateForJulianNumber:jnCurrent];
+ [start setTimeZone:[firStart timeZone]];
start = [start hour: [firStart hourOfDay]
minute:[firStart minuteOfHour]
second:[firStart secondOfMinute]];
end = [start addTimeInterval:[self->firstRange duration]];
r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
endDate:end];
- [ranges addObject:r];
+ if ([_r containsDateRange:r])
+ [ranges addObject:r];
}
}
}
NGCalendarDateRange *r;
start = [NSCalendarDate dateForJulianNumber:jnCurrent];
+ [start setTimeZone:[firStart timeZone]];
start = [start hour: [firStart hourOfDay]
minute:[firStart minuteOfHour]
second:[firStart secondOfMinute]];
end = [start addTimeInterval:[self->firstRange duration]];
r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
endDate:end];
- [ranges addObject:r];
+ if ([_r containsDateRange:r])
+ [ranges addObject:r];
}
}
}
}
else {
- [self errorWithFormat:@"Cannot calculate rules with byDayMask, yet!"];
+ long jnFirstWeekStart, weekStartOffset;
+
+ /* calculate jnFirst's week start - this depends on our setting of week
+ start */
+ weekStartOffset = [self offsetFromSundayForJulianNumber:jnFirst] -
+ [self offsetFromSundayForCurrentWeekStart];
+
+ jnFirstWeekStart = jnFirst - weekStartOffset;
+
+ for (i = 0 ; i < startEndCount; i++) {
+ long jnCurrent;
+
+ jnCurrent = jnStart + i;
+ if (jnCurrent >= jnFirst) {
+ long jnDiff;
+
+ /* we need to calculate a difference in weeks */
+ jnDiff = (jnCurrent - jnFirstWeekStart) % 7;
+ if ((jnDiff % interval) == 0) {
+ BOOL isRecurrence = NO;
+
+ if (jnCurrent == jnFirst) {
+ isRecurrence = YES;
+ }
+ else {
+ iCalWeekDay weekDay;
+
+ weekDay = [self weekDayForJulianNumber:jnCurrent];
+ isRecurrence = (weekDay & [self->rrule byDayMask]) ? YES : NO;
+ }
+ if (isRecurrence) {
+ NSCalendarDate *start, *end;
+ NGCalendarDateRange *r;
+
+ start = [NSCalendarDate dateForJulianNumber:jnCurrent];
+ [start setTimeZone:[firStart timeZone]];
+ start = [start hour: [firStart hourOfDay]
+ minute:[firStart minuteOfHour]
+ second:[firStart secondOfMinute]];
+ end = [start addTimeInterval:[self->firstRange duration]];
+ r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
+ endDate:end];
+ if ([_r containsDateRange:r])
+ [ranges addObject:r];
+ }
+ }
+ }
+ }
}
return ranges;
}
- (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
NSMutableArray *ranges;
NSCalendarDate *firStart, *rStart, *rEnd, *until;
- unsigned i, count, interval, diff;
+ unsigned i, count, interval;
+ int diff;
firStart = [self->firstRange startDate];
rStart = [_r startDate];
}
diff = [firStart monthsBetweenDate:rStart];
+ if ((diff != 0) && [rStart compare:firStart] == NSOrderedAscending)
+ diff = -diff;
+
count = [rStart monthsBetweenDate:rEnd] + 1;
ranges = [NSMutableArray arrayWithCapacity:count];
for (i = 0 ; i < count; i++) {
- unsigned test;
+ int test;
test = diff + i;
- if ((test % interval) == 0) {
+ if ((test >= 0) && (test % interval) == 0) {
NSCalendarDate *start, *end;
NGCalendarDateRange *r;
start = [firStart dateByAddingYears:0
months:diff + i
days:0];
+ [start setTimeZone:[firStart timeZone]];
end = [start addTimeInterval:[self->firstRange duration]];
r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
endDate:end];
- [ranges addObject:r];
+ if ([_r containsDateRange:r])
+ [ranges addObject:r];
}
}
return ranges;
- (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
NSMutableArray *ranges;
NSCalendarDate *firStart, *rStart, *rEnd, *until;
- unsigned i, count, interval, diff;
+ unsigned i, count, interval;
+ int diff;
firStart = [self->firstRange startDate];
rStart = [_r startDate];
}
diff = [firStart yearsBetweenDate:rStart];
+ if ((diff != 0) && [rStart compare:firStart] == NSOrderedAscending)
+ diff = -diff;
+
count = [rStart yearsBetweenDate:rEnd] + 1;
ranges = [NSMutableArray arrayWithCapacity:count];
for (i = 0 ; i < count; i++) {
- unsigned test;
-
+ int test;
+
test = diff + i;
- if ((test % interval) == 0) {
+ if ((test >= 0) && (test % interval) == 0) {
NSCalendarDate *start, *end;
NGCalendarDateRange *r;
start = [firStart dateByAddingYears:diff + i
months:0
days:0];
+ [start setTimeZone:[firStart timeZone]];
end = [start addTimeInterval:[self->firstRange duration]];
r = [NGCalendarDateRange calendarDateRangeWithStartDate:start
endDate:end];
- [ranges addObject:r];
+ if ([_r containsDateRange:r])
+ [ranges addObject:r];
}
}
return ranges;