]> err.no Git - sope/commitdiff
implemented byday in monthly calculator
authorhelge <helge@e4a50df8-12e2-0310-a44c-efbce7f8a7e3>
Thu, 22 Sep 2005 10:08:24 +0000 (10:08 +0000)
committerhelge <helge@e4a50df8-12e2-0310-a44c-efbce7f8a7e3>
Thu, 22 Sep 2005 10:08:24 +0000 (10:08 +0000)
git-svn-id: http://svn.opengroupware.org/SOPE/trunk@1127 e4a50df8-12e2-0310-a44c-efbce7f8a7e3

sope-ical/NGiCal/ChangeLog
sope-ical/NGiCal/Version
sope-ical/NGiCal/iCalMonthlyRecurrenceCalculator.m
sope-ical/NGiCal/iCalRecurrenceRule.h
sope-ical/NGiCal/iCalRecurrenceRule.m

index f19df928bfb7e90ceb249b6dc4b2734f73ebcb9b..1d1f3ae96404b07ef3b118c88bb151a2176eb55f 100644 (file)
@@ -1,3 +1,12 @@
+2005-09-22  Helge Hess  <helge.hess@opengroupware.org>
+
+       * v4.5.67
+
+       * iCalMonthlyRecurrenceCalculator.m: finished 'byday' calculation
+
+       * iCalRecurrenceRule.m: added support for 'bymonthday', fixed handling
+         of occurrence1
+
 2005-09-21  Helge Hess  <helge.hess@skyrix.com>
 
        * v4.5.66
index 59e5bbe07852a0e1804ee5f863d8ba0fc5c42bbb..ead974b44c4f7c5ef6aff1ec7b8b6b8740870797 100644 (file)
@@ -2,7 +2,7 @@
 
 MAJOR_VERSION=4
 MINOR_VERSION=5
-SUBMINOR_VERSION:=66
+SUBMINOR_VERSION:=67
 
 # v4.5.40 requires NGExtensions v4.5.145
 # v4.5.37 requires NGExtensions v4.5.140
index a9045ab38d8b8adf9ca859949fc98860f6e76ec4..2bfcdf5d6ce3f44b52031fe9af41c9a68db6dc5d 100644 (file)
 @implementation iCalMonthlyRecurrenceCalculator
 
 typedef BOOL NGMonthSet[12];
-typedef BOOL NGMonthDaySet[31];
+typedef BOOL NGMonthDaySet[32]; // 0 is unused
 
 static void NGMonthDaySet_clear(NGMonthDaySet *daySet) {
   register unsigned i;
   
-  for (i = 0; i < 31; i++)
+  for (i = 1; i <= 31; i++)
     (*daySet)[i] = NO;
 }
 
@@ -53,32 +53,38 @@ static void NGMonthDaySet_copyOrUnion(NGMonthDaySet *base, NGMonthDaySet *new,
   if (doCopy)
     memcpy(base, new, sizeof(NGMonthDaySet));
   else {
-    for (i = 0; i < 31; i++) {
+    for (i = 1; i <= 31; i++) {
       if (!(*new)[i])
         (*base)[i] = NO;
     }
   }
 }
 
-static void NGMonthDaySet_fillWithByMonthDay(NGMonthDaySet *daySet, 
-                                             NSArray *byMonthDay,
-                                             int numDaysInMonth)
+static BOOL NGMonthDaySet_fillWithByMonthDay(NGMonthDaySet *daySet, 
+                                             NSArray *byMonthDay)
 {
   /* list of days in the month */
   unsigned i, count;
+  BOOL ok;
   
   NGMonthDaySet_clear(daySet);
 
-  for (i = 0, count = [byMonthDay count]; i < count; i++) {
+  for (i = 0, count = [byMonthDay count], ok = YES; i < count; i++) {
     int dayInMonth; /* -31..-1 and 1..31 */
         
-    if ((dayInMonth = [[byMonthDay objectAtIndex:i] intValue]) == 0)
+    if ((dayInMonth = [[byMonthDay objectAtIndex:i] intValue]) == 0) {
+      ok = NO;
       continue; /* invalid value */
-    if (dayInMonth > numDaysInMonth)
-      continue; /* this month has less days */
-    if (dayInMonth < -numDaysInMonth)
-      continue; /* this month has less days */
-        
+    }
+    if (dayInMonth > 31) {
+      ok = NO;
+      continue; /* error, value to large */
+    }
+    if (dayInMonth < -31) {
+      ok = NO;
+      continue; /* error, value to large */
+    }
+    
     /* adjust negative days */
         
     if (dayInMonth < 0) {
@@ -88,23 +94,96 @@ static void NGMonthDaySet_fillWithByMonthDay(NGMonthDaySet *daySet,
     
     (*daySet)[dayInMonth] = YES;
   }
+  return ok;
+}
+
+static inline unsigned iCalDoWForNSDoW(int dow) {
+  switch (dow) {
+  case 0: return iCalWeekDaySunday;
+  case 1: return iCalWeekDayMonday;
+  case 2: return iCalWeekDayTuesday;
+  case 3: return iCalWeekDayWednesday;
+  case 4: return iCalWeekDayThursday;
+  case 5: return iCalWeekDayFriday;
+  case 6: return iCalWeekDaySaturday;
+  case 7: return iCalWeekDaySunday;
+  default: return 0;
+  }
 }
 
 static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet, 
                                          unsigned dayMask,
+                                        unsigned firstDoWInMonth,
                                          int occurrence1)
 {
-  unsigned i, count;
+  // 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 unsigned dow; /* current day of the week */
+  int occurrences[7] = { 0, 0, 0, 0, 0, 0, 0 } ;
   
   NGMonthDaySet_clear(daySet);
-  // TODO: complete me
+  
+  for (dayInMonth = 1, dow = firstDoWInMonth; dayInMonth <= 31; dayInMonth++) {
+    // TODO: complete me
+    
+    if (dayMask & iCalDoWForNSDoW(dow)) {
+      if (occurrence1 == 0)
+       (*daySet)[dayInMonth] = YES;
+      else {
+       occurrences[dow] = occurrences[dow] + 1;
+       
+       if (occurrences[dow] == occurrence1) 
+         (*daySet)[dayInMonth] = YES;
+      }
+    }
+    
+    dow = (dow == 6 /* Sat */) ? 0 /* Sun */ : (dow + 1);
+  }
+}
+
+- (BOOL)_addInstanceWithStartDate:(NSCalendarDate *)_startDate
+  limitDate:(NSCalendarDate *)_until
+  limitRange:(NGCalendarDateRange *)_r
+  toArray:(NSMutableArray *)_ranges
+{
+  NGCalendarDateRange *r;
+  NSCalendarDate *end;
+  
+  /* 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) {
+    /* Note: the 'until' in the rrule is inclusive as per spec */
+    if ([_until compare:_startDate] == NSOrderedAscending)
+      /* start after until */
+      return NO; /* Note: we assume that the algorithm is sequential */
+  }
+
+  /* create end date */
+
+  end = [_startDate addTimeInterval:[self->firstRange duration]];
+  [end setTimeZone:[_startDate timeZone]];
+    
+  /* create range and check whether its in the requested range */
+  
+  r = [[NGCalendarDateRange alloc] initWithStartDate:_startDate endDate:end];
+  if ([_r containsDateRange:r])
+    [_ranges addObject:r];
+  [r release]; r = nil;
+  
+  return YES;
 }
 
 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r{
   /* main entry */
+  // TODO: check whether this is OK for multiday-events!
   NSMutableArray *ranges;
   NSTimeZone     *timeZone;
   NSCalendarDate *eventStartDate, *rStart, *rEnd, *until;
+  int            eventDayOfMonth;
   unsigned       monthIdxInRange, numberOfMonthsInRange, interval;
   int            diff;
   NGMonthSet byMonthList = { // TODO: fill from rrule, this is the default
@@ -112,20 +191,18 @@ static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet,
     YES, YES, YES, YES, YES, YES
   };
   NSArray *byMonthDay = nil; // array of ints (-31..-1 and 1..31)
+  NGMonthDaySet byMonthDaySet;
   
-  eventStartDate = [self->firstRange startDate];
-  timeZone = [eventStartDate timeZone];
-  rStart   = [_r startDate];
-  rEnd     = [_r endDate];
-  interval = [self->rrule repeatInterval];
-  until    = [self lastInstanceStartDate]; // TODO: maybe replace
-  
-  if ([self->rrule byDayMask] != 0) {
-    [self errorWithFormat:@"cannot process byday part of rrule: %@", 
-           self->rrule];
-    return nil;
-  }
+  eventStartDate  = [self->firstRange startDate];
+  eventDayOfMonth = [eventStartDate dayOfMonth];
+  timeZone   = [eventStartDate timeZone];
+  rStart     = [_r startDate];
+  rEnd       = [_r endDate];
+  interval   = [self->rrule repeatInterval];
+  until      = [self lastInstanceStartDate]; // TODO: maybe replace
+  byMonthDay = [self->rrule byMonthDay];
   
+
   /* check whether the range to be processed is beyond the 'until' date */
   
   if (until != nil) {
@@ -134,6 +211,13 @@ static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet,
     if ([until compare:rEnd] == NSOrderedDescending) /* end before until */
       rEnd = until; // TODO: why is that? end is _before_ until?
   }
+
+  
+  /* precalculate month days (same for all instances) */
+
+  if (byMonthDay != nil)
+    NGMonthDaySet_fillWithByMonthDay(&byMonthDaySet, byMonthDay);
+  
   
   // TODO: I think the 'diff' is to skip recurrence which are before the
   //       requested range. Not sure whether this is actually possible, eg
@@ -153,13 +237,11 @@ static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet,
   
   for (monthIdxInRange = 0; monthIdxInRange < numberOfMonthsInRange; 
        monthIdxInRange++) {
-    NSCalendarDate      *cursor;
-    NSCalendarDate      *start, *end;
-    NGCalendarDateRange *r;
-    unsigned            numDaysInMonth;
-    int                 monthIdxInRecurrence;
-    NGMonthDaySet monthDays;
-    BOOL          didByFill;
+    NSCalendarDate *cursor;
+    unsigned       numDaysInMonth;
+    int            monthIdxInRecurrence, dom;
+    NGMonthDaySet  monthDays;
+    BOOL           didByFill, doCont;
     
     monthIdxInRecurrence = diff + monthIdxInRange;
     
@@ -194,52 +276,51 @@ static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet,
     didByFill = NO;
     
     if (byMonthDay != nil) { /* list of days in the month */
-      NGMonthDaySet ruleset;
-      
-      NGMonthDaySet_fillWithByMonthDay(&ruleset, byMonthDay, numDaysInMonth);
-      NGMonthDaySet_copyOrUnion(&monthDays, &ruleset, !didByFill);
+      NGMonthDaySet_copyOrUnion(&monthDays, &byMonthDaySet, !didByFill);
       didByFill = YES;
     }
     
     if ([self->rrule byDayMask] != 0) { // TODO: replace the mask with an array
       NGMonthDaySet ruleset;
+      unsigned firstDoWInMonth;
+      
+      firstDoWInMonth = [[cursor firstDayOfMonth] dayOfWeek];
       
       NGMonthDaySet_fillWithByDayX(&ruleset, 
                                    [self->rrule byDayMask],
+                                  firstDoWInMonth,
                                    [self->rrule byDayOccurence1]);
       NGMonthDaySet_copyOrUnion(&monthDays, &ruleset, !didByFill);
       didByFill = YES;
     }
     
-    
-    // TODO: complete byday support
-    
-    /* set start date */
-    
-    start = cursor;
-    
-    /* 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) {
-      /* Note: the 'until' in the rrule is inclusive as per spec */
-      if ([until compare:start] == NSOrderedAscending) /* start after until */
-        break; /* Note: we assume that the algorithm is sequential */
+    if (!didByFill) {
+      /* no rules applied, take the dayOfMonth of the startDate */
+      NGMonthDaySet_clear(&monthDays);
+      monthDays[eventDayOfMonth] = YES;
     }
     
-    /* create end date */
-
-    end   = [start addTimeInterval:[self->firstRange duration]];
-    [end setTimeZone:timeZone];
+    // TODO: add processing of byhour/byminute/bysecond etc
     
-    /* create range and check whether its in the requested range */
-    
-    r = [[NGCalendarDateRange alloc] initWithStartDate:start endDate:end];
-    if ([_r containsDateRange:r])
-      [ranges addObject:r];
-    [r release]; r = nil;
+    for (dom = 1, doCont = YES; dom <= numDaysInMonth && doCont; dom++) {
+      NSCalendarDate *start;
+      
+      if (!monthDays[dom])
+       continue;
+      
+      if (eventDayOfMonth == dom)
+       start = cursor;
+      else {
+       start = [cursor dateByAddingYears:0 months:0
+                       days:(dom - eventDayOfMonth)];
+      }
+      
+      doCont = [self _addInstanceWithStartDate:start
+                    limitDate:until
+                    limitRange:_r
+                    toArray:ranges];
+    }
+    if (!doCont) break; /* reached some limit */
   }
   return ranges;
 }
index a36d67536ba58815e145b0595837709efc8267a6..b1ba5022bcfe2d737498b7bc9b9020619d6345de 100644 (file)
@@ -55,7 +55,7 @@ typedef enum {
   iCalWeekDaySunday    = 64,
 } iCalWeekDay;
 
-@class NSString, NSCalendarDate, NGCalendarDateRange;
+@class NSString, NSCalendarDate, NGCalendarDateRange, NSArray;
 
 @interface iCalRecurrenceRule : NSObject
 {
@@ -70,7 +70,8 @@ typedef enum {
     unsigned reserved:1;
   } byDay;
   int byDayOccurence1;
-
+  NSArray        *byMonthDay;
+  
   NSString       *rrule;
 }
 
@@ -91,6 +92,8 @@ typedef enum {
 - (void)setByDayMask:(unsigned)_mask;
 - (unsigned)byDayMask;
 - (int)byDayOccurence1;
+
+- (NSArray *)byMonthDay;
   
 /* count and untilDate are mutually exclusive */
 
index f437738a57e707ab7083a3aa6ac7474aaba576f9..a8aaec0449083c210a77a16f26f0e00aae5ea0e9 100644 (file)
@@ -80,6 +80,7 @@
 }
 
 - (void)dealloc {
+  [self->byMonthDay release];
   [self->untilDate release];
   [self->rrule     release];
   [super dealloc];
 }
 
 - (void)setUntilDate:(NSCalendarDate *)_untilDate {
-  ASSIGN(self->untilDate, _untilDate);
+  ASSIGNCOPY(self->untilDate, _untilDate);
 }
 - (NSCalendarDate *)untilDate {
   return self->untilDate;
   return self->byDayOccurence1;
 }
 
+- (NSArray *)byMonthDay {
+  return self->byMonthDay;
+}
+
 - (BOOL)isInfinite {
   return (self->repeatCount != 0 || self->untilDate) ? NO : YES;
 }
 */
 - (NSString *)byDayList {
   NSMutableString *s;
-  unsigned        i, mask, day;
+  unsigned        dow, mask, day;
   BOOL            needsComma;
   
   s          = [NSMutableString stringWithCapacity:20];
   mask       = self->byDay.mask;
   day        = iCalWeekDayMonday;
   
-  for (i = 0; i < 7; i++) {
+  for (dow = 0 /* Sun */; dow < 7; dow++) {
     if (mask & day) {
       if (needsComma)
         [s appendString:@","];
-      else if (self->byDay.useOccurence)
-       // Note: we only support one occurrence currently
+      
+      if (self->byDay.useOccurence)
+       // Note: we only support one occurrence for all currently
        [s appendFormat:@"%i", self->byDayOccurence1];
       
       [s appendString:[self iCalRepresentationForWeekDay:day]];
   NSString *pUntil     = nil;
   NSString *pCount     = nil;
   NSString *pByday     = nil;
+  NSString *pBymday    = nil;
   NSString *pBysetpos  = nil;
   
   props = [_rrule componentsSeparatedByString:@";"];
     vHolder = NULL;
     switch ([key characterAtIndex:0]) {
     case 'b':
-      if ([key isEqualToString:@"byday"])    vHolder = &pByday;    break;
-      if ([key isEqualToString:@"bysetpos"]) vHolder = &pBysetpos; break;
+      if ([key isEqualToString:@"byday"])      { vHolder = &pByday;    break; }
+      if ([key isEqualToString:@"bymonthday"]) { vHolder = &pBymday;   break; }
+      if ([key isEqualToString:@"bysetpos"])   { vHolder = &pBysetpos; break; }
       break;
     case 'c':
-      if ([key isEqualToString:@"count"])    vHolder = &pCount;    break;
+      if ([key isEqualToString:@"count"]) { vHolder = &pCount; break; }
       break;
     case 'f':
-      if ([key isEqualToString:@"freq"]) vHolder = &pFrequency; break;
+      if ([key isEqualToString:@"freq"]) { vHolder = &pFrequency; break; }
       break;
     case 'u':
-      if ([key isEqualToString:@"until"]) vHolder = &pUntil; break;
+      if ([key isEqualToString:@"until"]) { vHolder = &pUntil; break; }
       break;
     default:
       break;
     else {
       // TODO: we should just parse known keys and put remainders into a
       //       separate dictionary
-      //[self logWithFormat:@"TODO: add explicit support for key: %@", key];
+      [self logWithFormat:@"TODO: add explicit support for key: %@", key];
       [self takeValue:value forKey:key];
     }
   }
   // TODO: we should parse byday in here
   if (pByday != nil) [self setByday:pByday];
   [pByday release]; pByday = nil;
+
+  if (pBymday != nil) {
+    NSArray *t;
+    
+    t = [pBymday componentsSeparatedByString:@","];
+    ASSIGNCOPY(self->byMonthDay, t);
+  }
+  [pBymday release]; pBymday = nil;
   
   if (pBysetpos != nil)
     // TODO: implement
       int offset;
       
       occurence = [iCalDay intValue];
-
-      offset = 1;
+      
+      offset = 1; /* skip occurence */
       while (offset < len && isdigit([iCalDay characterAtIndex:offset]))
        offset++;
       
       iCalDay = [iCalDay substringFromIndex:offset];
       
-      if (self->byDay.useOccurence) {
+      if (self->byDay.useOccurence && (occurence != self->byDayOccurence1)) {
        [self errorWithFormat:
                @"we only supported one occurence (occ=%i,day=%@): '%@'", 
                occurence, iCalDay, _byDayList];