Monotone-Parent: 4e8caa9222644e6820515bbf312f74dbd372c2fc

Monotone-Revision: 25c172f93ef4ab6ada0aeb6ff4267e98aef8b719

Monotone-Author: wsourdeau@inverse.ca
Monotone-Date: 2008-12-10T18:57:55
Monotone-Branch: ca.inverse.sogo
maint-2.0.2
Wolfgang Sourdeau 2008-12-10 18:57:55 +00:00
parent b8b4c6b5d3
commit ff33d95a42
4 changed files with 526 additions and 469 deletions

View File

@ -128,25 +128,22 @@
return ranges; return ranges;
} }
- (NSCalendarDate *) lastInstanceStartDate - (NSCalendarDate *) lastInstanceStartDate
{ {
NSCalendarDate *firStart, *lastInstanceStartDate;
if ([rrule repeatCount] > 0) if ([rrule repeatCount] > 0)
{ {
long jnFirst, jnRuleLast;
NSCalendarDate *firStart, *until;
firStart = [firstRange startDate]; firStart = [firstRange startDate];
jnFirst = [firStart julianNumber];
jnRuleLast = ([rrule repeatInterval] * lastInstanceStartDate = [firStart dateByAddingYears: 0 months: 0
[rrule repeatCount]) + days: ([rrule repeatInterval]
jnFirst; * [rrule repeatCount])];
until = [NSCalendarDate dateForJulianNumber:jnRuleLast];
until = [until hour: [firStart hourOfDay]
minute: [firStart minuteOfHour]
second: [firStart secondOfMinute]];
return until;
} }
return [super lastInstanceStartDate]; else
lastInstanceStartDate = [super lastInstanceStartDate];
return lastInstanceStartDate;
} }
@end /* iCalDailyRecurrenceCalculator */ @end /* iCalDailyRecurrenceCalculator */

View File

@ -32,8 +32,10 @@
#import "NSCalendarDate+ICal.h" #import "NSCalendarDate+ICal.h"
#import <string.h> #import <string.h>
@interface iCalRecurrenceCalculator(PrivateAPI) @interface iCalRecurrenceCalculator (PrivateAPI)
- (NSCalendarDate *)lastInstanceStartDate;
- (NSCalendarDate *) lastInstanceStartDate;
@end @end
// #define HEAVY_DEBUG 1 // #define HEAVY_DEBUG 1
@ -44,7 +46,8 @@ typedef BOOL NGMonthSet[12];
typedef BOOL NGMonthDaySet[32]; // 0 is unused typedef BOOL NGMonthDaySet[32]; // 0 is unused
static void static void
NGMonthDaySet_clear(NGMonthDaySet *daySet) NGMonthDaySet_clear (NGMonthDaySet *daySet)
{ {
register unsigned i; register unsigned i;
@ -53,70 +56,81 @@ NGMonthDaySet_clear(NGMonthDaySet *daySet)
} }
static void static void
NGMonthDaySet_copyOrUnion(NGMonthDaySet *base, NGMonthDaySet *new, NGMonthDaySet_copyOrUnion (NGMonthDaySet *base, NGMonthDaySet *new,
BOOL doCopy) BOOL doCopy)
{ {
register unsigned i; register unsigned i;
if (doCopy) if (doCopy)
memcpy(base, new, sizeof(NGMonthDaySet)); memcpy (base, new, sizeof (NGMonthDaySet));
else { else
for (i = 1; i <= 31; i++) { {
if (!(*new)[i]) for (i = 1; i <= 31; i++)
(*base)[i] = NO; {
if (! (*new)[i])
(*base)[i] = NO;
}
} }
}
} }
static BOOL NGMonthDaySet_fillWithByMonthDay(NGMonthDaySet *daySet, static BOOL NGMonthDaySet_fillWithByMonthDay (NGMonthDaySet *daySet,
NSArray *byMonthDay) NSArray *byMonthDay)
{ {
/* list of days in the month */ /* list of days in the month */
unsigned i, count; unsigned i, count;
BOOL ok; BOOL ok;
NGMonthDaySet_clear(daySet); NGMonthDaySet_clear (daySet);
for (i = 0, count = [byMonthDay count], ok = YES; i < count; i++) { for (i = 0, count = [byMonthDay count], ok = YES; i < count; i++)
int dayInMonth; /* -31..-1 and 1..31 */ {
int dayInMonth; /* -31..-1 and 1..31 */
if ((dayInMonth = [[byMonthDay objectAtIndex:i] intValue]) == 0) { if ((dayInMonth = [[byMonthDay objectAtIndex: i] intValue]) == 0)
ok = NO; {
continue; /* invalid value */ ok = NO;
} continue; /* invalid value */
if (dayInMonth > 31) { }
ok = NO; if (dayInMonth > 31)
continue; /* error, value to large */ {
} ok = NO;
if (dayInMonth < -31) { continue; /* error, value to large */
ok = NO; }
continue; /* error, value to large */ if (dayInMonth < -31)
} {
ok = NO;
continue; /* error, value to large */
}
/* adjust negative days */ /* adjust negative days */
if (dayInMonth < 0) { if (dayInMonth < 0)
/* eg: -1 == last day in month, 30 days => 30 */ {
dayInMonth = 32 - dayInMonth /* because we count from 1 */; /* eg: -1 == last day in month, 30 days => 30 */
} dayInMonth = 32 - dayInMonth /* because we count from 1 */;
}
(*daySet)[dayInMonth] = YES; (*daySet)[dayInMonth] = YES;
} }
return ok; return ok;
} }
static inline unsigned iCalDoWForNSDoW(int dow) { static inline unsigned iCalDoWForNSDoW (int dow)
switch (dow) { {
case 0: return iCalWeekDaySunday; switch (dow)
case 1: return iCalWeekDayMonday; {
case 2: return iCalWeekDayTuesday; case 0: return iCalWeekDaySunday;
case 3: return iCalWeekDayWednesday; case 1: return iCalWeekDayMonday;
case 4: return iCalWeekDayThursday; case 2: return iCalWeekDayTuesday;
case 5: return iCalWeekDayFriday; case 3: return iCalWeekDayWednesday;
case 6: return iCalWeekDaySaturday; case 4: return iCalWeekDayThursday;
case 7: return iCalWeekDaySunday; case 5: return iCalWeekDayFriday;
default: return 0; case 6: return iCalWeekDaySaturday;
} case 7: return iCalWeekDaySunday;
default: return 0;
}
} }
#if HEAVY_DEBUG #if HEAVY_DEBUG
@ -125,11 +139,12 @@ static NSString *dowEN[8] = {
}; };
#endif #endif
static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet, static void NGMonthDaySet_fillWithByDayX (NGMonthDaySet *daySet,
unsigned dayMask, unsigned dayMask,
unsigned firstDoWInMonth, unsigned firstDoWInMonth,
unsigned numberOfDaysInMonth, unsigned numberOfDaysInMonth,
int occurrence1) int occurrence1)
{ {
// TODO: this is called 'X' because the API doesn't allow for full iCalendar // TODO: this is called 'X' because the API doesn't allow for full iCalendar
// functionality. The daymask must be a list of occurence+dow // functionality. The daymask must be a list of occurence+dow
@ -137,78 +152,86 @@ static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet,
register int dow; /* current day of the week */ register int dow; /* current day of the week */
int occurrences[7] = { 0, 0, 0, 0, 0, 0, 0 } ; int occurrences[7] = { 0, 0, 0, 0, 0, 0, 0 } ;
NGMonthDaySet_clear(daySet); NGMonthDaySet_clear (daySet);
if (occurrence1 >= 0) { if (occurrence1 >= 0)
for (dayInMonth = 1, dow = firstDoWInMonth; dayInMonth<=31; dayInMonth++) { {
// TODO: complete me for (dayInMonth = 1, dow = firstDoWInMonth; dayInMonth<=31; dayInMonth++)
{
// TODO: complete me
if (dayMask & iCalDoWForNSDoW(dow)) { if (dayMask & iCalDoWForNSDoW (dow))
if (occurrence1 == 0) {
(*daySet)[dayInMonth] = YES; if (occurrence1 == 0)
else { /* occurrence1 > 0 */ (*daySet)[dayInMonth] = YES;
occurrences[dow] = occurrences[dow] + 1; else { /* occurrence1 > 0 */
occurrences[dow] = occurrences[dow] + 1;
if (occurrences[dow] == occurrence1) if (occurrences[dow] == occurrence1)
(*daySet)[dayInMonth] = YES; (*daySet)[dayInMonth] = YES;
} }
} }
dow = (dow == 6 /* Sat */) ? 0 /* Sun */ : (dow + 1); dow = (dow == 6 /* Sat */) ? 0 /* Sun */ : (dow + 1);
} }
} }
else { else
int lastDoWInMonthSet; {
int lastDoWInMonthSet;
/* get the last dow in the set (not necessarily the month!) */
for (dayInMonth = 1, dow = firstDoWInMonth; /* get the last dow in the set (not necessarily the month!) */
dayInMonth < numberOfDaysInMonth;dayInMonth++) for (dayInMonth = 1, dow = firstDoWInMonth;
dow = (dow == 6 /* Sat */) ? 0 /* Sun */ : (dow + 1); dayInMonth < numberOfDaysInMonth;dayInMonth++)
lastDoWInMonthSet = dow; dow = (dow == 6 /* Sat */) ? 0 /* Sun */ : (dow + 1);
lastDoWInMonthSet = dow;
#if HEAVY_DEBUG
NSLog(@"LAST DOW IN SET: %i / %@", #if HEAVY_DEBUG
lastDoWInMonthSet, dowEN[lastDoWInMonthSet]); NSLog (@"LAST DOW IN SET: %i / %@",
#endif lastDoWInMonthSet, dowEN[lastDoWInMonthSet]);
/* start at the end of the set */ #endif
for (dayInMonth = numberOfDaysInMonth, dow = lastDoWInMonthSet; /* start at the end of the set */
dayInMonth >= 1; dayInMonth--) { for (dayInMonth = numberOfDaysInMonth, dow = lastDoWInMonthSet;
// TODO: complete me dayInMonth >= 1; dayInMonth--)
{
#if HEAVY_DEBUG // TODO: complete me
NSLog(@" CHECK day-of-month %02i, "
@" dow=%i/%@ (first=%i/%@, last=%i/%@)", #if HEAVY_DEBUG
dayInMonth, NSLog (@" CHECK day-of-month %02i, "
dow, dowEN[dow], @" dow=%i/%@ (first=%i/%@, last=%i/%@)",
firstDoWInMonth, dowEN[firstDoWInMonth], dayInMonth,
lastDoWInMonthSet, dowEN[lastDoWInMonthSet] dow, dowEN[dow],
); firstDoWInMonth, dowEN[firstDoWInMonth],
#endif lastDoWInMonthSet, dowEN[lastDoWInMonthSet]
);
if (dayMask & iCalDoWForNSDoW(dow)) { #endif
occurrences[dow] = occurrences[dow] + 1;
#if HEAVY_DEBUG if (dayMask & iCalDoWForNSDoW (dow))
NSLog(@" MATCH %i/%@ count: %i occurences=%i", {
dow, dowEN[dow], occurrences[dow], occurrence1); occurrences[dow] = occurrences[dow] + 1;
#endif #if HEAVY_DEBUG
NSLog (@" MATCH %i/%@ count: %i occurences=%i",
if (occurrences[dow] == -occurrence1) { dow, dowEN[dow], occurrences[dow], occurrence1);
#if HEAVY_DEBUG #endif
NSLog(@" COUNT MATCH");
#endif if (occurrences[dow] == -occurrence1)
(*daySet)[dayInMonth] = YES; {
#if HEAVY_DEBUG
NSLog (@" COUNT MATCH");
#endif
(*daySet)[dayInMonth] = YES;
}
}
dow = (dow == 0 /* Sun */) ? 6 /* Sat */ : (dow - 1);
} }
}
dow = (dow == 0 /* Sun */) ? 6 /* Sat */ : (dow - 1);
} }
}
} }
- (BOOL)_addInstanceWithStartDate:(NSCalendarDate *)_startDate - (BOOL) _addInstanceWithStartDate: (NSCalendarDate *)_startDate
limitDate:(NSCalendarDate *)_until limitDate: (NSCalendarDate *)_until
limitRange:(NGCalendarDateRange *)_r limitRange: (NGCalendarDateRange *)_r
toArray:(NSMutableArray *)_ranges toArray: (NSMutableArray *)_ranges
{ {
NGCalendarDateRange *r; NGCalendarDateRange *r;
NSCalendarDate *end; NSCalendarDate *end;
@ -218,29 +241,32 @@ static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet,
// TODO: I think we should check in here whether we succeeded the // TODO: I think we should check in here whether we succeeded the
// repeatCount. Currently we precalculate that info in the // repeatCount. Currently we precalculate that info in the
// -lastInstanceStartDate method. // -lastInstanceStartDate method.
if (_until != nil) { if (_until != nil)
/* Note: the 'until' in the rrule is inclusive as per spec */ {
if ([_until compare:_startDate] == NSOrderedAscending) /* Note: the 'until' in the rrule is inclusive as per spec */
/* start after until */ if ([_until compare: _startDate] == NSOrderedAscending)
return NO; /* Note: we assume that the algorithm is sequential */ /* start after until */
} return NO; /* Note: we assume that the algorithm is sequential */
}
/* create end date */ /* create end date */
end = [_startDate addTimeInterval:[self->firstRange duration]]; end = [_startDate addTimeInterval: [firstRange duration]];
[end setTimeZone:[_startDate timeZone]]; [end setTimeZone: [_startDate timeZone]];
/* create range and check whether its in the requested range */ /* create range and check whether its in the requested range */
r = [[NGCalendarDateRange alloc] initWithStartDate:_startDate endDate:end]; r = [[NGCalendarDateRange alloc] initWithStartDate: _startDate endDate: end];
if ([_r containsDateRange:r]) if ([_r containsDateRange: r])
[_ranges addObject:r]; [_ranges addObject: r];
[r release]; r = nil; [r release]; r = nil;
return YES; return YES;
} }
- (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r{ - (NSArray *)
recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
{
/* main entry */ /* main entry */
// TODO: check whether this is OK for multiday-events! // TODO: check whether this is OK for multiday-events!
NSMutableArray *ranges; NSMutableArray *ranges;
@ -257,214 +283,225 @@ static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet,
NSArray *byMonthDay; // array of ints (-31..-1 and 1..31) NSArray *byMonthDay; // array of ints (-31..-1 and 1..31)
NGMonthDaySet byMonthDaySet; NGMonthDaySet byMonthDaySet;
eventStartDate = [self->firstRange startDate]; eventStartDate = [firstRange startDate];
eventDayOfMonth = [eventStartDate dayOfMonth]; eventDayOfMonth = [eventStartDate dayOfMonth];
timeZone = [eventStartDate timeZone]; timeZone = [eventStartDate timeZone];
rStart = [_r startDate]; rStart = [_r startDate];
rEnd = [_r endDate]; rEnd = [_r endDate];
interval = [self->rrule repeatInterval]; interval = [rrule repeatInterval];
until = [self lastInstanceStartDate]; // TODO: maybe replace until = [self lastInstanceStartDate]; // TODO: maybe replace
byMonthDay = [self->rrule byMonthDay]; byMonthDay = [rrule byMonthDay];
/* check whether the range to be processed is beyond the 'until' date */ /* check whether the range to be processed is beyond the 'until' date */
if (until) { if (until)
if ([until compare:rStart] == NSOrderedAscending) /* until before start */ {
return nil; if ([until compare: rStart] == NSOrderedAscending) /* until before start */
if ([until compare:rEnd] == NSOrderedDescending) /* end before until */ return nil;
rEnd = until; // TODO: why is that? end is _before_ until? if ([until compare: rEnd] == NSOrderedDescending) /* end before until */
} rEnd = until; // TODO: why is that? end is _before_ until?
}
/* precalculate month days (same for all instances) */ /* precalculate month days (same for all instances) */
if (byMonthDay) { if (byMonthDay)
{
#if HEAVY_DEBUG #if HEAVY_DEBUG
NSLog(@"byMonthDay: %@", byMonthDay); NSLog (@"byMonthDay: %@", byMonthDay);
#endif #endif
NGMonthDaySet_fillWithByMonthDay(&byMonthDaySet, byMonthDay); NGMonthDaySet_fillWithByMonthDay (&byMonthDaySet, byMonthDay);
} }
// TODO: I think the 'diff' is to skip recurrence which are before the // TODO: I think the 'diff' is to skip recurrence which are before the
// requested range. Not sure whether this is actually possible, eg // requested range. Not sure whether this is actually possible, eg
// the repeatCount must be processed from the start. // the repeatCount must be processed from the start.
diff = [eventStartDate monthsBetweenDate:rStart]; diff = [eventStartDate monthsBetweenDate: rStart];
if ((diff != 0) && [rStart compare:eventStartDate] == NSOrderedAscending) if ((diff != 0) && [rStart compare: eventStartDate] == NSOrderedAscending)
diff = -diff; diff = -diff;
numberOfMonthsInRange = [rStart monthsBetweenDate:rEnd] + 1; numberOfMonthsInRange = [rStart monthsBetweenDate: rEnd] + 1;
ranges = [NSMutableArray arrayWithCapacity:numberOfMonthsInRange]; ranges = [NSMutableArray arrayWithCapacity: numberOfMonthsInRange];
/* /*
Note: we do not add 'eventStartDate', this is intentional, the event date Note: we do not add 'eventStartDate', this is intentional, the event date
itself is _not_ necessarily part of the sequence, eg with monthly itself is _not_ necessarily part of the sequence, eg with monthly
byday recurrences. byday recurrences.
*/ */
for (monthIdxInRange = 0; monthIdxInRange < numberOfMonthsInRange; for (monthIdxInRange = 0; monthIdxInRange < numberOfMonthsInRange;
monthIdxInRange++) { monthIdxInRange++)
NSCalendarDate *cursor; {
unsigned numDaysInMonth; NSCalendarDate *cursor;
int monthIdxInRecurrence, dom; unsigned numDaysInMonth;
NGMonthDaySet monthDays; int monthIdxInRecurrence, dom;
BOOL didByFill, doCont; NGMonthDaySet monthDays;
BOOL didByFill, doCont;
monthIdxInRecurrence = diff + monthIdxInRange; monthIdxInRecurrence = diff + monthIdxInRange;
if (monthIdxInRecurrence < 0) if (monthIdxInRecurrence < 0)
continue; continue;
/* first check whether we are in the interval */ /* first check whether we are in the interval */
if ((monthIdxInRecurrence % interval) != 0) if ((monthIdxInRecurrence % interval) != 0)
continue; continue;
/* /*
Then the sequence is: Then the sequence is:
- check whether the month is in the BYMONTH list - check whether the month is in the BYMONTH list
*/ */
/* /*
Note: the function below adds exactly a month, eg: Note: the function below adds exactly a month, eg:
2007-01-30 + 1month => 2007-02-*28*!! 2007-01-30 + 1month => 2007-02-*28*!!
*/ */
cursor = [eventStartDate dateByAddingYears:0 cursor = [eventStartDate dateByAddingYears: 0
months:(diff + monthIdxInRange) months: (diff + monthIdxInRange)
days:0]; days: 0];
[cursor setTimeZone:timeZone]; [cursor setTimeZone: timeZone];
numDaysInMonth = [cursor numberOfDaysInMonth]; numDaysInMonth = [cursor numberOfDaysInMonth];
/* check whether we match the bymonth specification */ /* check whether we match the bymonth specification */
if (!byMonthList[[cursor monthOfYear] - 1]) if (!byMonthList[[cursor monthOfYear] - 1])
continue; continue;
/* check 'day level' byXYZ rules */ /* check 'day level' byXYZ rules */
didByFill = NO; didByFill = NO;
if (byMonthDay) { /* list of days in the month */ if (byMonthDay)
NGMonthDaySet_copyOrUnion(&monthDays, &byMonthDaySet, !didByFill); { /* list of days in the month */
didByFill = YES; NGMonthDaySet_copyOrUnion (&monthDays, &byMonthDaySet, !didByFill);
} didByFill = YES;
}
if ([self->rrule byDayMask] != 0) { // TODO: replace the mask with an array if ([rrule byDayMask] != 0)
NGMonthDaySet ruleset; { // TODO: replace the mask with an array
unsigned firstDoWInMonth; NGMonthDaySet ruleset;
unsigned firstDoWInMonth;
firstDoWInMonth = [[cursor firstDayOfMonth] dayOfWeek]; firstDoWInMonth = [[cursor firstDayOfMonth] dayOfWeek];
NGMonthDaySet_fillWithByDayX(&ruleset, NGMonthDaySet_fillWithByDayX (&ruleset,
[self->rrule byDayMask], [rrule byDayMask],
firstDoWInMonth, firstDoWInMonth,
[cursor numberOfDaysInMonth], [cursor numberOfDaysInMonth],
[self->rrule byDayOccurence1]); [rrule byDayOccurence1]);
NGMonthDaySet_copyOrUnion(&monthDays, &ruleset, !didByFill); NGMonthDaySet_copyOrUnion (&monthDays, &ruleset, !didByFill);
didByFill = YES; didByFill = YES;
} }
if (!didByFill) { if (!didByFill)
/* no rules applied, take the dayOfMonth of the startDate */ {
NGMonthDaySet_clear(&monthDays); /* no rules applied, take the dayOfMonth of the startDate */
monthDays[eventDayOfMonth] = YES; NGMonthDaySet_clear (&monthDays);
} monthDays[eventDayOfMonth] = YES;
}
// TODO: add processing of byhour/byminute/bysecond etc // TODO: add processing of byhour/byminute/bysecond etc
/* /*
Next step is to create NSCalendarDate instances from our 'monthDays' Next step is to create NSCalendarDate instances from our 'monthDays'
set. We walk over each day of the 'monthDays' set. If its flag isn't set. We walk over each day of the 'monthDays' set. If its flag isn't
set, we continue. set, we continue.
If its set, we add the date to the instance. If its set, we add the date to the instance.
The 'cursor' is the *startdate* of the event (not necessarily a The 'cursor' is the *startdate* of the event (not necessarily a
component of the sequence!) plus the currently processed month. component of the sequence!) plus the currently processed month.
Eg: Eg:
startdate: 2007-01-30 startdate: 2007-01-30
cursor[1]: 2007-01-30 cursor[1]: 2007-01-30
cursor[2]: 2007-02-28 <== Note: we have February! cursor[2]: 2007-02-28 <== Note: we have February!
*/ */
for (dom = 1, doCont = YES; dom <= numDaysInMonth && doCont; dom++) { for (dom = 1, doCont = YES; dom <= numDaysInMonth && doCont; dom++)
NSCalendarDate *start; {
NSCalendarDate *start;
if (!monthDays[dom]) if (!monthDays[dom])
continue; continue;
// TODO: what is this good for? // TODO: what is this good for?
/* /*
Here we need to correct the date. Remember that the startdate given in Here we need to correct the date. Remember that the startdate given in
the event is not necessarily a date of the sequence! the event is not necessarily a date of the sequence!
The 'numDaysInMonth' localvar contains the number of days in the The 'numDaysInMonth' localvar contains the number of days in the
current month (eg 31 for Januar, 28 for most February's, etc) current month (eg 31 for Januar, 28 for most February's, etc)
Eg: MONTHLY;BYDAY=-1WE (last wednesday, every month) Eg: MONTHLY;BYDAY=-1WE (last wednesday, every month)
cursor: 2007-01-30 (eventDayOfMonth = 30) cursor: 2007-01-30 (eventDayOfMonth = 30)
=>start: 2007-01-31 (dom = 31) =>start: 2007-01-31 (dom = 31)
cursor: 2007-02-28 (eventDayOfMonth = 30) cursor: 2007-02-28 (eventDayOfMonth = 30)
=>start: 2007-02-28 (dom = 28) =>start: 2007-02-28 (dom = 28)
Note: in case the cursor already had an event-day overflow, that is the Note: in case the cursor already had an event-day overflow, that is the
'eventDayOfMonth' is bigger than the 'numDaysInMonth', the cursor 'eventDayOfMonth' is bigger than the 'numDaysInMonth', the cursor
will already be corrected! will already be corrected!
Eg: Eg:
start was: 2007-01-30 start was: 2007-01-30
cursor will be: 2007-02-28 cursor will be: 2007-02-28
*/ */
if (eventDayOfMonth == dom) { if (eventDayOfMonth == dom)
start = cursor; {
} start = cursor;
else { }
int maxDay = else
eventDayOfMonth > numDaysInMonth ? numDaysInMonth : eventDayOfMonth; {
int maxDay =
eventDayOfMonth > numDaysInMonth ? numDaysInMonth : eventDayOfMonth;
start = [cursor dateByAddingYears:0 months:0 days:(dom - maxDay)]; start = [cursor dateByAddingYears: 0 months: 0 days: (dom - maxDay)];
} }
/* /*
Setup for 2007-02-28, MONTHLY;BYDAY=-1WE. Setup for 2007-02-28, MONTHLY;BYDAY=-1WE.
dom: 28 dom: 28
eventDayOfMonth: 31 eventDayOfMonth: 31
cursor: 2007-02-28 cursor: 2007-02-28
start: 2007-02-25 <== WRONG start: 2007-02-25 <== WRONG
*/ */
#if HEAVY_DEBUG #if HEAVY_DEBUG
NSLog(@"DOM %i EDOM %i NUMDAYS %i START: %@ CURSOR: %@", NSLog (@"DOM %i EDOM %i NUMDAYS %i START: %@ CURSOR: %@",
dom, eventDayOfMonth, numDaysInMonth, dom, eventDayOfMonth, numDaysInMonth,
start, cursor); start, cursor);
#endif #endif
doCont = [self _addInstanceWithStartDate:start doCont = [self _addInstanceWithStartDate: start
limitDate:until limitDate: until
limitRange:_r limitRange: _r
toArray:ranges]; toArray: ranges];
}
if (!doCont) break; /* reached some limit */
} }
if (!doCont) break; /* reached some limit */
}
return ranges; return ranges;
} }
- (NSCalendarDate *)lastInstanceStartDate { - (NSCalendarDate *) lastInstanceStartDate
if ([self->rrule repeatCount] > 0) { {
NSCalendarDate *until; if ([rrule repeatCount] > 0)
unsigned months, interval; {
NSCalendarDate *until;
unsigned months, interval;
interval = [self->rrule repeatInterval]; interval = [rrule repeatInterval];
months = [self->rrule repeatCount] - 1 /* the first counts as one! */; months = [rrule repeatCount] - 1 /* the first counts as one! */;
if (interval > 0) if (interval > 0)
months *= interval; months *= interval;
until = [[self->firstRange startDate] dateByAddingYears:0 until = [[firstRange startDate] dateByAddingYears: 0
months:months months: months
days:0]; days: 0];
return until; return until;
} }
return [super lastInstanceStartDate]; return [super lastInstanceStartDate];
} }

View File

@ -1,20 +1,20 @@
/* /*
Copyright (C) 2004-2005 SKYRIX Software AG Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of SOPE. This file is part of SOPE.
SOPE is free software; you can redistribute it and/or modify it under SOPE is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any Free Software Foundation; either version 2, or (at your option) any
later version. later version.
SOPE is distributed in the hope that it will be useful, but WITHOUT ANY SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details. License for more details.
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with SOPE; see the file COPYING. If not, write to the License along with SOPE; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. 02111-1307, USA.
*/ */
@ -34,17 +34,17 @@
- (NSCalendarDate *) lastInstanceStartDate; - (NSCalendarDate *) lastInstanceStartDate;
- (unsigned) offsetFromSundayForJulianNumber:(long)_jn; - (unsigned) offsetFromSundayForJulianNumber: (long) _jn;
- (unsigned) offsetFromSundayForWeekDay:(iCalWeekDay)_weekDay; - (unsigned) offsetFromSundayForWeekDay: (iCalWeekDay) _weekDay;
- (unsigned) offsetFromSundayForCurrentWeekStart; - (unsigned) offsetFromSundayForCurrentWeekStart;
- (iCalWeekDay) weekDayForJulianNumber:(long)_jn; - (iCalWeekDay) weekDayForJulianNumber: (long) _jn;
@end @end
/* /*
TODO: If BYDAY is specified, lastInstanceStartDate and recurrences will TODO: If BYDAY is specified, lastInstanceStartDate and recurrences will
differ significantly! differ significantly!
*/ */
@implementation iCalWeeklyRecurrenceCalculator @implementation iCalWeeklyRecurrenceCalculator
@ -53,140 +53,156 @@
{ {
NSMutableArray *ranges; NSMutableArray *ranges;
NSCalendarDate *firStart; NSCalendarDate *firStart;
long i, jnFirst, jnStart, jnEnd, startEndCount; long i, jnFirst, jnStart, jnEnd, startEndCount;
unsigned interval, byDayMask; unsigned interval, byDayMask;
firStart = [self->firstRange startDate]; firStart = [firstRange startDate];
jnFirst = [firStart julianNumber]; jnFirst = [firStart julianNumber];
jnEnd = [[_r endDate] julianNumber]; jnEnd = [[_r endDate] julianNumber];
if (jnFirst > jnEnd) if (jnFirst > jnEnd)
return nil; return nil;
jnStart = [[_r startDate] julianNumber]; jnStart = [[_r startDate] julianNumber];
interval = [self->rrule repeatInterval]; interval = [rrule repeatInterval];
/* if rule is bound, check the bounds */ /* if rule is bound, check the bounds */
if (![self->rrule isInfinite]) { if (![rrule isInfinite])
NSCalendarDate *until; {
long jnRuleLast; NSCalendarDate *until;
long jnRuleLast;
until = [self->rrule untilDate];
if (until) { until = [rrule untilDate];
if ([until compare:[_r startDate]] == NSOrderedAscending) if (until)
return nil; {
jnRuleLast = [until julianNumber]; if ([until compare: [_r startDate]] == NSOrderedAscending)
return nil;
jnRuleLast = [until julianNumber];
}
else
{
jnRuleLast = (interval * [rrule repeatCount] * 7)
+ jnFirst;
if (jnRuleLast < jnStart)
return nil;
}
/* jnStart < jnRuleLast < jnEnd ? */
if (jnEnd > jnRuleLast)
jnEnd = jnRuleLast;
} }
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; startEndCount = (jnEnd - jnStart) + 1;
ranges = [NSMutableArray arrayWithCapacity:startEndCount]; ranges = [NSMutableArray arrayWithCapacity: startEndCount];
byDayMask = [self->rrule byDayMask]; byDayMask = [rrule byDayMask];
if (!byDayMask) { if (!byDayMask)
for (i = 0 ; i < startEndCount; i++) { {
long jnCurrent; for (i = 0 ; i < startEndCount; i++)
{
jnCurrent = jnStart + i; long jnCurrent;
if (jnCurrent >= jnFirst) {
long jnDiff; jnCurrent = jnStart + i;
if (jnCurrent >= jnFirst)
jnDiff = jnCurrent - jnFirst; /* difference in days */ {
if ((jnDiff % (interval * 7)) == 0) { long jnDiff;
NSCalendarDate *start, *end;
NGCalendarDateRange *r; jnDiff = jnCurrent - jnFirst; /* difference in days */
if ((jnDiff % (interval * 7)) == 0)
start = [NSCalendarDate dateForJulianNumber:jnCurrent]; {
[start setTimeZone:[firStart timeZone]]; NSCalendarDate *start, *end;
start = [start hour: [firStart hourOfDay] NGCalendarDateRange *r;
minute:[firStart minuteOfHour]
second:[firStart secondOfMinute]]; start = [NSCalendarDate dateForJulianNumber: jnCurrent];
end = [start addTimeInterval:[self->firstRange duration]]; [start setTimeZone: [firStart timeZone]];
r = [NGCalendarDateRange calendarDateRangeWithStartDate:start start = [start hour: [firStart hourOfDay]
endDate:end]; minute: [firStart minuteOfHour]
if ([_r containsDateRange:r]) second: [firStart secondOfMinute]];
[ranges addObject:r]; end = [start addTimeInterval: [firstRange duration]];
} r = [NGCalendarDateRange calendarDateRangeWithStartDate: start
} endDate: end];
if ([_r containsDateRange: r])
[ranges addObject: r];
}
}
}
} }
} else
else { {
long jnFirstWeekStart, weekStartOffset; long jnFirstWeekStart, weekStartOffset;
/* calculate jnFirst's week start - this depends on our setting of week /* calculate jnFirst's week start - this depends on our setting of week
start */ start */
weekStartOffset = [self offsetFromSundayForJulianNumber:jnFirst] - weekStartOffset = [self offsetFromSundayForJulianNumber: jnFirst] -
[self offsetFromSundayForCurrentWeekStart]; [self offsetFromSundayForCurrentWeekStart];
jnFirstWeekStart = jnFirst - weekStartOffset; jnFirstWeekStart = jnFirst - weekStartOffset;
for (i = 0 ; i < startEndCount; i++) { for (i = 0 ; i < startEndCount; i++)
long jnCurrent; {
long jnCurrent;
jnCurrent = jnStart + i; jnCurrent = jnStart + i;
if (jnCurrent >= jnFirst) { if (jnCurrent >= jnFirst)
long jnDiff; {
long jnDiff;
/* we need to calculate a difference in weeks */
jnDiff = (jnCurrent - jnFirstWeekStart) % 7; /* we need to calculate a difference in weeks */
if ((jnDiff % interval) == 0) { jnDiff = (jnCurrent - jnFirstWeekStart) % 7;
BOOL isRecurrence = NO; if ((jnDiff % interval) == 0)
{
if (jnCurrent == jnFirst) { BOOL isRecurrence = NO;
isRecurrence = YES;
} if (jnCurrent == jnFirst)
else { {
iCalWeekDay weekDay; isRecurrence = YES;
}
else
{
iCalWeekDay weekDay;
weekDay = [self weekDayForJulianNumber:jnCurrent]; weekDay = [self weekDayForJulianNumber: jnCurrent];
isRecurrence = (weekDay & [self->rrule byDayMask]) ? YES : NO; isRecurrence = (weekDay & [rrule byDayMask]) ? YES : NO;
} }
if (isRecurrence) { if (isRecurrence)
NSCalendarDate *start, *end; {
NGCalendarDateRange *r; NSCalendarDate *start, *end;
NGCalendarDateRange *r;
start = [NSCalendarDate dateForJulianNumber:jnCurrent];
[start setTimeZone:[firStart timeZone]]; start = [NSCalendarDate dateForJulianNumber: jnCurrent];
start = [start hour: [firStart hourOfDay] [start setTimeZone: [firStart timeZone]];
minute:[firStart minuteOfHour] start = [start hour: [firStart hourOfDay]
second:[firStart secondOfMinute]]; minute: [firStart minuteOfHour]
end = [start addTimeInterval:[self->firstRange duration]]; second: [firStart secondOfMinute]];
r = [NGCalendarDateRange calendarDateRangeWithStartDate:start end = [start addTimeInterval: [firstRange duration]];
endDate:end]; r = [NGCalendarDateRange calendarDateRangeWithStartDate: start
if ([_r containsDateRange:r]) endDate: end];
[ranges addObject:r]; if ([_r containsDateRange: r])
} [ranges addObject: r];
} }
} }
}
}
} }
}
return ranges; return ranges;
} }
- (NSCalendarDate *)lastInstanceStartDate { - (NSCalendarDate *) lastInstanceStartDate
if ([self->rrule repeatCount] > 0) { {
long jnFirst, jnRuleLast; if ([rrule repeatCount] > 0)
NSCalendarDate *firStart, *until; {
long jnFirst, jnRuleLast;
firStart = [self->firstRange startDate]; NSCalendarDate *firStart, *until;
jnFirst = [firStart julianNumber];
jnRuleLast = ([self->rrule repeatInterval] * firStart = [firstRange startDate];
[self->rrule repeatCount] * 7) + jnFirst = [firStart julianNumber];
jnFirst; jnRuleLast = ([rrule repeatInterval] *
until = [NSCalendarDate dateForJulianNumber:jnRuleLast]; [rrule repeatCount] * 7) +
until = [until hour: [firStart hourOfDay] jnFirst;
minute:[firStart minuteOfHour] until = [NSCalendarDate dateForJulianNumber: jnRuleLast];
second:[firStart secondOfMinute]]; until = [until hour: [firStart hourOfDay]
return until; minute: [firStart minuteOfHour]
} second: [firStart secondOfMinute]];
return until;
}
return [super lastInstanceStartDate]; return [super lastInstanceStartDate];
} }

View File

@ -1,20 +1,20 @@
/* /*
Copyright (C) 2004-2005 SKYRIX Software AG Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of SOPE. This file is part of SOPE.
SOPE is free software; you can redistribute it and/or modify it under SOPE is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any Free Software Foundation; either version 2, or (at your option) any
later version. later version.
SOPE is distributed in the hope that it will be useful, but WITHOUT ANY SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details. License for more details.
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with SOPE; see the file COPYING. If not, write to the License along with SOPE; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. 02111-1307, USA.
*/ */
@ -30,71 +30,78 @@
#import "iCalRecurrenceRule.h" #import "iCalRecurrenceRule.h"
#import "NSCalendarDate+ICal.h" #import "NSCalendarDate+ICal.h"
@interface iCalRecurrenceCalculator(PrivateAPI) @interface iCalRecurrenceCalculator (PrivateAPI)
- (NSCalendarDate *)lastInstanceStartDate; - (NSCalendarDate *) lastInstanceStartDate;
@end @end
@implementation iCalYearlyRecurrenceCalculator @implementation iCalYearlyRecurrenceCalculator
- (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r{ - (NSArray *)
recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
{
NSMutableArray *ranges; NSMutableArray *ranges;
NSCalendarDate *firStart, *rStart, *rEnd, *until; NSCalendarDate *firStart, *rStart, *rEnd, *until;
unsigned i, count, interval; unsigned i, count, interval;
int diff; int diff;
firStart = [self->firstRange startDate]; firStart = [firstRange startDate];
rStart = [_r startDate]; rStart = [_r startDate];
rEnd = [_r endDate]; rEnd = [_r endDate];
interval = [self->rrule repeatInterval]; interval = [rrule repeatInterval];
until = [self lastInstanceStartDate]; until = [self lastInstanceStartDate];
if (until) { if (until)
if ([until compare:rStart] == NSOrderedAscending) {
return nil; if ([until compare: rStart] == NSOrderedAscending)
if ([until compare:rEnd] == NSOrderedDescending) return nil;
rEnd = until; if ([until compare: rEnd] == NSOrderedDescending)
} rEnd = until;
}
diff = [firStart yearsBetweenDate:rStart];
if ((diff != 0) && [rStart compare:firStart] == NSOrderedAscending) diff = [firStart yearsBetweenDate: rStart];
if ((diff != 0) && [rStart compare: firStart] == NSOrderedAscending)
diff = -diff; diff = -diff;
count = [rStart yearsBetweenDate:rEnd] + 1; count = [rStart yearsBetweenDate: rEnd] + 1;
ranges = [NSMutableArray arrayWithCapacity:count]; ranges = [NSMutableArray arrayWithCapacity: count];
for (i = 0 ; i < count; i++) { for (i = 0 ; i < count; i++)
int test; {
int test;
test = diff + i; test = diff + i;
if ((test >= 0) && (test % interval) == 0) { if ((test >= 0) && (test % interval) == 0)
NSCalendarDate *start, *end; {
NGCalendarDateRange *r; NSCalendarDate *start, *end;
NGCalendarDateRange *r;
start = [firStart dateByAddingYears:diff + i
months:0 start = [firStart dateByAddingYears: diff + i
days:0]; months: 0
[start setTimeZone:[firStart timeZone]]; days: 0];
end = [start addTimeInterval:[self->firstRange duration]]; [start setTimeZone: [firStart timeZone]];
r = [NGCalendarDateRange calendarDateRangeWithStartDate:start end = [start addTimeInterval: [firstRange duration]];
endDate:end]; r = [NGCalendarDateRange calendarDateRangeWithStartDate: start
if ([_r containsDateRange:r]) endDate: end];
[ranges addObject:r]; if ([_r containsDateRange: r])
[ranges addObject: r];
}
} }
}
return ranges; return ranges;
} }
- (NSCalendarDate *)lastInstanceStartDate { - (NSCalendarDate *) lastInstanceStartDate
if ([self->rrule repeatCount] > 0) { {
NSCalendarDate *until; if ([rrule repeatCount] > 0)
unsigned years, interval; {
NSCalendarDate *until;
interval = [self->rrule repeatInterval]; unsigned years, interval;
years = [self->rrule repeatCount] * interval;
until = [[self->firstRange startDate] dateByAddingYears:years interval = [rrule repeatInterval];
months:0 years = [rrule repeatCount] * interval;
days:0]; until = [[firstRange startDate] dateByAddingYears: years
return until; months: 0
} days: 0];
return until;
}
return [super lastInstanceStartDate]; return [super lastInstanceStartDate];
} }