@interface iCalDailyRecurrenceCalculator : iCalRecurrenceCalculator
{
}
-- (unsigned)factor;
@end
-@interface iCalWeeklyRecurrenceCalculator : iCalDailyRecurrenceCalculator
+@interface iCalWeeklyRecurrenceCalculator : iCalRecurrenceCalculator
{
}
@end
@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;
+ 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 are also possible */
+ rCount = [ranges count];
+ count = [_exDates count];
+ for (i = 0; i < count; i++) {
+ id exDate;
+ NGCalendarDateRange *r;
+ unsigned k;
+
+ exDate = [_exDates objectAtIndex:i];
+ if (![exDate isKindOfClass:NSCalendarDateClass]) {
+ exDate = [NSCalendarDate calendarDateWithICalRepresentation:exDate];
+ }
+ for (k = 0; k < rCount; k++) {
+ r = [ranges objectAtIndex:(rCount - k) - 1];
+ if ([r containsDate:exDate]) {
+ [ranges removeObjectAtIndex:k];
+ }
+ }
+ }
+ 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 */
jnRuleLast = [until julianNumber];
}
else {
- jnRuleLast = (interval * [self->rrule repeatCount] * [self factor])
+ jnRuleLast = (interval * [self->rrule repeatCount])
+ jnFirst;
if (jnRuleLast < jnStart)
return nil;
jnEnd = jnRuleLast;
}
- ranges = [NSMutableArray arrayWithCapacity:5];
startEndCount = (jnEnd - jnStart) + 1;
+ ranges = [NSMutableArray arrayWithCapacity:startEndCount];
for (i = 0 ; i < startEndCount; i++) {
- long jnTest;
-
- jnTest = (jnStart + i) - jnFirst;
- if ((jnTest % interval) == 0) {
- NSCalendarDate *start, *end;
- NGCalendarDateRange *r;
+ long jnCurrent;
- start = [NSCalendarDate dateForJulianNumber:jnStart + i];
- 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];
+ jnCurrent = jnStart + i;
+ if (jnCurrent >= jnFirst) {
+ long jnTest;
+
+ jnTest = jnCurrent - jnFirst;
+ if ((jnTest % interval) == 0) {
+ 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];
+ [ranges addObject:r];
+ }
}
}
-
return ranges;
}
firStart = [self->firstRange startDate];
jnFirst = [firStart julianNumber];
jnRuleLast = ([self->rrule repeatInterval] *
- [self->rrule repeatCount] *
- [self factor]) +
+ [self->rrule repeatCount]) +
jnFirst;
until = [NSCalendarDate dateForJulianNumber:jnRuleLast];
until = [until hour: [firStart hourOfDay]
return [super lastInstanceStartDate];
}
-- (unsigned)factor {
- return 1;
-}
-
@end /* iCalDailyRecurrenceCalculator */
*/
@implementation iCalWeeklyRecurrenceCalculator
-- (unsigned)factor {
- return 7;
+- (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
+ NSMutableArray *ranges;
+ NSCalendarDate *firStart;
+ long i, jnFirst, jnStart, jnEnd, startEndCount;
+ unsigned interval, byDayMask;
+
+ firStart = [self->firstRange startDate];
+ jnFirst = [firStart julianNumber];
+ jnEnd = [[_r endDate] julianNumber];
+
+ if (jnFirst > jnEnd)
+ return nil;
+
+ jnStart = [[_r startDate] julianNumber];
+ interval = [self->rrule repeatInterval];
+
+ /* if rule is bound, check the bounds */
+ if (![self->rrule isInfinite]) {
+ NSCalendarDate *until;
+ long jnRuleLast;
+
+ until = [self->rrule untilDate];
+ if (until) {
+ if ([until compare:[_r startDate]] == NSOrderedAscending)
+ return nil;
+ jnRuleLast = [until julianNumber];
+ }
+ else {
+ jnRuleLast = (interval * [self->rrule repeatCount] * 7)
+ + jnFirst;
+ if (jnRuleLast < jnStart)
+ return nil;
+ }
+ /* jnStart < jnRuleLast < jnEnd ? */
+ if (jnEnd > jnRuleLast)
+ jnEnd = jnRuleLast;
+ }
+
+ startEndCount = (jnEnd - jnStart) + 1;
+ ranges = [NSMutableArray arrayWithCapacity:startEndCount];
+ byDayMask = [self->rrule byDayMask];
+ if (!byDayMask) {
+ for (i = 0 ; i < startEndCount; i++) {
+ long jnCurrent;
+
+ jnCurrent = jnStart + i;
+ if (jnCurrent >= jnFirst) {
+ long jnDiff;
+
+ jnDiff = jnCurrent - jnFirst; /* difference in days */
+ if ((jnDiff % (interval * 7)) == 0) {
+ 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];
+ [ranges addObject:r];
+ }
+ }
+ }
+ }
+ else {
+ 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];
+ [ranges addObject:r];
+ }
+ }
+ }
+ }
+ }
+ return ranges;
+}
+
+- (NSCalendarDate *)lastInstanceStartDate {
+ if ([self->rrule repeatCount] > 0) {
+ long jnFirst, jnRuleLast;
+ NSCalendarDate *firStart, *until;
+
+ firStart = [self->firstRange startDate];
+ jnFirst = [firStart julianNumber];
+ jnRuleLast = ([self->rrule repeatInterval] *
+ [self->rrule repeatCount] * 7) +
+ jnFirst;
+ until = [NSCalendarDate dateForJulianNumber:jnRuleLast];
+ until = [until hour: [firStart hourOfDay]
+ minute:[firStart minuteOfHour]
+ second:[firStart secondOfMinute]];
+ return until;
+ }
+ return [super lastInstanceStartDate];
}
@end /* iCalWeeklyRecurrenceCalculator */
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 = [NSMutableArray arrayWithCapacity:count];
for (i = 0 ; i < count; i++) {
unsigned test;
-
+
test = diff + i;
if ((test % interval) == 0) {
NSCalendarDate *start, *end;
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];