Added proper support for BYxxx recurrent masks.

See ChangeLog

Monotone-Parent: 1388a39a062a16e073dca526237e1b25d5fa19d7
Monotone-Revision: 0e9f9326c164b5f5513aeb461785100c6c18fd0e

Monotone-Author: flachapelle@inverse.ca
Monotone-Date: 2010-04-19T21:05:35
Monotone-Branch: ca.inverse.sogo
maint-2.0.2
Francis Lachapelle 2010-04-19 21:05:35 +00:00
parent 97e9fb3e67
commit 6e1ee64101
16 changed files with 1105 additions and 646 deletions

View File

@ -2,7 +2,6 @@ config.make
shared_debug_obj
obj
err
log
build\.log
imgs-.*
diff

4
NEWS
View File

@ -4,10 +4,14 @@
- added support for LDAP password policies
- added support for custom Sieve filters
- fixed timezone issues occurring specifically in the southern hemisphere
- updated ckeditor to version 3.2
- tabs: enabled the scrolling when overflowing
- updated Czech translation, thanks to Milos Wimmer
- removed remaining .wo templates, thereby easing the effort for future translations
- fixed regressions with Courier IMAP and Dovecot
- added support for BYDAY with multiple values and negative positions
- added support for BYMONTHDAY with multiple values and negative positions
- added support for BYMONTH with multiple values
- added ability to delete events from a keypress
- added the "remove" command to "sogo-tool", in order to remove user data and settings
- added the ability to export address books in LDIF format from the web interface

View File

@ -1,3 +1,41 @@
2010-04-19 Francis Lachapelle <flachapelle@inverse.ca>
* iCalByDayMask.[h|m]: new class that computes complex BYDAY constraints.
* iCalRecurrenceRule.m (-byDayMask): new method that returns an
instance of iCalByDayMask.
(-byMonth): new method that returns an array with the values of
the BYMONTH constraint.
(-hasByMask): new method that returns true if the rule has at
least one BYxxx mask defined.
* iCalDailyRecurrenceCalculator.m
(-recurrenceRangesWithinCalendarDateRange:): make use of the new
iCalByDayMask class.
(-lastInstanceStartDate): make use of the previous method when a
BYxxx mask is defined in the rule.
* iCalWeeklyRecurrenceCalculator.m
(-recurrenceRangesWithinCalendarDateRange:): make use of the new
iCalByDayMask class.
(-lastInstanceStartDate): make use of the previous method when a
BYxxx mask is defined in the rule.
* iCalMonthlyRecurrenceCalculator.m
(-recurrenceRangesWithinCalendarDateRange:): added support for
BYMONTH, negative BYMONTHDAY, and BYDAY.
(-lastInstanceStartDate): make use of the previous method when a
BYxxx mask is defined in the rule.
* iCalYearlyRecurrenceCalculator.m
(-recurrenceRangesWithinCalendarDateRange:): added support for
BYxxx constraints using the iCalMonthlyRecurrenceCalculator class.
(-lastInstanceStartDate): make use of the previous method when a
BYxxx mask is defined in the rule.
* iCalTimeZonePeriod.m (-_occurenceForDate:byRRule:): make use of
the new iCalByDayMask class.
2010-04-09 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* iCalEvent.m (-propertyValue:): new method that accept a

View File

@ -31,6 +31,7 @@ libNGCards_HEADER_FILES = \
NGCards.h \
iCalAlarm.h \
iCalAttachment.h \
iCalByDayMask.h \
iCalCalendar.h \
iCalDataSource.h \
iCalDateTime.h \
@ -78,6 +79,7 @@ libNGCards_OBJC_FILES = \
\
iCalAlarm.m \
iCalAttachment.m \
iCalByDayMask.m \
iCalCalendar.m \
iCalDailyRecurrenceCalculator.m \
iCalDateTime.m \

View File

@ -1,6 +1,7 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2006-2010 Inverse inc.
This file is part of SOPE.
SOPE is free software; you can redistribute it and/or modify it under
@ -29,6 +30,7 @@
#import "iCalRecurrenceCalculator.h"
#import "iCalRecurrenceRule.h"
#import "iCalByDayMask.h"
@interface iCalDailyRecurrenceCalculator : iCalRecurrenceCalculator
@end
@ -39,71 +41,122 @@
@implementation iCalDailyRecurrenceCalculator
/**
* TODO : Unsupported conditions for DAILY recurrences :
*
* BYYEAR
* BYYEARDAY
* BYWEEKNO
* BYMONTH
* BYMONTHDAY
* BYHOUR
* BYMINUTE
*
* There's no GUI to defined such conditions, so there's no
* problem for now.
*/
- (NSArray *)
recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
{
NSMutableArray *ranges;
NSCalendarDate *firStart, *startDate, *endDate, *currentStartDate, *currentEndDate;
long i;
iCalByDayMask *dayMask;
long i, count, repeatCount;
unsigned interval;
firStart = [firstRange startDate];
startDate = [_r startDate];
endDate = [_r endDate];
dayMask = nil;
repeatCount = 0;
if ([endDate compare: firStart] == NSOrderedAscending)
// Range ends before first occurrence
return nil;
interval = [rrule repeatInterval];
if ([[rrule byDay] length])
dayMask = [rrule byDayMask];
// If rule is bound, check the bounds
if (![rrule isInfinite])
if (![rrule isInfinite])
{
NSCalendarDate *until, *lastDate;
lastDate = nil;
until = [rrule untilDate];
if (until)
lastDate = until;
if (until)
{
lastDate = until;
}
else
lastDate = [firStart dateByAddingYears: 0 months: 0
days: (interval
* ([rrule repeatCount] - 1))];
if ([lastDate compare: startDate] == NSOrderedAscending)
// Range starts after last occurrence
return nil;
{
repeatCount = [rrule repeatCount];
if (dayMask == nil)
// If there's no day mask, we can compute the date of the last
// occurrence of the recurrent rule.
lastDate = [firStart dateByAddingYears: 0 months: 0
days: (interval
* (repeatCount - 1))];
}
if ([lastDate compare: endDate] == NSOrderedAscending)
// Range ends after last occurence; adjust end date
endDate = lastDate;
if (lastDate != nil)
{
if ([lastDate compare: startDate] == NSOrderedAscending)
// Range starts after last occurrence
return nil;
if ([lastDate compare: endDate] == NSOrderedAscending)
// Range ends after last occurence; adjust end date
endDate = lastDate;
}
}
currentStartDate = [firStart copy];
[currentStartDate autorelease];
ranges = [NSMutableArray array];
i = 1;
count = 0;
while ([currentStartDate compare: endDate] == NSOrderedAscending ||
[currentStartDate compare: endDate] == NSOrderedSame)
{
if ([startDate compare: currentStartDate] == NSOrderedAscending ||
[startDate compare: currentStartDate] == NSOrderedSame)
BOOL wrongDay, isFirStart;
wrongDay = NO;
isFirStart = NO;
if (i == 1)
{
isFirStart = YES;
count++;
}
else if (repeatCount > 0 && dayMask)
{
// If the rule count is defined, stop once the count is reached.
if ([dayMask occursOnDay: [currentStartDate dayOfWeek]])
count++;
else
wrongDay = YES;
if (count > repeatCount)
break;
}
if (wrongDay == NO &&
([startDate compare: currentStartDate] == NSOrderedAscending ||
[startDate compare: currentStartDate] == NSOrderedSame))
{
BOOL wrongDay = NO;
unsigned int mask;
NGCalendarDateRange *r;
if ([rrule byDayMask])
if (isFirStart == NO && dayMask && repeatCount == 0)
{
mask = ([currentStartDate dayOfWeek]
? (unsigned int) 1 << ([currentStartDate dayOfWeek])
: iCalWeekDaySunday);
if (([rrule byDayMask] & mask) != mask)
if (![dayMask occursOnDay: [currentStartDate dayOfWeek]])
wrongDay = YES;
}
if (wrongDay == NO)
if (isFirStart == YES || wrongDay == NO)
{
currentEndDate = [currentStartDate addTimeInterval: [firstRange duration]];
r = [NGCalendarDateRange calendarDateRangeWithStartDate: currentStartDate
@ -115,6 +168,13 @@
currentStartDate = [firStart dateByAddingYears: 0 months: 0
days: (interval * i)];
if (repeatCount > 0 && count == repeatCount)
// The count variable is only usefull when a BYDAY constraint is
// defined; when there's no BYDAY constraint, the endDate has been
// adjusted to match the repeat count, if defined.
break;
i++;
}
return ranges;
@ -123,14 +183,29 @@
- (NSCalendarDate *) lastInstanceStartDate
{
NSCalendarDate *firStart, *lastInstanceStartDate;
NGCalendarDateRange *r;
NSArray *instances;
lastInstanceStartDate = nil;
if ([rrule repeatCount] > 0)
{
firStart = [firstRange startDate];
lastInstanceStartDate = [firStart dateByAddingYears: 0 months: 0
days: ([rrule repeatInterval]
* ([rrule repeatCount] - 1))];
if ([rrule hasByMask])
{
// Must perform the complete calculation
firStart = [firstRange startDate];
r = [NGCalendarDateRange calendarDateRangeWithStartDate: firStart
endDate: [NSCalendarDate distantFuture]];
instances = [self recurrenceRangesWithinCalendarDateRange: r];
if ([instances count])
lastInstanceStartDate = [(NGCalendarDateRange *)[instances lastObject] startDate];
}
else
{
// No BYxxx mask
lastInstanceStartDate = [firStart dateByAddingYears: 0 months: 0
days: ([rrule repeatInterval]
* ([rrule repeatCount] - 1))];
}
}
else
lastInstanceStartDate = [super lastInstanceStartDate];

View File

@ -1,6 +1,7 @@
/*
Copyright (C) 2004-2007 SKYRIX Software AG
Copyright (C) 2007 Helge Hess
Copyright (C) 2010 Inverse inc.
This file is part of SOPE.
@ -29,8 +30,11 @@
#import <NGExtensions/NGCalendarDateRange.h>
#import "iCalRecurrenceRule.h"
#import "iCalByDayMask.h"
#import "NSCalendarDate+ICal.h"
#import <string.h>
#import <math.h>
@interface iCalRecurrenceCalculator (PrivateAPI)
@ -74,15 +78,20 @@ NGMonthDaySet_copyOrUnion (NGMonthDaySet *base, NGMonthDaySet *new,
}
}
static BOOL NGMonthDaySet_fillWithByMonthDay (NGMonthDaySet *daySet,
/**
* This method fills split the positions of the BYMONTHDAY constraints
* into two separate arrays: one for the positive positions and one for the
* negatives positions (converted to their absolute values).
*/
static BOOL NGMonthDaySet_fillWithByMonthDay (NGMonthDaySet *positiveDaySet,
NGMonthDaySet *negativeDaySet,
NSArray *byMonthDay)
{
/* list of days in the month */
unsigned i, count;
BOOL ok;
NGMonthDaySet_clear (daySet);
NGMonthDaySet_clear (positiveDaySet);
NGMonthDaySet_clear (negativeDaySet);
for (i = 0, count = [byMonthDay count], ok = YES; i < count; i++)
{
@ -98,7 +107,7 @@ static BOOL NGMonthDaySet_fillWithByMonthDay (NGMonthDaySet *daySet,
ok = NO;
continue; /* error, value to large */
}
if (dayInMonth < -31)
if (dayInMonth < -31)
{
ok = NO;
continue; /* error, value to large */
@ -107,19 +116,16 @@ static BOOL NGMonthDaySet_fillWithByMonthDay (NGMonthDaySet *daySet,
/* adjust negative days */
if (dayInMonth < 0)
{
/* eg: -1 == last day in month, 30 days => 30 */
dayInMonth = 32 - dayInMonth /* because we count from 1 */;
}
(*daySet)[dayInMonth] = YES;
(*negativeDaySet)[abs(dayInMonth)] = YES;
else
(*positiveDaySet)[dayInMonth] = YES;
}
return ok;
}
static inline unsigned iCalDoWForNSDoW (int dow)
{
switch (dow)
switch (dow)
{
case 0: return iCalWeekDaySunday;
case 1: return iCalWeekDayMonday;
@ -133,100 +139,6 @@ static inline unsigned iCalDoWForNSDoW (int dow)
}
}
#if HEAVY_DEBUG
static NSString *dowEN[8] = {
@"SU", @"MO", @"TU", @"WE", @"TH", @"FR", @"SA", @"SU-"
};
#endif
static void NGMonthDaySet_fillWithByDayX (NGMonthDaySet *daySet,
unsigned dayMask,
unsigned firstDoWInMonth,
unsigned numberOfDaysInMonth,
int occurrence1)
{
// TODO: this is called 'X' because the API doesn't allow for full iCalendar
// functionality. The daymask must be a list of occurence+dow
register unsigned dayInMonth;
register int dow; /* current day of the week */
int occurrences[7] = { 0, 0, 0, 0, 0, 0, 0 } ;
NGMonthDaySet_clear (daySet);
if (occurrence1 >= 0)
{
for (dayInMonth = 1, dow = firstDoWInMonth; dayInMonth <= 31; dayInMonth++)
{
// TODO: complete me
if (dayMask & iCalDoWForNSDoW (dow))
{
if (occurrence1 == 0)
(*daySet)[dayInMonth] = YES;
else { /* occurrence1 > 0 */
occurrences[dow] = occurrences[dow] + 1;
if (occurrences[dow] == occurrence1)
(*daySet)[dayInMonth] = YES;
}
}
dow = (dow == 6 /* Sat */) ? 0 /* Sun */ : (dow + 1);
}
}
else
{
int lastDoWInMonthSet;
/* get the last dow in the set (not necessarily the month!) */
for (dayInMonth = 1, dow = firstDoWInMonth;
dayInMonth < numberOfDaysInMonth;dayInMonth++)
dow = (dow == 6 /* Sat */) ? 0 /* Sun */ : (dow + 1);
lastDoWInMonthSet = dow;
#if HEAVY_DEBUG
NSLog (@"LAST DOW IN SET: %i / %@",
lastDoWInMonthSet, dowEN[lastDoWInMonthSet]);
#endif
/* start at the end of the set */
for (dayInMonth = numberOfDaysInMonth, dow = lastDoWInMonthSet;
dayInMonth >= 1; dayInMonth--)
{
// TODO: complete me
#if HEAVY_DEBUG
NSLog (@" CHECK day-of-month %02i, "
@" dow=%i/%@ (first=%i/%@, last=%i/%@)",
dayInMonth,
dow, dowEN[dow],
firstDoWInMonth, dowEN[firstDoWInMonth],
lastDoWInMonthSet, dowEN[lastDoWInMonthSet]
);
#endif
if (dayMask & iCalDoWForNSDoW (dow))
{
occurrences[dow] = occurrences[dow] + 1;
#if HEAVY_DEBUG
NSLog (@" MATCH %i/%@ count: %i occurences=%i",
dow, dowEN[dow], occurrences[dow], occurrence1);
#endif
if (occurrences[dow] == -occurrence1)
{
#if HEAVY_DEBUG
NSLog (@" COUNT MATCH");
#endif
(*daySet)[dayInMonth] = YES;
}
}
dow = (dow == 0 /* Sun */) ? 6 /* Sat */ : (dow - 1);
}
}
}
- (BOOL) _addInstanceWithStartDate: (NSCalendarDate *)_startDate
limitDate: (NSCalendarDate *)_until
limitRange: (NGCalendarDateRange *)_r
@ -238,10 +150,7 @@ static void NGMonthDaySet_fillWithByDayX (NGMonthDaySet *daySet,
/* check whether we are still in the limits */
// TODO: I think we should check in here whether we succeeded the
// repeatCount. Currently we precalculate that info in the
// -lastInstanceStartDate method.
if (_until != nil)
if (_until != nil)
{
/* Note: the 'until' in the rrule is inclusive as per spec */
if ([_until compare: _startDate] == NSOrderedAscending)
@ -259,29 +168,42 @@ static void NGMonthDaySet_fillWithByDayX (NGMonthDaySet *daySet,
r = [[NGCalendarDateRange alloc] initWithStartDate: _startDate endDate: end];
if ([_r containsDateRange: r])
[_ranges addObject: r];
[r release]; r = nil;
[r release];
r = nil;
return YES;
}
/**
* TODO : Unsupported conditions for MONTHLY recurrences :
*
* BYYEAR
* BYYEARDAY
* BYWEEKNO
* BYHOUR
* BYMINUTE
*
* There's no GUI to defined such conditions, so there's no
* problem for now.
*/
- (NSArray *)
recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
{
/* main entry */
// TODO: check whether this is OK for multiday-events!
NSMutableArray *ranges;
NSTimeZone *timeZone;
NSCalendarDate *eventStartDate, *rStart, *rEnd, *until;
NSCalendarDate *eventStartDate, *rStart, *rEnd, *until, *referenceDate;
int eventDayOfMonth;
unsigned monthIdxInRange, numberOfMonthsInRange, interval;
int diff;
NGMonthSet byMonthList = { // TODO: fill from rrule, this is the default
/* enable all months of the year */
unsigned monthIdxInRange, numberOfMonthsInRange, interval, repeatCount;
int diff, count;
NGMonthSet byMonthList = {
// Enable all months of the year
YES, YES, YES, YES, YES, YES,
YES, YES, YES, YES, YES, YES
};
NSArray *byMonthDay; // array of ints (-31..-1 and 1..31)
NGMonthDaySet byMonthDaySet;
NSArray *byMonth, *byMonthDay; // array of ints (-31..-1 and 1..31)
NGMonthDaySet byPositiveMonthDaySet, byNegativeMonthDaySet;
iCalByDayMask *byDayMask;
eventStartDate = [firstRange startDate];
eventDayOfMonth = [eventStartDate dayOfMonth];
@ -289,48 +211,81 @@ static void NGMonthDaySet_fillWithByDayX (NGMonthDaySet *daySet,
rStart = [_r startDate];
rEnd = [_r endDate];
interval = [rrule repeatInterval];
until = [self lastInstanceStartDate]; // TODO: maybe replace
until = nil;
repeatCount = [rrule repeatCount];
byMonth = [rrule byMonth];
byMonthDay = [rrule byMonthDay];
byDayMask = [rrule byDayMask];
diff = 0;
/* check whether the range to be processed is beyond the 'until' date */
if (until)
if (![rrule isInfinite])
{
if ([until compare: rStart] == NSOrderedAscending) /* until before start */
if (repeatCount > 0 && ![rrule hasByMask])
{
// When there's no BYxxx mask, we can find the date of the last
// occurrence.
until = [eventStartDate dateByAddingYears: 0
months: (interval * (repeatCount - 1))
days: 0];
}
else
{
until = [rrule untilDate];
}
}
if (until != nil)
{
if ([until compare: rStart] == NSOrderedAscending)
// Range starts after last occurrence
return nil;
if ([until compare: rEnd] == NSOrderedDescending) /* end before until */
rEnd = until; // TODO: why is that? end is _before_ until?
if ([until compare: rEnd] == NSOrderedDescending)
// Range ends after last occurence; adjust end date
rEnd = until;
}
/* precalculate month days (same for all instances) */
if (byMonth && [byMonth count] > 0)
{
int i;
for (i = 0; i <= 12; i++)
byMonthList[i] = [byMonth containsObject: [NSString stringWithFormat: @"%i", i + 1]];
}
if (byMonthDay)
/* precalculate month days */
if (byMonthDay)
{
#if HEAVY_DEBUG
NSLog (@"byMonthDay: %@", byMonthDay);
#endif
NGMonthDaySet_fillWithByMonthDay (&byMonthDaySet, byMonthDay);
NGMonthDaySet_fillWithByMonthDay (&byPositiveMonthDaySet, &byNegativeMonthDaySet, byMonthDay);
}
// TODO: I think the 'diff' is to skip recurrence which are before the
// requested range. Not sure whether this is actually possible, eg
// the repeatCount must be processed from the start.
diff = [eventStartDate monthsBetweenDate: rStart];
if ((diff != 0) && [rStart compare: eventStartDate] == NSOrderedAscending)
diff = -diff;
numberOfMonthsInRange = [rStart monthsBetweenDate: rEnd] + 1;
if (repeatCount > 0)
{
numberOfMonthsInRange = [eventStartDate monthsBetweenDate: rEnd] + 1;
}
else
{
diff = [eventStartDate monthsBetweenDate: rStart];
if ((diff != 0) && [rStart compare: eventStartDate] == NSOrderedAscending)
diff = -diff;
numberOfMonthsInRange = [rStart monthsBetweenDate: rEnd] + 1;
}
ranges = [NSMutableArray arrayWithCapacity: numberOfMonthsInRange];
/*
Note: we do not add 'eventStartDate', this is intentional, the event date
itself is _not_ necessarily part of the sequence, eg with monthly
byday recurrences.
*/
for (monthIdxInRange = 0; monthIdxInRange < numberOfMonthsInRange;
// There's a bug in GNUstep in [NSCalendarDate dateByAddingYears:months:days:]
// that causes errors when adding subsequently a month. For this reason,
// we set the day of the reference date to 1.
referenceDate = [NSCalendarDate dateWithYear: [eventStartDate yearOfCommonEra]
month: [eventStartDate monthOfYear]
day: 1
hour: [eventStartDate hourOfDay]
minute: [eventStartDate minuteOfHour]
second: 0
timeZone: [eventStartDate timeZone]];
for (monthIdxInRange = 0, count = 0;
monthIdxInRange < numberOfMonthsInRange;
monthIdxInRange++)
{
NSCalendarDate *cursor;
@ -349,63 +304,95 @@ static void NGMonthDaySet_fillWithByDayX (NGMonthDaySet *daySet,
if ((monthIdxInRecurrence % interval) != 0)
continue;
/*
Then the sequence is:
- check whether the month is in the BYMONTH list
*/
/*
Note: the function below adds exactly a month, eg:
2007-01-30 + 1month => 2007-02-*28*!!
*/
cursor = [eventStartDate dateByAddingYears: 0
months: (diff + monthIdxInRange)
days: 0];
cursor = [referenceDate dateByAddingYears: 0
months: monthIdxInRecurrence
days: 0];
[cursor setTimeZone: timeZone];
numDaysInMonth = [cursor numberOfDaysInMonth];
/* check whether we match the bymonth specification */
/* check whether we match the BYMONTH constraint */
if (!byMonthList[[cursor monthOfYear] - 1])
continue;
/* check 'day level' byXYZ rules */
/* check whether we match the BYMONTHDAY and BYDAY constraints */
didByFill = NO;
if (byMonthDay)
{ /* list of days in the month */
NGMonthDaySet_copyOrUnion (&monthDays, &byMonthDaySet, !didByFill);
didByFill = YES;
}
if ([rrule byDayMask] != 0)
{ // TODO: replace the mask with an array
NGMonthDaySet ruleset;
unsigned firstDoWInMonth;
firstDoWInMonth = [[cursor firstDayOfMonth] dayOfWeek];
NGMonthDaySet_fillWithByDayX (&ruleset,
[rrule byDayMask],
firstDoWInMonth,
[cursor numberOfDaysInMonth],
[rrule byDayOccurence1]);
NGMonthDaySet_copyOrUnion (&monthDays, &ruleset, !didByFill);
didByFill = YES;
}
if (!didByFill)
{
/* no rules applied, take the dayOfMonth of the startDate */
// Initialize the monthDays array with the positive days positions
NGMonthDaySet_copyOrUnion (&monthDays, &byPositiveMonthDaySet, !didByFill);
// Add to the array the days matching the negative days positions
int i;
for (i = 1; i <= 31; i++)
if (byNegativeMonthDaySet[i])
monthDays[numDaysInMonth - i + 1] = YES;
didByFill = YES;
}
if (byDayMask)
{
unsigned int firstDoWInMonth, currentWeekDay;
unsigned int weekDaysCount[7], currentWeekDaysCount[7];
int i, positiveOrder, negativeOrder;
firstDoWInMonth = [[cursor firstDayOfMonth] dayOfWeek];
if (!didByFill)
NGMonthDaySet_clear (&monthDays);
// Fill weekDaysCount to handle negative positions
currentWeekDay = firstDoWInMonth;
memset(weekDaysCount, 0, 7 * sizeof(unsigned int));
for (i = 1; i <= numDaysInMonth; i++)
{
weekDaysCount[currentWeekDay]++;
currentWeekDay++;
currentWeekDay = fmod (currentWeekDay, 7);
}
currentWeekDay = firstDoWInMonth;
memset(currentWeekDaysCount, 0, 7 * sizeof(unsigned int));
for (i = 1; i <= numDaysInMonth; i++)
{
if (!didByFill || monthDays[i])
{
positiveOrder = currentWeekDaysCount[currentWeekDay] + 1;
negativeOrder = currentWeekDaysCount[currentWeekDay] - weekDaysCount[currentWeekDay];
monthDays[i] = (([byDayMask occursOnDay: (iCalWeekDay)currentWeekDay
withWeekNumber: positiveOrder]) ||
([byDayMask occursOnDay: (iCalWeekDay)currentWeekDay
withWeekNumber: negativeOrder]));
}
currentWeekDaysCount[currentWeekDay]++;
currentWeekDay++;
currentWeekDay = fmod (currentWeekDay, 7);
}
didByFill = YES;
}
if (didByFill)
{
if (diff + monthIdxInRange == 0)
{
// When dealing with the month of the first occurence, remove days
// that occur before the first occurrence.
int i;
for (i = 1; i < eventDayOfMonth; i++)
monthDays[i] = NO;
// The first occurrence must always be included.
monthDays[i] = YES;
}
}
else
{
// No rules applied, take the dayOfMonth of the startDate
NGMonthDaySet_clear (&monthDays);
monthDays[eventDayOfMonth] = YES;
}
// TODO: add processing of byhour/byminute/bysecond etc
/*
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
@ -420,64 +407,25 @@ static void NGMonthDaySet_fillWithByDayX (NGMonthDaySet *daySet,
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;
if (!monthDays[dom])
continue;
// TODO: what is this good for?
/*
Here we need to correct the date. Remember that the startdate given in
the event is not necessarily a date of the sequence!
The 'numDaysInMonth' localvar contains the number of days in the
current month (eg 31 for January, 28 for most February's, etc)
Eg: MONTHLY;BYDAY=-1WE (last wednesday, every month)
cursor: 2007-01-30 (eventDayOfMonth = 30)
=>start: 2007-01-31 (dom = 31)
cursor: 2007-02-28 (eventDayOfMonth = 30)
=>start: 2007-02-28 (dom = 28)
Note: in case the cursor already had an event-day overflow, that is the
'eventDayOfMonth' is bigger than the 'numDaysInMonth', the cursor
will already be corrected!
Eg:
start was: 2007-01-30
cursor will be: 2007-02-28
*/
if (eventDayOfMonth == dom)
{
start = cursor;
}
else
{
int maxDay =
eventDayOfMonth > numDaysInMonth ? numDaysInMonth : eventDayOfMonth;
start = [cursor dateByAddingYears: 0 months: 0 days: (dom - maxDay)];
}
/*
Setup for 2007-02-28, MONTHLY;BYDAY=-1WE.
dom: 28
eventDayOfMonth: 31
cursor: 2007-02-28
start: 2007-02-25 <== WRONG
*/
#if HEAVY_DEBUG
NSLog (@"DOM %i EDOM %i NUMDAYS %i START: %@ CURSOR: %@",
dom, eventDayOfMonth, numDaysInMonth,
start, cursor);
#endif
start = [cursor dateByAddingYears: 0 months: 0 days: (dom - 1)];
doCont = [self _addInstanceWithStartDate: start
limitDate: until
limitRange: _r
toArray: ranges];
limitDate: until
limitRange: _r
toArray: ranges];
//NSLog(@"*** MONTHLY [%i/%i] adding %@%@ (count = %i)", dom, numDaysInMonth, start, (doCont?@"":@" .. NOT!"), count);
if (repeatCount > 0)
{
count++;
//NSLog(@"MONTHLY count = %i/%i", count, repeatCount);
doCont = (count < repeatCount);
}
}
if (!doCont) break; /* reached some limit */
}
@ -487,15 +435,30 @@ static void NGMonthDaySet_fillWithByDayX (NGMonthDaySet *daySet,
- (NSCalendarDate *) lastInstanceStartDate
{
NSCalendarDate *firStart, *lastInstanceStartDate;
NGCalendarDateRange *r;
NSArray *instances;
if ([rrule repeatCount] > 0)
lastInstanceStartDate = nil;
if ([rrule repeatCount] > 0)
{
firStart = [firstRange startDate];
lastInstanceStartDate = [firStart dateByAddingYears: 0
months: ([rrule repeatInterval]
* ([rrule repeatCount] - 1))
days: 0];
if ([rrule hasByMask])
{
// Must perform the complete calculation
firStart = [firstRange startDate];
r = [NGCalendarDateRange calendarDateRangeWithStartDate: firStart
endDate: [NSCalendarDate distantFuture]];
instances = [self recurrenceRangesWithinCalendarDateRange: r];
if ([instances count])
lastInstanceStartDate = [(NGCalendarDateRange *)[instances lastObject] startDate];
}
else
{
// No BYxxx mask
lastInstanceStartDate = [firStart dateByAddingYears: 0
months: ([rrule repeatInterval]
* ([rrule repeatCount] - 1))
days: 0];
}
}
else
lastInstanceStartDate = [super lastInstanceStartDate];

View File

@ -340,7 +340,6 @@ static Class yearlyCalcClass = Nil;
/*
NOTE: this is horribly inaccurate and doesn't even consider the use
of repeatCount. It MUST be implemented by subclasses properly!
However, it does the trick for SOGo 1.0 - that's why it's left here.
*/
return [rrule untilDate];
}

View File

@ -1,5 +1,6 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2006-2010 Inverse inc.
This file is part of SOPE.
@ -46,34 +47,25 @@ typedef enum {
} iCalRecurrenceFrequency;
typedef enum {
iCalWeekDaySunday = 1,
iCalWeekDayMonday = 2,
iCalWeekDayTuesday = 4,
iCalWeekDayWednesday = 8,
iCalWeekDayThursday = 16,
iCalWeekDayFriday = 32,
iCalWeekDaySaturday = 64,
iCalWeekDayUnknown = -1,
iCalWeekDaySunday = 0,
iCalWeekDayMonday = 1,
iCalWeekDayTuesday = 2,
iCalWeekDayWednesday = 3,
iCalWeekDayThursday = 4,
iCalWeekDayFriday = 5,
iCalWeekDaySaturday = 6
} iCalWeekDay;
extern NSString *iCalWeekDayString[];
@class NSString, NSCalendarDate, NGCalendarDateRange, NSArray;
@class iCalByDayMask;
@interface iCalRecurrenceRule : CardElement
// {
// iCalRecurrenceFrequency frequency;
// int interval;
// unsigned repeatCount;
// NSCalendarDate *untilDate;
// struct {
// unsigned weekStart: 7;
// unsigned mask: 7;
// unsigned useOccurence:1;
// unsigned reserved:1;
// } byDay;
// int byDayOccurence1;
// NSArray *byMonthDay;
// NSString *rrule;
// }
{
iCalByDayMask *dayMask;
}
+ (id) recurrenceRuleWithICalRepresentation: (NSString *) _iCalRep;
- (id) initWithString: (NSString *) _str;
@ -92,12 +84,14 @@ typedef enum {
- (void) setWeekStart: (iCalWeekDay) _weekStart;
- (iCalWeekDay) weekStart;
- (void) setByDayMask: (unsigned int) _mask;
- (unsigned int) byDayMask;
- (int) byDayOccurence1;
- (void) setByDay: (NSString *) newByDay;
- (NSString *) byDay;
- (void) setByDayMask: (iCalByDayMask *) newMask;
- (iCalByDayMask *) byDayMask;
- (NSArray *) byMonthDay;
- (NSArray *) byMonth;
- (BOOL) hasByMask;
/* count and untilDate are mutually exclusive */
- (void) setRepeatCount: (int) _repeatCount;
@ -112,8 +106,6 @@ typedef enum {
- (void) setRrule: (NSString *) _rrule; // TODO: weird name? (better: RRule?)
// - (NSString *)iCalRepresentation;
@end
#endif /* __NGiCal_iCalRecurrenceRule_H_ */

View File

@ -1,5 +1,6 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2006-2010 Inverse inc.
This file is part of SOPE.
@ -19,6 +20,168 @@
02111-1307, USA.
*/
/*
See http://tools.ietf.org/html/rfc2445#section-4.3.10
4.3.10 Recurrence Rule
Value Name: RECUR
Purpose: This value type is used to identify properties that contain
a recurrence rule specification.
Formal Definition: The value type is defined by the following
notation:
recur = "FREQ"=freq *(
; either UNTIL or COUNT may appear in a 'recur',
; but UNTIL and COUNT MUST NOT occur in the same 'recur'
( ";" "UNTIL" "=" enddate ) /
( ";" "COUNT" "=" 1*DIGIT ) /
; the rest of these keywords are optional,
; but MUST NOT occur more than once
( ";" "INTERVAL" "=" 1*DIGIT ) /
( ";" "BYSECOND" "=" byseclist ) /
( ";" "BYMINUTE" "=" byminlist ) /
( ";" "BYHOUR" "=" byhrlist ) /
( ";" "BYDAY" "=" bywdaylist ) /
( ";" "BYMONTHDAY" "=" bymodaylist ) /
( ";" "BYYEARDAY" "=" byyrdaylist ) /
( ";" "BYWEEKNO" "=" bywknolist ) /
( ";" "BYMONTH" "=" bymolist ) /
( ";" "BYSETPOS" "=" bysplist ) /
( ";" "WKST" "=" weekday ) /
( ";" x-name "=" text )
)
freq = "SECONDLY" / "MINUTELY" / "HOURLY" / "DAILY"
/ "WEEKLY" / "MONTHLY" / "YEARLY"
enddate = date
enddate =/ date-time ;An UTC value
byseclist = seconds / ( seconds *("," seconds) )
seconds = 1DIGIT / 2DIGIT ;0 to 59
byminlist = minutes / ( minutes *("," minutes) )
minutes = 1DIGIT / 2DIGIT ;0 to 59
byhrlist = hour / ( hour *("," hour) )
hour = 1DIGIT / 2DIGIT ;0 to 23
bywdaylist = weekdaynum / ( weekdaynum *("," weekdaynum) )
weekdaynum = [([plus] ordwk / minus ordwk)] weekday
plus = "+"
minus = "-"
ordwk = 1DIGIT / 2DIGIT ;1 to 53
weekday = "SU" / "MO" / "TU" / "WE" / "TH" / "FR" / "SA"
;Corresponding to SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY,
;FRIDAY, SATURDAY and SUNDAY days of the week.
bymodaylist = monthdaynum / ( monthdaynum *("," monthdaynum) )
monthdaynum = ([plus] ordmoday) / (minus ordmoday)
ordmoday = 1DIGIT / 2DIGIT ;1 to 31
byyrdaylist = yeardaynum / ( yeardaynum *("," yeardaynum) )
yeardaynum = ([plus] ordyrday) / (minus ordyrday)
ordyrday = 1DIGIT / 2DIGIT / 3DIGIT ;1 to 366
bywknolist = weeknum / ( weeknum *("," weeknum) )
weeknum = ([plus] ordwk) / (minus ordwk)
bymolist = monthnum / ( monthnum *("," monthnum) )
monthnum = 1DIGIT / 2DIGIT ;1 to 12
bysplist = setposday / ( setposday *("," setposday) )
setposday = yeardaynum
*/
/*
Examples :
Every other week on Monday, Wednesday and Friday until December 24,
1997, but starting on Tuesday, September 2, 1997:
DTSTART;TZID=US-Eastern:19970902T090000
RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;
BYDAY=MO,WE,FR
==> (1997 9:00 AM EDT)September 2,3,5,15,17,19,29;October
1,3,13,15,17
(1997 9:00 AM EST)October 27,29,31;November 10,12,14,24,26,28;
December 8,10,12,22
Monthly on the 1st Friday for ten occurrences:
DTSTART;TZID=US-Eastern:19970905T090000
RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=1FR
==> (1997 9:00 AM EDT)September 5;October 3
(1997 9:00 AM EST)November 7;Dec 5
(1998 9:00 AM EST)January 2;February 6;March 6;April 3
(1998 9:00 AM EDT)May 1;June 5
Every other month on the 1st and last Sunday of the month for 10
occurrences:
DTSTART;TZID=US-Eastern:19970907T090000
RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU
==> (1997 9:00 AM EDT)September 7,28
(1997 9:00 AM EST)November 2,30
Monthly on the third to the last day of the month, forever:
DTSTART;TZID=US-Eastern:19970928T090000
RRULE:FREQ=MONTHLY;BYMONTHDAY=-3
==> (1997 9:00 AM EDT)September 28
(1997 9:00 AM EST)October 29;November 28;December 29
(1998 9:00 AM EST)January 29;February 26
...
Every other year on January, February, and March for 10 occurrences:
DTSTART;TZID=US-Eastern:19970310T090000
RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3
==> (1997 9:00 AM EST)March 10
(1999 9:00 AM EST)January 10;February 10;March 10
(2001 9:00 AM EST)January 10;February 10;March 10
(2003 9:00 AM EST)January 10;February 10;March 10
Everyday in January, for 3 years:
DTSTART;TZID=US-Eastern:19980101T090000
RRULE:FREQ=YEARLY;UNTIL=20000131T090000Z;
BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA
or
RRULE:FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1
==> (1998 9:00 AM EDT)January 1-31
(1999 9:00 AM EDT)January 1-31
(2000 9:00 AM EDT)January 1-31
*/
#import <Foundation/NSArray.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSException.h>
@ -27,13 +190,16 @@
#import <ctype.h>
#import "NSCalendarDate+ICal.h"
#import "NSCalendarDate+NGCards.h"
#import "NSString+NGCards.h"
#import "NSCalendarDate+ICal.h"
#import "iCalByDayMask.h"
#import "iCalRecurrenceRule.h"
NSString *iCalWeekDayString[] = { @"SU", @"MO", @"TU", @"WE", @"TH", @"FR",
@"SA" };
/*
freq = rrFreq;
until = rrUntil;
@ -60,11 +226,6 @@
- (NSString *) wkst;
- (NSString *) byDayList;
// - (void)_parseRuleString:(NSString *)_rrule;
/* currently used by parser, should be removed (replace with an -init..) */
- (void)setByday:(NSString *)_byDayList;
@end
@implementation iCalRecurrenceRule
@ -85,6 +246,7 @@
if ((self = [super init]) != nil)
{
[self setTag: @"rrule"];
dayMask = nil;
}
return self;
@ -100,6 +262,12 @@
return self;
}
- (void) dealloc
{
[dayMask release];
[super dealloc];
}
- (void) setRrule: (NSString *) _rrule
{
NSEnumerator *newValues;
@ -273,54 +441,30 @@
return [self weekDayFromICalRepresentation: [self wkst]];
}
- (void) setByDayMask: (unsigned) _mask
- (void) setByDay: (NSString *) newByDay
{
NSMutableArray *days;
unsigned int count;
unsigned char maskDays[] = { iCalWeekDaySunday, iCalWeekDayMonday,
iCalWeekDayTuesday, iCalWeekDayWednesday,
iCalWeekDayThursday, iCalWeekDayFriday,
iCalWeekDaySaturday };
days = [NSMutableArray arrayWithCapacity: 7];
if (_mask)
{
for (count = 0; count < 7; count++)
if (_mask & maskDays[count])
[days addObject:
[self iCalRepresentationForWeekDay: maskDays[count]]];
}
[self setNamedValue: @"byday" to: [days componentsJoinedByString: @","]];
[self setNamedValue: @"byday" to: newByDay];
}
- (unsigned int) byDayMask
- (NSString *) byDay
{
NSArray *days;
unsigned int mask, count, max;
NSString *day, *value;
mask = 0;
value = [self namedValue: @"byday"];
if ([value length] > 0)
{
days = [value componentsSeparatedByString: @","];
max = [days count];
for (count = 0; count < max; count++)
{
day = [days objectAtIndex: count];
mask |= [self weekDayFromICalRepresentation: day];
}
}
return mask;
return [self namedValue: @"byday"];
}
#warning this is bad
- (int) byDayOccurence1
- (void) setByDayMask: (iCalByDayMask *) newByDayMask
{
return 0;
// return byDayOccurence1;
[self setByDay: [newByDayMask asRuleString]];
}
- (iCalByDayMask *) byDayMask
{
if (dayMask == nil && [[self byDay] length])
{
dayMask = [iCalByDayMask byDayMaskWithRuleString: [self byDay]];
[dayMask retain];
}
return dayMask;
}
- (NSArray *) byMonthDay
@ -337,6 +481,35 @@
return byMonthDay;
}
- (NSArray *) byMonth
{
NSArray *byMonth;
NSString *byMonthStr;
byMonthStr = [self namedValue: @"bymonth"];
if ([byMonthStr length])
byMonth = [byMonthStr componentsSeparatedByString: @","];
else
byMonth = nil;
return byMonth;
}
- (BOOL) hasByMask
{
/* There are more BYxxx rule parts but we don't support them yet :
* - BYYEARDAY
* - BYWEEKNO
* - BYHOUR
* - BYMINUTE
* - BYSECOND
* - BYSETPOS
*/
return ([[self namedValue: @"bymonthday"] length] ||
[[self namedValue: @"byday"] length] ||
[[self namedValue: @"bymonth"] length]);
}
- (BOOL) isInfinite
{
return !([self repeatCount] || [self untilDate]);
@ -356,6 +529,7 @@
dayLength = [_day length];
if (dayLength > 1)
{
// Ignore any prefix, only consider last two characters
[[_day uppercaseString] getCharacters: chars
range: NSMakeRange (dayLength - 2, 2)];

View File

@ -26,6 +26,7 @@
#import "iCalDateTime.h"
#import "iCalRecurrenceRule.h"
#import "iCalByDayMask.h"
#import "iCalTimeZonePeriod.h"
@ -88,16 +89,16 @@
return ((negative) ? -seconds : seconds);
}
- (unsigned int) dayOfWeekFromRruleDay: (iCalWeekDay) day
{
unsigned int dayOfWeek;
// - (unsigned int) dayOfWeekFromRruleDay: (iCalWeekDay) day
// {
// unsigned int dayOfWeek;
dayOfWeek = 0;
while (day >> (dayOfWeek + 1))
dayOfWeek++;
// dayOfWeek = 0;
// while (day >> (dayOfWeek + 1))
// dayOfWeek++;
return dayOfWeek;
}
// return dayOfWeek;
// }
- (NSCalendarDate *) startDate
{
@ -105,23 +106,34 @@
dateTime];
}
/* This method returns the date corresponding for to the start of the period
in the year of the reference date. */
/**
* This method returns the date corresponding for to the start of the period
* in the year of the reference date.
* We assume that a RRULE for a timezone will always be YEARLY with a BYMONTH
* and a BYDAY rule.
*/
- (NSCalendarDate *) _occurenceForDate: (NSCalendarDate *) refDate
byRRule: (iCalRecurrenceRule *) rrule
{
NSCalendarDate *tmpDate;
NSString *byDay;
iCalByDayMask *byDayMask;
int dayOfWeek, dateDayOfWeek, offset, pos;
NSCalendarDate *tzStart;
byDay = [rrule namedValue: @"byday"];
dayOfWeek = [self dayOfWeekFromRruleDay: [rrule byDayMask]];
pos = [[byDay substringToIndex: 2] intValue];
if (!pos)
/* if byday = "SU", instead of "1SU"... */
pos = 1;
byDayMask = [rrule byDayMask];
dayOfWeek = 0;
if (byDayMask == nil)
{
dayOfWeek = 0;
pos = 1;
}
else
{
dayOfWeek = (int)[byDayMask firstDay];
pos = [byDayMask firstOccurrence];
}
tzStart = [self startDate];
[tzStart setTimeZone: [NSTimeZone timeZoneWithName: @"GMT"]];
tmpDate = [NSCalendarDate dateWithYear: [refDate yearOfCommonEra]

View File

@ -1,5 +1,6 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2006-2010 Inverse inc.
This file is part of SOPE.
@ -28,6 +29,7 @@
#import <NGExtensions/NGCalendarDateRange.h>
#import "iCalRecurrenceRule.h"
#import "iCalByDayMask.h"
#import "NSCalendarDate+ICal.h"
@interface iCalRecurrenceCalculator (PrivateAPI)
@ -42,22 +44,36 @@
@end
/*
TODO: If BYDAY is specified, lastInstanceStartDate and recurrences will
differ significantly!
*/
@implementation iCalWeeklyRecurrenceCalculator
/**
* TODO : Unsupported conditions for WEEKLY recurrences :
*
* BYYEAR
* BYYEARDAY
* BYWEEKNO
* BYMONTH
* BYMONTHDAY
* BYHOUR
* BYMINUTE
* WKST
*
* There's no GUI to defined such conditions, so there's no
* problem for now.
*/
- (NSArray *) recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
{
NSMutableArray *ranges;
NSCalendarDate *firStart, *startDate, *endDate, *currentStartDate, *currentEndDate;
long i;
unsigned interval, byDayMask;
long i, repeatCount, count;
unsigned interval;
iCalByDayMask *dayMask;
firStart = [firstRange startDate];
startDate = [_r startDate];
endDate = [_r endDate];
dayMask = nil;
repeatCount = 0;
if ([endDate compare: firStart] == NSOrderedAscending)
// Range ends before first occurrence
@ -65,33 +81,47 @@
interval = [rrule repeatInterval];
if ([[rrule byDay] length])
dayMask = [rrule byDayMask];
// If rule is bound, check the bounds
if (![rrule isInfinite])
{
NSCalendarDate *until, *lastDate;
lastDate = nil;
until = [rrule untilDate];
if (until)
lastDate = until;
else
lastDate = [firStart dateByAddingYears: 0 months: 0
days: (interval
* ([rrule repeatCount] - 1) * 7)];
else
{
repeatCount = [rrule repeatCount];
if (dayMask == nil)
// When there's no BYxxx mask, we can find the date of the last
// occurrence.
lastDate = [firStart dateByAddingYears: 0 months: 0
days: (interval
* (repeatCount - 1) * 7)];
}
if ([lastDate compare: startDate] == NSOrderedAscending)
// Range starts after last occurrence
return nil;
if ([lastDate compare: endDate] == NSOrderedAscending)
// Range ends after last occurence; adjust end date
endDate = lastDate;
if (lastDate != nil)
{
if ([lastDate compare: startDate] == NSOrderedAscending)
// Range starts after last occurrence
return nil;
if ([lastDate compare: endDate] == NSOrderedAscending)
// Range ends after last occurence; adjust end date
endDate = lastDate;
}
}
currentStartDate = [firStart copy];
[currentStartDate autorelease];
ranges = [NSMutableArray array];
byDayMask = [rrule byDayMask];
i = 1;
if (!byDayMask)
count = 0;
if (dayMask == nil)
{
while ([currentStartDate compare: endDate] == NSOrderedAscending ||
[currentStartDate compare: endDate] == NSOrderedSame)
@ -106,7 +136,7 @@
endDate: currentEndDate];
if ([_r containsDateRange: r])
[ranges addObject: r];
}
}
currentStartDate = [firStart dateByAddingYears: 0
months: 0
days: (interval * i * 7)];
@ -115,53 +145,52 @@
}
else
{
unsigned dayOfWeek;
NGCalendarDateRange *r;
while ([currentStartDate compare: endDate] == NSOrderedAscending ||
[currentStartDate compare: endDate] == NSOrderedSame)
{
if ([startDate compare: currentStartDate] == NSOrderedAscending ||
BOOL isRecurrence = NO;
int days, week;
if (repeatCount > 0 ||
[startDate compare: currentStartDate] == NSOrderedAscending ||
[startDate compare: currentStartDate] == NSOrderedSame)
{
int days, week;
[currentStartDate years:NULL months:NULL days:(int *)&days hours:NULL
minutes:NULL seconds:NULL sinceDate:firStart];
week = days / 7;
if ((week % interval) == 0)
// If the rule count is defined, stop once the count is reached.
if (i == 1)
{
// Date is in the proper week with respect to the
// week interval
BOOL isRecurrence = NO;
if ([currentStartDate compare: firStart] == NSOrderedSame)
// Always add the event of the start date of
// the recurring event.
// Always add the start date the recurring event if within
// the lookup range.
isRecurrence = YES;
}
else
{
[currentStartDate years:NULL months:NULL days:(int *)&days hours:NULL
minutes:NULL seconds:NULL sinceDate: firStart];
week = days / 7;
if ((week % interval) == 0 &&
[dayMask occursOnDay: [currentStartDate dayOfWeek]])
isRecurrence = YES;
else
{
// Only consider events that matches the day mask.
dayOfWeek = ([currentStartDate dayOfWeek]
? (unsigned int) 1 << [currentStartDate dayOfWeek]
: iCalWeekDaySunday);
if (dayOfWeek & [rrule byDayMask])
isRecurrence = YES;
}
if (isRecurrence)
{
currentEndDate = [currentStartDate addTimeInterval: [firstRange duration]];
r = [NGCalendarDateRange calendarDateRangeWithStartDate: currentStartDate
endDate: currentEndDate];
if ([_r containsDateRange: r])
[ranges addObject: r];
}
}
if (isRecurrence)
{
count++;
if (repeatCount > 0 && count > repeatCount)
break;
currentEndDate = [currentStartDate addTimeInterval: [firstRange duration]];
r = [NGCalendarDateRange calendarDateRangeWithStartDate: currentStartDate
endDate: currentEndDate];
if ([_r containsDateRange: r])
[ranges addObject: r];
}
}
currentStartDate = [currentStartDate dateByAddingYears: 0
months: 0
days: 1];
months: 0
days: 1];
i++;
}
}
@ -171,14 +200,18 @@
- (NSCalendarDate *) lastInstanceStartDate
{
NSCalendarDate *firStart, *lastInstanceStartDate;
NGCalendarDateRange *r;
NSArray *instances;
if ([rrule repeatCount] > 0)
lastInstanceStartDate = nil;
if ([rrule repeatCount] > 0)
{
firStart = [firstRange startDate];
lastInstanceStartDate = [firStart dateByAddingYears: 0 months: 0
days: (7 * [rrule repeatInterval]
* ([rrule repeatCount] - 1))];
r = [NGCalendarDateRange calendarDateRangeWithStartDate: firStart
endDate: [NSCalendarDate distantFuture]];
instances = [self recurrenceRangesWithinCalendarDateRange: r];
if ([instances count])
lastInstanceStartDate = [(NGCalendarDateRange *)[instances lastObject] startDate];
}
else
lastInstanceStartDate = [super lastInstanceStartDate];

View File

@ -1,5 +1,6 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2006-2010 Inverse inc.
This file is part of SOPE.
@ -34,77 +35,234 @@
- (NSCalendarDate *) lastInstanceStartDate;
@end
@class iCalMonthlyRecurrenceCalculator;
@implementation iCalYearlyRecurrenceCalculator
- (NSArray *)
recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
{
NSMutableArray *ranges;
NSCalendarDate *firStart, *rStart, *rEnd, *until;
unsigned i, count, interval;
int diff;
NSArray *byMonth;
NSCalendarDate *firStart, *lastDate, *rStart, *rEnd, *until, *referenceDate;
iCalMonthlyRecurrenceCalculator *monthlyCalc;
unsigned j, yearIdxInRange, numberOfYearsInRange, count, interval, monthDiff;
int diff, repeatCount, currentMonth;
firStart = [firstRange startDate];
rStart = [_r startDate];
rEnd = [_r endDate];
interval = [rrule repeatInterval];
until = [self lastInstanceStartDate];
byMonth = [rrule byMonth];
diff = 0;
repeatCount = 0;
count = 0;
referenceDate = nil;
if (until)
if ([rEnd compare: firStart] == NSOrderedAscending)
// Range ends before first occurrence
return nil;
// If rule is bound, check the bounds
if (![rrule isInfinite])
{
if ([until compare: rStart] == NSOrderedAscending)
return nil;
if ([until compare: rEnd] == NSOrderedDescending)
rEnd = until;
}
diff = [firStart yearsBetweenDate: rStart];
if ((diff != 0) && [rStart compare: firStart] == NSOrderedAscending)
diff = -diff;
lastDate = nil;
until = [rrule untilDate];
repeatCount = [rrule repeatCount];
count = [rStart yearsBetweenDate: rEnd] + 1;
ranges = [NSMutableArray arrayWithCapacity: count];
for (i = 0 ; i < count; i++)
{
int test;
test = diff + i;
if ((test >= 0) && (test % interval) == 0)
if (until)
{
NSCalendarDate *start, *end;
NGCalendarDateRange *r;
start = [firStart dateByAddingYears: diff + i
months: 0
days: 0];
[start setTimeZone: [firStart timeZone]];
end = [start addTimeInterval: [firstRange duration]];
r = [NGCalendarDateRange calendarDateRangeWithStartDate: start
endDate: end];
if ([_r containsDateRange: r])
[ranges addObject: r];
lastDate = until;
}
if (repeatCount > 0)
{
if (lastDate == nil && ![rrule hasByMask])
// When there's no BYxxx mask, we can find the date of the last
// occurrence.
lastDate = [firStart dateByAddingYears: (interval * (repeatCount - 1))
months: 0
days: 0];
referenceDate = firStart;
}
if (lastDate != nil)
{
if ([lastDate compare: rStart] == NSOrderedAscending)
// Range starts after last occurrence
return nil;
if ([lastDate compare: rEnd] == NSOrderedDescending)
// Range ends after last occurence; adjust end date
rEnd = lastDate;
}
}
if (referenceDate == nil)
{
diff = [firStart yearsBetweenDate: rStart];
if ((diff != 0) && [rStart compare: firStart] == NSOrderedAscending)
diff = -diff;
referenceDate = rStart;
}
// Initialize array to return with an approximation of the number total
// number of possible matches, ie the number of years spawned by the period.
numberOfYearsInRange = [referenceDate yearsBetweenDate: rEnd] + 1;
ranges = [NSMutableArray arrayWithCapacity: numberOfYearsInRange];
if (byMonth)
{
/*
* WARNING/TODO : if there's no BYMONTH rule but there's a BYMONTHDAY
* rule we should implicitely define a BYMONTH rule by extracting the
* month from the DTSTART field. However, this kind of definition is
* uncommon.
*/
// Instantiate a MONTHLY calculator
if (repeatCount > 0)
// Fool the monthly calculator, otherwise it will verify the COUNT
// constraint and perform the calculation from the first occurence of
// the recurrence. This calculation is performed by the current method.
[rrule setRepeatCount: 0];
monthlyCalc = [[iCalMonthlyRecurrenceCalculator alloc]
initWithRecurrenceRule: rrule
firstInstanceCalendarDateRange: firstRange];
[monthlyCalc autorelease];
// There's a bug in GNUstep in [NSCalendarDate dateByAddingYears:months:days:]
// that causes errors when adding subsequently a month. For this reason,
// we set the day of the reference date to 1.
referenceDate = [NSCalendarDate dateWithYear: [referenceDate yearOfCommonEra]
month: [referenceDate monthOfYear]
day: 1
hour: [referenceDate hourOfDay]
minute: [referenceDate minuteOfHour]
second: 0
timeZone: [referenceDate timeZone]];
// If the BYMONTH constraints exclude the month of the event DTSTART, we
// add the corresponding range manually if it is included in the period.
// Otherwise, it will be included by the monthly calculator in the loop
// bellow.
int month = [firStart monthOfYear];
if (![byMonth containsObject: [NSString stringWithFormat: @"%i", month]])
{
count++;
if ([_r containsDateRange: firstRange])
{
[ranges addObject: firstRange];
}
}
}
monthDiff = 0;
currentMonth = [referenceDate monthOfYear];
for (yearIdxInRange = 0 ; yearIdxInRange < numberOfYearsInRange; yearIdxInRange++)
{
int test, year;
test = diff + yearIdxInRange;
if ((test >= 0) && (test % interval) == 0)
{
year = yearIdxInRange + [referenceDate yearOfCommonEra];
if (byMonth)
{
// When there's a BYMONTH constraint, evaluate each month of the constraint using
// the monthly calculator.
for (j = 0; currentMonth < 13 && j <= 12; j++, currentMonth++, monthDiff++)
{
if ([byMonth containsObject: [NSString stringWithFormat: @"%i", currentMonth]])
{
NGCalendarDateRange *rangeForMonth;
NSArray *rangesInMonth;
rStart = [referenceDate dateByAddingYears: 0
months: monthDiff
days: 0];
rEnd = [rStart dateByAddingYears: 0
months: 0
days: [rStart numberOfDaysInMonth] - 1];
rangeForMonth = [NGCalendarDateRange calendarDateRangeWithStartDate: rStart
endDate: rEnd];
rangesInMonth = [monthlyCalc recurrenceRangesWithinCalendarDateRange: rangeForMonth];
int k;
for (k = 0; k < [rangesInMonth count] && (repeatCount == 0 || count < repeatCount); k++) {
//NSLog(@"*** YEARLY found %@ (count = %i)", [[rangesInMonth objectAtIndex: k] startDate], count);
count++;
if ([_r containsDateRange: [rangesInMonth objectAtIndex: k]])
{
[ranges addObject: [rangesInMonth objectAtIndex: k]];
//NSLog(@"*** YEARLY adding %@ (count = %i)", [[rangesInMonth objectAtIndex: k] startDate], count);
}
}
}
}
// Done with the current year; start the next iteration from January
currentMonth = 1;
}
else
{
// No BYxxx mask
NSCalendarDate *start, *end;
NGCalendarDateRange *r;
start = [firStart dateByAddingYears: diff + yearIdxInRange
months: 0
days: 0];
[start setTimeZone: [firStart timeZone]];
end = [start addTimeInterval: [firstRange duration]];
r = [NGCalendarDateRange calendarDateRangeWithStartDate: start
endDate: end];
if ([_r containsDateRange: r] && (repeatCount == 0 || count < repeatCount))
{
[ranges addObject: r];
count++;
}
}
}
}
if (byMonth && repeatCount > 0)
// Restore the repeat count
[rrule setRepeatCount: repeatCount];
return ranges;
}
- (NSCalendarDate *) lastInstanceStartDate
{
NSCalendarDate *firStart, *lastInstanceStartDate;
NGCalendarDateRange *r;
NSArray *instances;
if ([rrule repeatCount] > 0)
lastInstanceStartDate = nil;
if ([rrule repeatCount] > 0)
{
firStart = [firstRange startDate];
lastInstanceStartDate
= [firStart dateByAddingYears: ([rrule repeatInterval]
* ([rrule repeatCount] - 1))
months: 0
days: 0];
if ([rrule hasByMask])
{
// Must perform the complete calculation
firStart = [firstRange startDate];
r = [NGCalendarDateRange calendarDateRangeWithStartDate: firStart
endDate: [NSCalendarDate distantFuture]];
instances = [self recurrenceRangesWithinCalendarDateRange: r];
if ([instances count])
lastInstanceStartDate = [(NGCalendarDateRange *)[instances lastObject] startDate];
}
else
{
// No BYxxx mask
lastInstanceStartDate = [firStart dateByAddingYears: ([rrule repeatInterval]
* ([rrule repeatCount] - 1))
months: 0
days: 0];
}
}
else
lastInstanceStartDate = [super lastInstanceStartDate];
return lastInstanceStartDate;
}

View File

@ -1,6 +1,6 @@
/* UIxAppointmentEditor.m - this file is part of SOGo
*
* Copyright (C) 2007-2009 Inverse inc.
* Copyright (C) 2007-2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
@ -419,13 +419,11 @@
{
WOResponse *result;
NSDictionary *data;
NSCalendarDate *firstDate, *eventDate;
NSCalendarDate *eventDate;
NSTimeZone *timeZone;
SOGoUserDefaults *ud;
SOGoCalendarComponent *co;
iCalEvent *master;
BOOL resetAlarm;
signed int daylightOffset;
BOOL resetAlarm;
[self event];
@ -457,20 +455,6 @@
[co saveComponent: event];
}
if ([co isNew] && [co isKindOfClass: [SOGoAppointmentOccurence class]])
{
// This is a new exception in a recurrent event -- compute the daylight
// saving time with respect to the first occurrence of the recurrent event.
master = (iCalEvent*)[[event parent] firstChildWithTag: @"vevent"];
firstDate = [master startDate];
if ([timeZone isDaylightSavingTimeForDate: eventDate] != [timeZone isDaylightSavingTimeForDate: firstDate])
{
daylightOffset = (signed int)[timeZone secondsFromGMTForDate: firstDate]
- (signed int)[timeZone secondsFromGMTForDate: eventDate];
eventDate = [eventDate dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 seconds:daylightOffset];
}
}
data = [NSDictionary dictionaryWithObjectsAndKeys:
[componentCalendar displayName], @"calendar",
[event tag], @"component",

View File

@ -273,27 +273,28 @@ static NSArray *tasksFields = nil;
static NSString *fields[] = { @"startDate", @"c_startdate",
@"endDate", @"c_enddate" };
for (count = 0; count < 2; count++)
{
aDateField = fields[count * 2];
aDate = [aRecord objectForKey: aDateField];
daylightOffset = (int) ([userTimeZone secondsFromGMTForDate: aDate]
- [userTimeZone secondsFromGMTForDate: startDate]);
if (daylightOffset)
{
aDate = [aDate dateByAddingYears: 0 months: 0 days: 0 hours: 0
minutes: 0 seconds: daylightOffset];
[aRecord setObject: aDate forKey: aDateField];
aDateValue = [NSNumber numberWithInt: [aDate timeIntervalSince1970]];
[aRecord setObject: aDateValue forKey: fields[count * 2 + 1]];
}
}
if (dayBasedView)
for (count = 0; count < 2; count++)
{
aDateField = fields[count * 2];
aDate = [aRecord objectForKey: aDateField];
daylightOffset = (int) ([userTimeZone secondsFromGMTForDate: aDate]
- [userTimeZone secondsFromGMTForDate: startDate]);
if (daylightOffset)
{
aDate = [aDate dateByAddingYears: 0 months: 0 days: 0 hours: 0
minutes: 0 seconds: daylightOffset];
[aRecord setObject: aDate forKey: aDateField];
aDateValue = [NSNumber numberWithInt: [aDate timeIntervalSince1970]];
[aRecord setObject: aDateValue forKey: fields[count * 2 + 1]];
}
}
aDateValue = [aRecord objectForKey: @"c_recurrence_id"];
aDate = [aRecord objectForKey: @"cycleStartDate"];
aStartDate = [aRecord objectForKey: @"startDate"];
if (aDateValue && aDate)
{
aStartDate = [aRecord objectForKey: @"startDate"];
if ([userTimeZone isDaylightSavingTimeForDate: aStartDate] !=
[userTimeZone isDaylightSavingTimeForDate: aDate])
{
@ -387,11 +388,9 @@ static NSArray *tasksFields = nil;
forKey: @"c_owner"];
if (![[newInfo objectForKey: @"c_title"] length])
[self _fixComponentTitle: newInfo withType: component];
if (dayBasedView
|| [[newInfo objectForKey: @"c_isallday"] boolValue])
// Possible improvement: only call _fixDates if event is recurrent
// or the view range span a daylight saving time change
[self _fixDates: newInfo];
// Possible improvement: only call _fixDates if event is recurrent
// or the view range span a daylight saving time change
[self _fixDates: newInfo];
[infos addObject: [newInfo objectsForKeys: fields
notFoundMarker: marker]];
}

View File

@ -31,6 +31,7 @@
#import <Foundation/NSURL.h>
#import <NGCards/iCalAlarm.h>
#import <NGCards/iCalByDayMask.h>
#import <NGCards/iCalPerson.h>
#import <NGCards/iCalRepeatableEntityObject.h>
#import <NGCards/iCalRecurrenceRule.h>
@ -294,25 +295,6 @@ iRANGE(2);
}
}
- (NSString *) _dayMaskToInteger: (unsigned int) theMask
{
iCalWeekDay maskDays[] = {iCalWeekDaySunday, iCalWeekDayMonday,
iCalWeekDayTuesday, iCalWeekDayWednesday,
iCalWeekDayThursday, iCalWeekDayFriday,
iCalWeekDaySaturday};
unsigned int i;
NSMutableString *s;
s = [NSMutableString string];
for (i = 0; i < 7; i++)
if ((theMask & maskDays[i]))
[s appendFormat: @"%d,", i];
[s deleteSuffix: @","];
return s;
}
- (void) _loadRRules
{
SOGoUserDefaults *ud;
@ -326,15 +308,12 @@ iRANGE(2);
rule = [[component recurrenceRules] lastObject];
/* DAILY */
if ([rule frequency] == iCalRecurrenceFrequenceDaily)
{
repeatType = @"0";
if ([rule byDayMask] == (iCalWeekDayMonday
| iCalWeekDayTuesday
| iCalWeekDayWednesday
| iCalWeekDayThursday
| iCalWeekDayFriday))
if ([[rule byDayMask] isWeekDays])
{
if ([rule isInfinite])
repeat = @"EVERY WEEKDAY";
@ -350,12 +329,14 @@ iRANGE(2);
[self setRepeat2: [NSString stringWithFormat: @"%d", [rule repeatInterval]]];
}
}
/* WEEKLY */
else if ([rule frequency] == iCalRecurrenceFrequenceWeekly)
{
repeatType = @"1";
[self setRepeat1: [NSString stringWithFormat: @"%d", [rule repeatInterval]]];
if (![rule byDayMask])
if (![[rule byDay] length])
{
if ([rule repeatInterval] == 1)
repeat = @"WEEKLY";
@ -364,45 +345,81 @@ iRANGE(2);
}
else
{
[self setRepeat2: [self _dayMaskToInteger: [rule byDayMask]]];
[self setRepeat2: [[rule byDayMask] asRuleStringWithIntegers]];
}
}
/* MONTHLY */
else if ([rule frequency] == iCalRecurrenceFrequenceMonthly)
{
repeatType = @"2";
if ([rule byDayMask])
if ([[rule byDay] length])
{
// TODO
int firstOccurrence;
iCalByDayMask *dayMask;
dayMask = [rule byDayMask];
firstOccurrence = [dayMask firstOccurrence] - 1;
if (firstOccurrence < 0)
firstOccurrence = 5;
[self setRepeat2: @"0"];
[self setRepeat3: [NSString stringWithFormat: @"%d", firstOccurrence]];
[self setRepeat4: [NSString stringWithFormat: @"%d", [dayMask firstDay]]];
}
else if ([[rule byMonthDay] count])
{
[self setRepeat2: @"1"];
[self setRepeat5: [[rule byMonthDay] componentsJoinedByString: @","]];
NSArray *days;
days = [rule byMonthDay];
if ([days count] > 0 && [[days objectAtIndex: 0] intValue] < 0)
{
// BYMONTHDAY=-1
[self setRepeat2: @"0"];
[self setRepeat3: @"5"]; // last ..
[self setRepeat4: @"7"]; // .. day of the month
}
else
{
[self setRepeat2: @"1"];
[self setRepeat5: [[rule byMonthDay] componentsJoinedByString: @","]];
}
}
else if ([rule repeatInterval] == 1)
repeat = @"MONTHLY";
[self setRepeat1: [NSString stringWithFormat: @"%d", [rule repeatInterval]]];
}
/* YEARLY */
else
{
repeatType = @"3";
if ([rule namedValue: @"bymonth"])
if ([[rule namedValue: @"bymonth"] length])
{
if (![rule byDayMask])
if ([[rule byDay] length])
{
int firstOccurrence;
iCalByDayMask *dayMask;
dayMask = [rule byDayMask];
firstOccurrence = [dayMask firstOccurrence] - 1;
if (firstOccurrence < 0)
firstOccurrence = 5;
[self setRepeat2: @"1"];
[self setRepeat5: [NSString stringWithFormat: @"%d", firstOccurrence]];
[self setRepeat6: [NSString stringWithFormat: @"%d", [dayMask firstDay]]];
[self setRepeat7: [NSString stringWithFormat: @"%d", [[rule namedValue: @"bymonth"] intValue]-1]];
}
else
{
[self setRepeat2: @"0"];
[self setRepeat3: [rule namedValue: @"bymonthday"]];
[self setRepeat4: [NSString stringWithFormat: @"%d", [[rule namedValue: @"bymonth"] intValue]-1]];
}
else
{
// TODO
[self setRepeat2: @"1"];
}
}
else if ([rule repeatInterval] == 1)
repeat = @"YEARLY";
@ -410,7 +427,7 @@ iRANGE(2);
[self setRepeat1: [NSString stringWithFormat: @"%d", [rule repeatInterval]]];
}
// We decode the proper end date, recurrences count, etc.
/* We decode the proper end date, recurrences count, etc. */
if ([rule repeatCount])
{
repeat = @"CUSTOM";
@ -1639,9 +1656,9 @@ RANGE(2);
switch (type)
{
// DAILY:
// DAILY (0)
//
// repeat1 holds the value of the radio button:
// repeat1 holds the value of the frequency radio button:
// 0 -> Every X days
// 1 -> Every weekday
//
@ -1653,11 +1670,7 @@ RANGE(2);
if ([[self repeat1] intValue] > 0)
{
[theRule setByDayMask: (iCalWeekDayMonday
|iCalWeekDayTuesday
|iCalWeekDayWednesday
|iCalWeekDayThursday
|iCalWeekDayFriday)];
[theRule setByDayMask: [iCalByDayMask byDayMaskWithWeekDays]];
}
else
{
@ -1670,7 +1683,7 @@ RANGE(2);
}
break;
// WEEKLY
// WEEKLY (1)
//
// repeat1 holds the value of "Every X week(s)"
//
@ -1683,7 +1696,8 @@ RANGE(2);
if ([[self repeat1] intValue] > 0)
{
NSArray *v;
int c, mask;
int c, day;
iCalWeekOccurrences days;
[theRule setFrequency: iCalRecurrenceFrequenceWeekly];
[theRule setInterval: [self repeat1]];
@ -1692,18 +1706,21 @@ RANGE(2);
{
v = [[self repeat2] componentsSeparatedByString: @","];
c = [v count];
mask = 0;
memset(days, 0, 7 * sizeof(iCalWeekOccurrence));
while (c--)
mask |= 1 << ([[v objectAtIndex: c] intValue]);
[theRule setByDayMask: mask];
{
day = [[v objectAtIndex: c] intValue];
if (day >= 0 && day <= 7)
days[day] = iCalWeekOccurrenceAll;
}
[theRule setByDayMask: [iCalByDayMask byDayMaskWithDays: days]];
}
}
}
break;
// MONTHLY
// MONTHLY (2)
//
// repeat1 holds the value of "Every X month(s)"
//
@ -1731,20 +1748,29 @@ RANGE(2);
[theRule setInterval: [self repeat1]];
// We recur on specific days...
if ([[self repeat2] intValue] == 1
&& [[self repeat5] intValue] > 0)
{
[theRule setNamedValue: @"bymonthday" to: [self repeat5]];
}
else
{
// TODO
}
if ([[self repeat2] intValue] == 0)
{
NSString *day;
int occurence;
day = [theRule iCalRepresentationForWeekDay: [[self repeat4] intValue]];
occurence = [[self repeat3] intValue] + 1;
if (occurence > 5) // the first/second/third/fourth/fifth ..
occurence = -1; // the last ..
[theRule setNamedValue: @"byday"
to: [NSString stringWithFormat: @"%d%@",
occurence, day]];
}
else
{
if ([[self repeat5] intValue] > 0)
[theRule setNamedValue: @"bymonthday" to: [self repeat5]];
}
}
}
break;
// YEARLY
// YEARLY (3)
//
// repeat1 holds the value of "Every X year(s)"
//
@ -1756,7 +1782,7 @@ RANGE(2);
// repeat4 holds the value of the MONTH parameter (0 -> January, 1 -> February ... )
// ex: 3 February
//
// repeat5 holds the value of the OCCURENCE parameter (0 -> First, 1 -> Second ..)
// repeat5 holds the value of the OCCURENCE parameter (0 -> First, 1 -> Second .., 5 -> Last)
// repeat6 holds the value of the DAY parameter (0 -> Sunday, 1 -> Monday, etc..)
// repeat7 holds the value of the MONTH parameter (0 -> January, 1 -> February ... )
//
@ -1771,7 +1797,19 @@ RANGE(2);
// We recur Every .. of ..
if ([[self repeat2] intValue] == 1)
{
// TODO
NSString *day;
int occurence;
day = [theRule iCalRepresentationForWeekDay: [[self repeat6] intValue]];
occurence = [[self repeat5] intValue] + 1;
if (occurence > 5) // the first/second/third/fourth/fifth ..
occurence = -1; // the last ..
[theRule setNamedValue: @"byday"
to: [NSString stringWithFormat: @"%d%@",
occurence, day]];
[theRule setNamedValue: @"bymonth"
to: [NSString stringWithFormat: @"%d",
[[self repeat7] intValue] + 1]];
}
else
{
@ -1883,11 +1921,7 @@ RANGE(2);
}
else if ([repeat caseInsensitiveCompare: @"EVERY WEEKDAY"] == NSOrderedSame)
{
[rule setByDayMask: (iCalWeekDayMonday
|iCalWeekDayTuesday
|iCalWeekDayWednesday
|iCalWeekDayThursday
|iCalWeekDayFriday)];
[rule setByDayMask: [iCalByDayMask byDayMaskWithWeekDays]];
[rule setFrequency: iCalRecurrenceFrequenceDaily];
}
else if ([repeat caseInsensitiveCompare: @"MONTHLY"] == NSOrderedSame

View File

@ -241,32 +241,26 @@ function handleMonthlyRecurrence() {
var radioValue = $('recurrence_form').getRadioValue('monthlyRadioButtonName');
// FIXME - right now we do not support rules
// such as The Second Tuesday...
if (radioValue == 0)
window.alert(recurrenceUnsupported);
else {
// We check if the monthlyMonthsField really contains an integer
var showError = true;
// We check if the monthlyMonthsField really contains an integer
var showError = true;
var fieldValue = "" + $('monthlyMonthsField').value;
if (fieldValue.length > 0) {
var v = parseInt(fieldValue);
if (!isNaN(v) && v > 0) {
validate = true;
showError = false;
parent$("repeat1").value = fieldValue;
parent$("repeat2").value = radioValue;
parent$("repeat3").value = $('monthlyRepeat').value;
parent$("repeat4").value = $('monthlyDay').value;
parent$("repeat5").value = getSelectedDays("month");
}
var fieldValue = "" + $('monthlyMonthsField').value;
if (fieldValue.length > 0) {
var v = parseInt(fieldValue);
if (!isNaN(v) && v > 0) {
validate = true;
showError = false;
parent$("repeat1").value = fieldValue;
parent$("repeat2").value = radioValue;
parent$("repeat3").value = $('monthlyRepeat').value;
parent$("repeat4").value = $('monthlyDay').value;
parent$("repeat5").value = getSelectedDays("month");
}
if (showError)
window.alert(monthFieldInvalid);
}
if (showError)
window.alert(monthFieldInvalid);
return validate;
}
@ -279,15 +273,20 @@ function validateYearlyRecurrence() {
// We check if the yearlyYearsField really contains an integer
var v = parseInt(fieldValue);
if (!isNaN(v) && v > 0) {
errorToShow = 1;
fieldValue = "" + $('yearlyDayField').value;
if (fieldValue.length > 0) {
// We check if the yearlyYearsField really contains an integer
var v = parseInt(fieldValue);
if (!isNaN(v) && v > 0) {
errorToShow = -1;
var radioValue = $('recurrence_form').getRadioValue('yearlyRadioButtonName');
if (radioValue == 0) {
errorToShow = 1;
fieldValue = "" + $('yearlyDayField').value;
if (fieldValue.length > 0) {
// We check if the yearlyDayField really contains an integer
var v = parseInt(fieldValue);
if (!isNaN(v) && v > 0) {
errorToShow = -1;
}
}
}
else
errorToShow = -1;
}
}
@ -300,34 +299,28 @@ function validateYearlyRecurrence() {
function handleYearlyRecurrence() {
var validate = false;
var radioValue = $('recurrence_form').getRadioValue('yearlyRadioButtonName');
// FIXME - right now we do not support rules
// such as Every Second Tuesday of February
if (radioValue == 1)
window.alert(recurrenceUnsupported);
else {
if (validateYearlyRecurrence()) {
var fieldValue = "" + $('yearlyYearsField').value;
if (fieldValue.length > 0) {
// We check if the yearlyYearsField really contains an integer
var v = parseInt(fieldValue);
if (!isNaN(v) && v > 0) {
validate = true;
showError = false;
parent$("repeat1").value = fieldValue;
parent$("repeat2").value = radioValue;
parent$("repeat3").value = $('yearlyDayField').value;
parent$("repeat4").value = $('yearlyMonth1').value;
parent$("repeat5").value = $('yearlyRepeat').value;
parent$("repeat6").value = $('yearlyDay').value;
parent$("repeat7").value = $('yearlyMonth2').value;
}
if (validateYearlyRecurrence()) {
var radioValue = $('recurrence_form').getRadioValue('yearlyRadioButtonName');
var fieldValue = "" + $('yearlyYearsField').value;
if (fieldValue.length > 0) {
// We check if the yearlyYearsField (interval) really contains an integer
var v = parseInt(fieldValue);
if (!isNaN(v) && v > 0) {
validate = true;
showError = false;
parent$("repeat1").value = fieldValue;
parent$("repeat2").value = radioValue;
parent$("repeat3").value = $('yearlyDayField').value;
parent$("repeat4").value = $('yearlyMonth1').value;
parent$("repeat5").value = $('yearlyRepeat').value;
parent$("repeat6").value = $('yearlyDay').value;
parent$("repeat7").value = $('yearlyMonth2').value;
}
}
else
validate = false;
}
else
validate = false;
return validate;
}