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 shared_debug_obj
obj obj
err err
log
build\.log build\.log
imgs-.* imgs-.*
diff diff

4
NEWS
View File

@ -4,10 +4,14 @@
- added support for LDAP password policies - added support for LDAP password policies
- added support for custom Sieve filters - added support for custom Sieve filters
- fixed timezone issues occurring specifically in the southern hemisphere - fixed timezone issues occurring specifically in the southern hemisphere
- updated ckeditor to version 3.2
- tabs: enabled the scrolling when overflowing - tabs: enabled the scrolling when overflowing
- updated Czech translation, thanks to Milos Wimmer - updated Czech translation, thanks to Milos Wimmer
- removed remaining .wo templates, thereby easing the effort for future translations - removed remaining .wo templates, thereby easing the effort for future translations
- fixed regressions with Courier IMAP and Dovecot - 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 ability to delete events from a keypress
- added the "remove" command to "sogo-tool", in order to remove user data and settings - 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 - 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> 2010-04-09 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* iCalEvent.m (-propertyValue:): new method that accept a * iCalEvent.m (-propertyValue:): new method that accept a

View File

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

View File

@ -1,6 +1,7 @@
/* /*
Copyright (C) 2004-2005 SKYRIX Software AG Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2006-2010 Inverse inc.
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
@ -29,6 +30,7 @@
#import "iCalRecurrenceCalculator.h" #import "iCalRecurrenceCalculator.h"
#import "iCalRecurrenceRule.h" #import "iCalRecurrenceRule.h"
#import "iCalByDayMask.h"
@interface iCalDailyRecurrenceCalculator : iCalRecurrenceCalculator @interface iCalDailyRecurrenceCalculator : iCalRecurrenceCalculator
@end @end
@ -39,71 +41,122 @@
@implementation iCalDailyRecurrenceCalculator @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 *) - (NSArray *)
recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
{ {
NSMutableArray *ranges; NSMutableArray *ranges;
NSCalendarDate *firStart, *startDate, *endDate, *currentStartDate, *currentEndDate; NSCalendarDate *firStart, *startDate, *endDate, *currentStartDate, *currentEndDate;
long i; iCalByDayMask *dayMask;
long i, count, repeatCount;
unsigned interval; unsigned interval;
firStart = [firstRange startDate]; firStart = [firstRange startDate];
startDate = [_r startDate]; startDate = [_r startDate];
endDate = [_r endDate]; endDate = [_r endDate];
dayMask = nil;
repeatCount = 0;
if ([endDate compare: firStart] == NSOrderedAscending) if ([endDate compare: firStart] == NSOrderedAscending)
// Range ends before first occurrence // Range ends before first occurrence
return nil; return nil;
interval = [rrule repeatInterval]; interval = [rrule repeatInterval];
if ([[rrule byDay] length])
dayMask = [rrule byDayMask];
// If rule is bound, check the bounds // If rule is bound, check the bounds
if (![rrule isInfinite]) if (![rrule isInfinite])
{ {
NSCalendarDate *until, *lastDate; NSCalendarDate *until, *lastDate;
lastDate = nil;
until = [rrule untilDate]; until = [rrule untilDate];
if (until) if (until)
lastDate = until; {
lastDate = until;
}
else else
lastDate = [firStart dateByAddingYears: 0 months: 0 {
days: (interval repeatCount = [rrule repeatCount];
* ([rrule repeatCount] - 1))]; if (dayMask == nil)
// If there's no day mask, we can compute the date of the last
if ([lastDate compare: startDate] == NSOrderedAscending) // occurrence of the recurrent rule.
// Range starts after last occurrence lastDate = [firStart dateByAddingYears: 0 months: 0
return nil; days: (interval
* (repeatCount - 1))];
}
if ([lastDate compare: endDate] == NSOrderedAscending) if (lastDate != nil)
// Range ends after last occurence; adjust end date {
endDate = lastDate; 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 = [firStart copy];
[currentStartDate autorelease]; [currentStartDate autorelease];
ranges = [NSMutableArray array]; ranges = [NSMutableArray array];
i = 1; i = 1;
count = 0;
while ([currentStartDate compare: endDate] == NSOrderedAscending || while ([currentStartDate compare: endDate] == NSOrderedAscending ||
[currentStartDate compare: endDate] == NSOrderedSame) [currentStartDate compare: endDate] == NSOrderedSame)
{ {
if ([startDate compare: currentStartDate] == NSOrderedAscending || BOOL wrongDay, isFirStart;
[startDate compare: currentStartDate] == NSOrderedSame)
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; NGCalendarDateRange *r;
if ([rrule byDayMask]) if (isFirStart == NO && dayMask && repeatCount == 0)
{ {
mask = ([currentStartDate dayOfWeek] if (![dayMask occursOnDay: [currentStartDate dayOfWeek]])
? (unsigned int) 1 << ([currentStartDate dayOfWeek])
: iCalWeekDaySunday);
if (([rrule byDayMask] & mask) != mask)
wrongDay = YES; wrongDay = YES;
} }
if (wrongDay == NO) if (isFirStart == YES || wrongDay == NO)
{ {
currentEndDate = [currentStartDate addTimeInterval: [firstRange duration]]; currentEndDate = [currentStartDate addTimeInterval: [firstRange duration]];
r = [NGCalendarDateRange calendarDateRangeWithStartDate: currentStartDate r = [NGCalendarDateRange calendarDateRangeWithStartDate: currentStartDate
@ -115,6 +168,13 @@
currentStartDate = [firStart dateByAddingYears: 0 months: 0 currentStartDate = [firStart dateByAddingYears: 0 months: 0
days: (interval * i)]; 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++; i++;
} }
return ranges; return ranges;
@ -123,14 +183,29 @@
- (NSCalendarDate *) lastInstanceStartDate - (NSCalendarDate *) lastInstanceStartDate
{ {
NSCalendarDate *firStart, *lastInstanceStartDate; NSCalendarDate *firStart, *lastInstanceStartDate;
NGCalendarDateRange *r;
NSArray *instances;
lastInstanceStartDate = nil;
if ([rrule repeatCount] > 0) if ([rrule repeatCount] > 0)
{ {
firStart = [firstRange startDate]; if ([rrule hasByMask])
{
lastInstanceStartDate = [firStart dateByAddingYears: 0 months: 0 // Must perform the complete calculation
days: ([rrule repeatInterval] firStart = [firstRange startDate];
* ([rrule repeatCount] - 1))]; 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 else
lastInstanceStartDate = [super lastInstanceStartDate]; lastInstanceStartDate = [super lastInstanceStartDate];

View File

@ -1,6 +1,7 @@
/* /*
Copyright (C) 2004-2007 SKYRIX Software AG Copyright (C) 2004-2007 SKYRIX Software AG
Copyright (C) 2007 Helge Hess Copyright (C) 2007 Helge Hess
Copyright (C) 2010 Inverse inc.
This file is part of SOPE. This file is part of SOPE.
@ -29,8 +30,11 @@
#import <NGExtensions/NGCalendarDateRange.h> #import <NGExtensions/NGCalendarDateRange.h>
#import "iCalRecurrenceRule.h" #import "iCalRecurrenceRule.h"
#import "iCalByDayMask.h"
#import "NSCalendarDate+ICal.h" #import "NSCalendarDate+ICal.h"
#import <string.h> #import <string.h>
#import <math.h>
@interface iCalRecurrenceCalculator (PrivateAPI) @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) NSArray *byMonthDay)
{ {
/* list of days in the month */
unsigned i, count; unsigned i, count;
BOOL ok; BOOL ok;
NGMonthDaySet_clear (daySet); NGMonthDaySet_clear (positiveDaySet);
NGMonthDaySet_clear (negativeDaySet);
for (i = 0, count = [byMonthDay count], ok = YES; i < count; i++) for (i = 0, count = [byMonthDay count], ok = YES; i < count; i++)
{ {
@ -98,7 +107,7 @@ static BOOL NGMonthDaySet_fillWithByMonthDay (NGMonthDaySet *daySet,
ok = NO; ok = NO;
continue; /* error, value to large */ continue; /* error, value to large */
} }
if (dayInMonth < -31) if (dayInMonth < -31)
{ {
ok = NO; ok = NO;
continue; /* error, value to large */ continue; /* error, value to large */
@ -107,19 +116,16 @@ static BOOL NGMonthDaySet_fillWithByMonthDay (NGMonthDaySet *daySet,
/* adjust negative days */ /* adjust negative days */
if (dayInMonth < 0) if (dayInMonth < 0)
{ (*negativeDaySet)[abs(dayInMonth)] = YES;
/* eg: -1 == last day in month, 30 days => 30 */ else
dayInMonth = 32 - dayInMonth /* because we count from 1 */; (*positiveDaySet)[dayInMonth] = YES;
}
(*daySet)[dayInMonth] = YES;
} }
return ok; return ok;
} }
static inline unsigned iCalDoWForNSDoW (int dow) static inline unsigned iCalDoWForNSDoW (int dow)
{ {
switch (dow) switch (dow)
{ {
case 0: return iCalWeekDaySunday; case 0: return iCalWeekDaySunday;
case 1: return iCalWeekDayMonday; 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 - (BOOL) _addInstanceWithStartDate: (NSCalendarDate *)_startDate
limitDate: (NSCalendarDate *)_until limitDate: (NSCalendarDate *)_until
limitRange: (NGCalendarDateRange *)_r limitRange: (NGCalendarDateRange *)_r
@ -238,10 +150,7 @@ static void NGMonthDaySet_fillWithByDayX (NGMonthDaySet *daySet,
/* check whether we are still in the limits */ /* check whether we are still in the limits */
// TODO: I think we should check in here whether we succeeded the if (_until != nil)
// repeatCount. Currently we precalculate that info in the
// -lastInstanceStartDate method.
if (_until != nil)
{ {
/* Note: the 'until' in the rrule is inclusive as per spec */ /* Note: the 'until' in the rrule is inclusive as per spec */
if ([_until compare: _startDate] == NSOrderedAscending) if ([_until compare: _startDate] == NSOrderedAscending)
@ -259,29 +168,42 @@ static void NGMonthDaySet_fillWithByDayX (NGMonthDaySet *daySet,
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;
} }
/**
* 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 *) - (NSArray *)
recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
{ {
/* main entry */
// TODO: check whether this is OK for multiday-events! // TODO: check whether this is OK for multiday-events!
NSMutableArray *ranges; NSMutableArray *ranges;
NSTimeZone *timeZone; NSTimeZone *timeZone;
NSCalendarDate *eventStartDate, *rStart, *rEnd, *until; NSCalendarDate *eventStartDate, *rStart, *rEnd, *until, *referenceDate;
int eventDayOfMonth; int eventDayOfMonth;
unsigned monthIdxInRange, numberOfMonthsInRange, interval; unsigned monthIdxInRange, numberOfMonthsInRange, interval, repeatCount;
int diff; int diff, count;
NGMonthSet byMonthList = { // TODO: fill from rrule, this is the default NGMonthSet byMonthList = {
/* enable all months of the year */ // Enable all months of the year
YES, YES, YES, YES, YES, YES, YES, YES, YES, YES, YES, YES,
YES, YES, YES, YES, YES, YES YES, YES, YES, YES, YES, YES
}; };
NSArray *byMonthDay; // array of ints (-31..-1 and 1..31) NSArray *byMonth, *byMonthDay; // array of ints (-31..-1 and 1..31)
NGMonthDaySet byMonthDaySet; NGMonthDaySet byPositiveMonthDaySet, byNegativeMonthDaySet;
iCalByDayMask *byDayMask;
eventStartDate = [firstRange startDate]; eventStartDate = [firstRange startDate];
eventDayOfMonth = [eventStartDate dayOfMonth]; eventDayOfMonth = [eventStartDate dayOfMonth];
@ -289,48 +211,81 @@ static void NGMonthDaySet_fillWithByDayX (NGMonthDaySet *daySet,
rStart = [_r startDate]; rStart = [_r startDate];
rEnd = [_r endDate]; rEnd = [_r endDate];
interval = [rrule repeatInterval]; interval = [rrule repeatInterval];
until = [self lastInstanceStartDate]; // TODO: maybe replace until = nil;
repeatCount = [rrule repeatCount];
byMonth = [rrule byMonth];
byMonthDay = [rrule byMonthDay]; byMonthDay = [rrule byMonthDay];
byDayMask = [rrule byDayMask];
diff = 0;
if (![rrule isInfinite])
/* check whether the range to be processed is beyond the 'until' date */
if (until)
{ {
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; return nil;
if ([until compare: rEnd] == NSOrderedDescending) /* end before until */ if ([until compare: rEnd] == NSOrderedDescending)
rEnd = until; // TODO: why is that? end is _before_ until? // 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 NGMonthDaySet_fillWithByMonthDay (&byPositiveMonthDaySet, &byNegativeMonthDaySet, byMonthDay);
NSLog (@"byMonthDay: %@", byMonthDay);
#endif
NGMonthDaySet_fillWithByMonthDay (&byMonthDaySet, byMonthDay);
} }
if (repeatCount > 0)
// TODO: I think the 'diff' is to skip recurrence which are before the {
// requested range. Not sure whether this is actually possible, eg numberOfMonthsInRange = [eventStartDate monthsBetweenDate: rEnd] + 1;
// the repeatCount must be processed from the start. }
diff = [eventStartDate monthsBetweenDate: rStart]; else
if ((diff != 0) && [rStart compare: eventStartDate] == NSOrderedAscending) {
diff = -diff; diff = [eventStartDate monthsBetweenDate: rStart];
if ((diff != 0) && [rStart compare: eventStartDate] == NSOrderedAscending)
numberOfMonthsInRange = [rStart monthsBetweenDate: rEnd] + 1; diff = -diff;
numberOfMonthsInRange = [rStart monthsBetweenDate: rEnd] + 1;
}
ranges = [NSMutableArray arrayWithCapacity: numberOfMonthsInRange]; ranges = [NSMutableArray arrayWithCapacity: numberOfMonthsInRange];
/* // There's a bug in GNUstep in [NSCalendarDate dateByAddingYears:months:days:]
Note: we do not add 'eventStartDate', this is intentional, the event date // that causes errors when adding subsequently a month. For this reason,
itself is _not_ necessarily part of the sequence, eg with monthly // we set the day of the reference date to 1.
byday recurrences. referenceDate = [NSCalendarDate dateWithYear: [eventStartDate yearOfCommonEra]
*/ month: [eventStartDate monthOfYear]
day: 1
for (monthIdxInRange = 0; monthIdxInRange < numberOfMonthsInRange; hour: [eventStartDate hourOfDay]
minute: [eventStartDate minuteOfHour]
second: 0
timeZone: [eventStartDate timeZone]];
for (monthIdxInRange = 0, count = 0;
monthIdxInRange < numberOfMonthsInRange;
monthIdxInRange++) monthIdxInRange++)
{ {
NSCalendarDate *cursor; NSCalendarDate *cursor;
@ -349,63 +304,95 @@ static void NGMonthDaySet_fillWithByDayX (NGMonthDaySet *daySet,
if ((monthIdxInRecurrence % interval) != 0) if ((monthIdxInRecurrence % interval) != 0)
continue; continue;
/* cursor = [referenceDate dateByAddingYears: 0
Then the sequence is: months: monthIdxInRecurrence
- check whether the month is in the BYMONTH list days: 0];
*/
/*
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 setTimeZone: timeZone]; [cursor setTimeZone: timeZone];
numDaysInMonth = [cursor numberOfDaysInMonth]; numDaysInMonth = [cursor numberOfDaysInMonth];
/* check whether we match the BYMONTH constraint */
/* check whether we match the bymonth specification */
if (!byMonthList[[cursor monthOfYear] - 1]) if (!byMonthList[[cursor monthOfYear] - 1])
continue; continue;
/* check whether we match the BYMONTHDAY and BYDAY constraints */
/* check 'day level' byXYZ rules */
didByFill = NO; didByFill = NO;
if (byMonthDay) 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); NGMonthDaySet_clear (&monthDays);
monthDays[eventDayOfMonth] = YES; monthDays[eventDayOfMonth] = YES;
} }
// 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
@ -420,64 +407,25 @@ static void NGMonthDaySet_fillWithByDayX (NGMonthDaySet *daySet,
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? start = [cursor dateByAddingYears: 0 months: 0 days: (dom - 1)];
/*
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
doCont = [self _addInstanceWithStartDate: start doCont = [self _addInstanceWithStartDate: start
limitDate: until limitDate: until
limitRange: _r limitRange: _r
toArray: ranges]; 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 */ if (!doCont) break; /* reached some limit */
} }
@ -487,15 +435,30 @@ static void NGMonthDaySet_fillWithByDayX (NGMonthDaySet *daySet,
- (NSCalendarDate *) lastInstanceStartDate - (NSCalendarDate *) lastInstanceStartDate
{ {
NSCalendarDate *firStart, *lastInstanceStartDate; NSCalendarDate *firStart, *lastInstanceStartDate;
NGCalendarDateRange *r;
NSArray *instances;
if ([rrule repeatCount] > 0) lastInstanceStartDate = nil;
if ([rrule repeatCount] > 0)
{ {
firStart = [firstRange startDate]; if ([rrule hasByMask])
{
lastInstanceStartDate = [firStart dateByAddingYears: 0 // Must perform the complete calculation
months: ([rrule repeatInterval] firStart = [firstRange startDate];
* ([rrule repeatCount] - 1)) r = [NGCalendarDateRange calendarDateRangeWithStartDate: firStart
days: 0]; 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 else
lastInstanceStartDate = [super lastInstanceStartDate]; 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 NOTE: this is horribly inaccurate and doesn't even consider the use
of repeatCount. It MUST be implemented by subclasses properly! 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]; return [rrule untilDate];
} }

View File

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

View File

@ -1,5 +1,6 @@
/* /*
Copyright (C) 2004-2005 SKYRIX Software AG Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2006-2010 Inverse inc.
This file is part of SOPE. This file is part of SOPE.
@ -19,6 +20,168 @@
02111-1307, USA. 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/NSArray.h>
#import <Foundation/NSEnumerator.h> #import <Foundation/NSEnumerator.h>
#import <Foundation/NSException.h> #import <Foundation/NSException.h>
@ -27,13 +190,16 @@
#import <ctype.h> #import <ctype.h>
#import "NSCalendarDate+ICal.h"
#import "NSCalendarDate+NGCards.h" #import "NSCalendarDate+NGCards.h"
#import "NSString+NGCards.h" #import "NSString+NGCards.h"
#import "NSCalendarDate+ICal.h" #import "iCalByDayMask.h"
#import "iCalRecurrenceRule.h" #import "iCalRecurrenceRule.h"
NSString *iCalWeekDayString[] = { @"SU", @"MO", @"TU", @"WE", @"TH", @"FR",
@"SA" };
/* /*
freq = rrFreq; freq = rrFreq;
until = rrUntil; until = rrUntil;
@ -60,11 +226,6 @@
- (NSString *) wkst; - (NSString *) wkst;
- (NSString *) byDayList; - (NSString *) byDayList;
// - (void)_parseRuleString:(NSString *)_rrule;
/* currently used by parser, should be removed (replace with an -init..) */
- (void)setByday:(NSString *)_byDayList;
@end @end
@implementation iCalRecurrenceRule @implementation iCalRecurrenceRule
@ -85,6 +246,7 @@
if ((self = [super init]) != nil) if ((self = [super init]) != nil)
{ {
[self setTag: @"rrule"]; [self setTag: @"rrule"];
dayMask = nil;
} }
return self; return self;
@ -100,6 +262,12 @@
return self; return self;
} }
- (void) dealloc
{
[dayMask release];
[super dealloc];
}
- (void) setRrule: (NSString *) _rrule - (void) setRrule: (NSString *) _rrule
{ {
NSEnumerator *newValues; NSEnumerator *newValues;
@ -273,54 +441,30 @@
return [self weekDayFromICalRepresentation: [self wkst]]; return [self weekDayFromICalRepresentation: [self wkst]];
} }
- (void) setByDayMask: (unsigned) _mask - (void) setByDay: (NSString *) newByDay
{ {
NSMutableArray *days; [self setNamedValue: @"byday" to: newByDay];
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: @","]];
} }
- (unsigned int) byDayMask - (NSString *) byDay
{ {
NSArray *days; return [self namedValue: @"byday"];
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;
} }
#warning this is bad - (void) setByDayMask: (iCalByDayMask *) newByDayMask
- (int) byDayOccurence1
{ {
return 0; [self setByDay: [newByDayMask asRuleString]];
// return byDayOccurence1; }
- (iCalByDayMask *) byDayMask
{
if (dayMask == nil && [[self byDay] length])
{
dayMask = [iCalByDayMask byDayMaskWithRuleString: [self byDay]];
[dayMask retain];
}
return dayMask;
} }
- (NSArray *) byMonthDay - (NSArray *) byMonthDay
@ -337,6 +481,35 @@
return byMonthDay; 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 - (BOOL) isInfinite
{ {
return !([self repeatCount] || [self untilDate]); return !([self repeatCount] || [self untilDate]);
@ -356,6 +529,7 @@
dayLength = [_day length]; dayLength = [_day length];
if (dayLength > 1) if (dayLength > 1)
{ {
// Ignore any prefix, only consider last two characters
[[_day uppercaseString] getCharacters: chars [[_day uppercaseString] getCharacters: chars
range: NSMakeRange (dayLength - 2, 2)]; range: NSMakeRange (dayLength - 2, 2)];

View File

@ -26,6 +26,7 @@
#import "iCalDateTime.h" #import "iCalDateTime.h"
#import "iCalRecurrenceRule.h" #import "iCalRecurrenceRule.h"
#import "iCalByDayMask.h"
#import "iCalTimeZonePeriod.h" #import "iCalTimeZonePeriod.h"
@ -88,16 +89,16 @@
return ((negative) ? -seconds : seconds); return ((negative) ? -seconds : seconds);
} }
- (unsigned int) dayOfWeekFromRruleDay: (iCalWeekDay) day // - (unsigned int) dayOfWeekFromRruleDay: (iCalWeekDay) day
{ // {
unsigned int dayOfWeek; // unsigned int dayOfWeek;
dayOfWeek = 0; // dayOfWeek = 0;
while (day >> (dayOfWeek + 1)) // while (day >> (dayOfWeek + 1))
dayOfWeek++; // dayOfWeek++;
return dayOfWeek; // return dayOfWeek;
} // }
- (NSCalendarDate *) startDate - (NSCalendarDate *) startDate
{ {
@ -105,23 +106,34 @@
dateTime]; 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 - (NSCalendarDate *) _occurenceForDate: (NSCalendarDate *) refDate
byRRule: (iCalRecurrenceRule *) rrule byRRule: (iCalRecurrenceRule *) rrule
{ {
NSCalendarDate *tmpDate; NSCalendarDate *tmpDate;
NSString *byDay; iCalByDayMask *byDayMask;
int dayOfWeek, dateDayOfWeek, offset, pos; int dayOfWeek, dateDayOfWeek, offset, pos;
NSCalendarDate *tzStart; NSCalendarDate *tzStart;
byDay = [rrule namedValue: @"byday"]; byDayMask = [rrule byDayMask];
dayOfWeek = [self dayOfWeekFromRruleDay: [rrule byDayMask]]; dayOfWeek = 0;
pos = [[byDay substringToIndex: 2] intValue];
if (!pos)
/* if byday = "SU", instead of "1SU"... */
pos = 1;
if (byDayMask == nil)
{
dayOfWeek = 0;
pos = 1;
}
else
{
dayOfWeek = (int)[byDayMask firstDay];
pos = [byDayMask firstOccurrence];
}
tzStart = [self startDate]; tzStart = [self startDate];
[tzStart setTimeZone: [NSTimeZone timeZoneWithName: @"GMT"]]; [tzStart setTimeZone: [NSTimeZone timeZoneWithName: @"GMT"]];
tmpDate = [NSCalendarDate dateWithYear: [refDate yearOfCommonEra] tmpDate = [NSCalendarDate dateWithYear: [refDate yearOfCommonEra]

View File

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

View File

@ -1,5 +1,6 @@
/* /*
Copyright (C) 2004-2005 SKYRIX Software AG Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2006-2010 Inverse inc.
This file is part of SOPE. This file is part of SOPE.
@ -34,77 +35,234 @@
- (NSCalendarDate *) lastInstanceStartDate; - (NSCalendarDate *) lastInstanceStartDate;
@end @end
@class iCalMonthlyRecurrenceCalculator;
@implementation iCalYearlyRecurrenceCalculator @implementation iCalYearlyRecurrenceCalculator
- (NSArray *) - (NSArray *)
recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
{ {
NSMutableArray *ranges; NSMutableArray *ranges;
NSCalendarDate *firStart, *rStart, *rEnd, *until; NSArray *byMonth;
unsigned i, count, interval; NSCalendarDate *firStart, *lastDate, *rStart, *rEnd, *until, *referenceDate;
int diff; iCalMonthlyRecurrenceCalculator *monthlyCalc;
unsigned j, yearIdxInRange, numberOfYearsInRange, count, interval, monthDiff;
int diff, repeatCount, currentMonth;
firStart = [firstRange startDate]; firStart = [firstRange startDate];
rStart = [_r startDate]; rStart = [_r startDate];
rEnd = [_r endDate]; rEnd = [_r endDate];
interval = [rrule repeatInterval]; 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) lastDate = nil;
return nil; until = [rrule untilDate];
if ([until compare: rEnd] == NSOrderedDescending) repeatCount = [rrule repeatCount];
rEnd = until;
}
diff = [firStart yearsBetweenDate: rStart];
if ((diff != 0) && [rStart compare: firStart] == NSOrderedAscending)
diff = -diff;
count = [rStart yearsBetweenDate: rEnd] + 1; if (until)
ranges = [NSMutableArray arrayWithCapacity: count];
for (i = 0 ; i < count; i++)
{
int test;
test = diff + i;
if ((test >= 0) && (test % interval) == 0)
{ {
NSCalendarDate *start, *end; lastDate = until;
NGCalendarDateRange *r; }
if (repeatCount > 0)
start = [firStart dateByAddingYears: diff + i {
months: 0 if (lastDate == nil && ![rrule hasByMask])
days: 0]; // When there's no BYxxx mask, we can find the date of the last
[start setTimeZone: [firStart timeZone]]; // occurrence.
end = [start addTimeInterval: [firstRange duration]]; lastDate = [firStart dateByAddingYears: (interval * (repeatCount - 1))
r = [NGCalendarDateRange calendarDateRangeWithStartDate: start months: 0
endDate: end]; days: 0];
if ([_r containsDateRange: r]) referenceDate = firStart;
[ranges addObject: r]; }
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; return ranges;
} }
- (NSCalendarDate *) lastInstanceStartDate - (NSCalendarDate *) lastInstanceStartDate
{ {
NSCalendarDate *firStart, *lastInstanceStartDate; NSCalendarDate *firStart, *lastInstanceStartDate;
NGCalendarDateRange *r;
NSArray *instances;
if ([rrule repeatCount] > 0) lastInstanceStartDate = nil;
if ([rrule repeatCount] > 0)
{ {
firStart = [firstRange startDate]; if ([rrule hasByMask])
{
lastInstanceStartDate // Must perform the complete calculation
= [firStart dateByAddingYears: ([rrule repeatInterval] firStart = [firstRange startDate];
* ([rrule repeatCount] - 1)) r = [NGCalendarDateRange calendarDateRangeWithStartDate: firStart
months: 0 endDate: [NSCalendarDate distantFuture]];
days: 0]; 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 else
lastInstanceStartDate = [super lastInstanceStartDate]; lastInstanceStartDate = [super lastInstanceStartDate];
return lastInstanceStartDate; return lastInstanceStartDate;
} }

View File

@ -1,6 +1,6 @@
/* UIxAppointmentEditor.m - this file is part of SOGo /* 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> * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* *
@ -419,13 +419,11 @@
{ {
WOResponse *result; WOResponse *result;
NSDictionary *data; NSDictionary *data;
NSCalendarDate *firstDate, *eventDate; NSCalendarDate *eventDate;
NSTimeZone *timeZone; NSTimeZone *timeZone;
SOGoUserDefaults *ud; SOGoUserDefaults *ud;
SOGoCalendarComponent *co; SOGoCalendarComponent *co;
iCalEvent *master; BOOL resetAlarm;
BOOL resetAlarm;
signed int daylightOffset;
[self event]; [self event];
@ -457,20 +455,6 @@
[co saveComponent: event]; [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: data = [NSDictionary dictionaryWithObjectsAndKeys:
[componentCalendar displayName], @"calendar", [componentCalendar displayName], @"calendar",
[event tag], @"component", [event tag], @"component",

View File

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

View File

@ -31,6 +31,7 @@
#import <Foundation/NSURL.h> #import <Foundation/NSURL.h>
#import <NGCards/iCalAlarm.h> #import <NGCards/iCalAlarm.h>
#import <NGCards/iCalByDayMask.h>
#import <NGCards/iCalPerson.h> #import <NGCards/iCalPerson.h>
#import <NGCards/iCalRepeatableEntityObject.h> #import <NGCards/iCalRepeatableEntityObject.h>
#import <NGCards/iCalRecurrenceRule.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 - (void) _loadRRules
{ {
SOGoUserDefaults *ud; SOGoUserDefaults *ud;
@ -326,15 +308,12 @@ iRANGE(2);
rule = [[component recurrenceRules] lastObject]; rule = [[component recurrenceRules] lastObject];
/* DAILY */
if ([rule frequency] == iCalRecurrenceFrequenceDaily) if ([rule frequency] == iCalRecurrenceFrequenceDaily)
{ {
repeatType = @"0"; repeatType = @"0";
if ([rule byDayMask] == (iCalWeekDayMonday if ([[rule byDayMask] isWeekDays])
| iCalWeekDayTuesday
| iCalWeekDayWednesday
| iCalWeekDayThursday
| iCalWeekDayFriday))
{ {
if ([rule isInfinite]) if ([rule isInfinite])
repeat = @"EVERY WEEKDAY"; repeat = @"EVERY WEEKDAY";
@ -350,12 +329,14 @@ iRANGE(2);
[self setRepeat2: [NSString stringWithFormat: @"%d", [rule repeatInterval]]]; [self setRepeat2: [NSString stringWithFormat: @"%d", [rule repeatInterval]]];
} }
} }
/* WEEKLY */
else if ([rule frequency] == iCalRecurrenceFrequenceWeekly) else if ([rule frequency] == iCalRecurrenceFrequenceWeekly)
{ {
repeatType = @"1"; repeatType = @"1";
[self setRepeat1: [NSString stringWithFormat: @"%d", [rule repeatInterval]]]; [self setRepeat1: [NSString stringWithFormat: @"%d", [rule repeatInterval]]];
if (![rule byDayMask]) if (![[rule byDay] length])
{ {
if ([rule repeatInterval] == 1) if ([rule repeatInterval] == 1)
repeat = @"WEEKLY"; repeat = @"WEEKLY";
@ -364,45 +345,81 @@ iRANGE(2);
} }
else else
{ {
[self setRepeat2: [self _dayMaskToInteger: [rule byDayMask]]]; [self setRepeat2: [[rule byDayMask] asRuleStringWithIntegers]];
} }
} }
/* MONTHLY */
else if ([rule frequency] == iCalRecurrenceFrequenceMonthly) else if ([rule frequency] == iCalRecurrenceFrequenceMonthly)
{ {
repeatType = @"2"; 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 setRepeat2: @"0"];
[self setRepeat3: [NSString stringWithFormat: @"%d", firstOccurrence]];
[self setRepeat4: [NSString stringWithFormat: @"%d", [dayMask firstDay]]];
} }
else if ([[rule byMonthDay] count]) else if ([[rule byMonthDay] count])
{ {
[self setRepeat2: @"1"]; NSArray *days;
[self setRepeat5: [[rule byMonthDay] componentsJoinedByString: @","]];
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) else if ([rule repeatInterval] == 1)
repeat = @"MONTHLY"; repeat = @"MONTHLY";
[self setRepeat1: [NSString stringWithFormat: @"%d", [rule repeatInterval]]]; [self setRepeat1: [NSString stringWithFormat: @"%d", [rule repeatInterval]]];
} }
/* YEARLY */
else else
{ {
repeatType = @"3"; 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 setRepeat2: @"0"];
[self setRepeat3: [rule namedValue: @"bymonthday"]]; [self setRepeat3: [rule namedValue: @"bymonthday"]];
[self setRepeat4: [NSString stringWithFormat: @"%d", [[rule namedValue: @"bymonth"] intValue]-1]]; [self setRepeat4: [NSString stringWithFormat: @"%d", [[rule namedValue: @"bymonth"] intValue]-1]];
} }
else
{
// TODO
[self setRepeat2: @"1"];
}
} }
else if ([rule repeatInterval] == 1) else if ([rule repeatInterval] == 1)
repeat = @"YEARLY"; repeat = @"YEARLY";
@ -410,7 +427,7 @@ iRANGE(2);
[self setRepeat1: [NSString stringWithFormat: @"%d", [rule repeatInterval]]]; [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]) if ([rule repeatCount])
{ {
repeat = @"CUSTOM"; repeat = @"CUSTOM";
@ -1639,9 +1656,9 @@ RANGE(2);
switch (type) 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 // 0 -> Every X days
// 1 -> Every weekday // 1 -> Every weekday
// //
@ -1653,11 +1670,7 @@ RANGE(2);
if ([[self repeat1] intValue] > 0) if ([[self repeat1] intValue] > 0)
{ {
[theRule setByDayMask: (iCalWeekDayMonday [theRule setByDayMask: [iCalByDayMask byDayMaskWithWeekDays]];
|iCalWeekDayTuesday
|iCalWeekDayWednesday
|iCalWeekDayThursday
|iCalWeekDayFriday)];
} }
else else
{ {
@ -1670,7 +1683,7 @@ RANGE(2);
} }
break; break;
// WEEKLY // WEEKLY (1)
// //
// repeat1 holds the value of "Every X week(s)" // repeat1 holds the value of "Every X week(s)"
// //
@ -1683,7 +1696,8 @@ RANGE(2);
if ([[self repeat1] intValue] > 0) if ([[self repeat1] intValue] > 0)
{ {
NSArray *v; NSArray *v;
int c, mask; int c, day;
iCalWeekOccurrences days;
[theRule setFrequency: iCalRecurrenceFrequenceWeekly]; [theRule setFrequency: iCalRecurrenceFrequenceWeekly];
[theRule setInterval: [self repeat1]]; [theRule setInterval: [self repeat1]];
@ -1692,18 +1706,21 @@ RANGE(2);
{ {
v = [[self repeat2] componentsSeparatedByString: @","]; v = [[self repeat2] componentsSeparatedByString: @","];
c = [v count]; c = [v count];
mask = 0; memset(days, 0, 7 * sizeof(iCalWeekOccurrence));
while (c--) while (c--)
mask |= 1 << ([[v objectAtIndex: c] intValue]); {
day = [[v objectAtIndex: c] intValue];
[theRule setByDayMask: mask]; if (day >= 0 && day <= 7)
days[day] = iCalWeekOccurrenceAll;
}
[theRule setByDayMask: [iCalByDayMask byDayMaskWithDays: days]];
} }
} }
} }
break; break;
// MONTHLY // MONTHLY (2)
// //
// repeat1 holds the value of "Every X month(s)" // repeat1 holds the value of "Every X month(s)"
// //
@ -1731,20 +1748,29 @@ RANGE(2);
[theRule setInterval: [self repeat1]]; [theRule setInterval: [self repeat1]];
// We recur on specific days... // We recur on specific days...
if ([[self repeat2] intValue] == 1 if ([[self repeat2] intValue] == 0)
&& [[self repeat5] intValue] > 0) {
{ NSString *day;
[theRule setNamedValue: @"bymonthday" to: [self repeat5]]; int occurence;
}
else day = [theRule iCalRepresentationForWeekDay: [[self repeat4] intValue]];
{ occurence = [[self repeat3] intValue] + 1;
// TODO 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; break;
// YEARLY // YEARLY (3)
// //
// repeat1 holds the value of "Every X year(s)" // 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 ... ) // repeat4 holds the value of the MONTH parameter (0 -> January, 1 -> February ... )
// ex: 3 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..) // 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 ... ) // repeat7 holds the value of the MONTH parameter (0 -> January, 1 -> February ... )
// //
@ -1771,7 +1797,19 @@ RANGE(2);
// We recur Every .. of .. // We recur Every .. of ..
if ([[self repeat2] intValue] == 1) 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 else
{ {
@ -1883,11 +1921,7 @@ RANGE(2);
} }
else if ([repeat caseInsensitiveCompare: @"EVERY WEEKDAY"] == NSOrderedSame) else if ([repeat caseInsensitiveCompare: @"EVERY WEEKDAY"] == NSOrderedSame)
{ {
[rule setByDayMask: (iCalWeekDayMonday [rule setByDayMask: [iCalByDayMask byDayMaskWithWeekDays]];
|iCalWeekDayTuesday
|iCalWeekDayWednesday
|iCalWeekDayThursday
|iCalWeekDayFriday)];
[rule setFrequency: iCalRecurrenceFrequenceDaily]; [rule setFrequency: iCalRecurrenceFrequenceDaily];
} }
else if ([repeat caseInsensitiveCompare: @"MONTHLY"] == NSOrderedSame else if ([repeat caseInsensitiveCompare: @"MONTHLY"] == NSOrderedSame

View File

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