]> err.no Git - sope/commitdiff
recurrence work in progress
authorznek <znek@e4a50df8-12e2-0310-a44c-efbce7f8a7e3>
Sat, 12 Feb 2005 22:03:18 +0000 (22:03 +0000)
committerznek <znek@e4a50df8-12e2-0310-a44c-efbce7f8a7e3>
Sat, 12 Feb 2005 22:03:18 +0000 (22:03 +0000)
git-svn-id: http://svn.opengroupware.org/SOPE/trunk@554 e4a50df8-12e2-0310-a44c-efbce7f8a7e3

12 files changed:
sope-ical/NGiCal/ChangeLog
sope-ical/NGiCal/NGiCal.xcode/project.pbxproj
sope-ical/NGiCal/NSCalendarDate+ICal.h
sope-ical/NGiCal/NSCalendarDate+ICal.m
sope-ical/NGiCal/Version
sope-ical/NGiCal/iCalEvent.h
sope-ical/NGiCal/iCalEvent.m
sope-ical/NGiCal/iCalRecurrenceCalculator.h
sope-ical/NGiCal/iCalRecurrenceCalculator.m
sope-ical/NGiCal/iCalRepeatableEntityObject.h
sope-ical/NGiCal/iCalRepeatableEntityObject.m
sope-ical/NGiCal/tests/iCalRecurrenceCalculatorTests.m

index f79d637bb4c022d14b3ba18afc8a9b0b2abcfa30..7799a49c74385ce5aef478574bec433ac9cd3eb7 100644 (file)
@@ -1,3 +1,23 @@
+2005-02-12  Marcus Mueller  <znek@mulle-kybernetik.com>
+
+       * v4.5.40
+
+       * iCalRecurrenceCalculator.[hm]: implemented all required (and simple)
+         calculations. Added some convenience API to query some of the more
+         obvious ranges suitable as limits for fetching/comparison. 
+
+       * iCalRepeatableEntityObject.[hm]: new method for calculating the
+         last possible recurrence start date. This can be used for fetches
+         as well.
+
+       * iCalEvent.[hm]: more convenient wrapper for the new method found in
+         iCalRepeatableEntityObject.
+
+       * NSCalendarDate+ICal.[hm]: convenience methods for calculating
+         "distances" between dates.
+
+       * tests/*: updated
+
 2005-02-11  Marcus Mueller  <znek@mulle-kybernetik.com>
 
        * v4.5.39
index ec2b5a23b339b79fb04b0599f65e3dc0ff57c92f..33a59f72a8b7034f1a85f05dd0624baa2cb1ddda 100644 (file)
                        );
                        buildSettings = {
                                DYLIB_COMPATIBILITY_VERSION = 1;
-                               DYLIB_CURRENT_VERSION = 4.5.39;
+                               DYLIB_CURRENT_VERSION = 4.5.40;
                                FRAMEWORK_SEARCH_PATHS = "\"$(USER_LIBRARY_DIR)/EmbeddedFrameworks\"";
                                FRAMEWORK_VERSION = A;
                                GCC_PRECOMPILE_PREFIX_HEADER = NO;
                        );
                        buildSettings = {
                                DYLIB_COMPATIBILITY_VERSION = 1;
-                               DYLIB_CURRENT_VERSION = 4.5.39;
+                               DYLIB_CURRENT_VERSION = 4.5.40;
                                FRAMEWORK_SEARCH_PATHS = "$(LOCAL_LIBRARY_DIR)/Frameworks";
                                FRAMEWORK_VERSION = A;
                                GCC_PRECOMPILE_PREFIX_HEADER = YES;
index ea594978ab8c139b809238bbeede85bac7ea5061..2af61b3f4649e5e0b95c018e933ee7997aaf763f 100644 (file)
 
 @end
 
+@interface NSCalendarDate (iCalRecurrenceCalculatorExtensions)
+- (unsigned)yearsBetweenDate:(NSCalendarDate *)_date;
+- (unsigned)monthsBetweenDate:(NSCalendarDate *)_date;
+- (unsigned)daysBetweenDate:(NSCalendarDate *)_date;
+@end
+
 #endif /* __ICal2_NSCalendarDate_ICal_H__ */
index 777dbb6df203126aa6747cae1fa4bf44fe6faf54..7a51986c9dfb65b581c913a666049660a557af9f 100644 (file)
@@ -82,3 +82,48 @@ static NSString *gmtcalfmt = @"%Y%m%dT%H%M00Z";
 }
 
 @end /* NSDate(ICalValue) */
+
+
+#ifndef ABS
+#define ABS(a) ((a) < 0 ? -(a) : (a))
+#endif
+
+@implementation NSCalendarDate (iCalRecurrenceCalculatorExtensions)
+
+- (unsigned)yearsBetweenDate:(NSCalendarDate *)_date {
+  return ABS([self yearOfCommonEra] - [_date yearOfCommonEra]);
+}
+
+- (unsigned)monthsBetweenDate:(NSCalendarDate *)_date {
+  NSCalendarDate     *start, *end;
+  NSComparisonResult order;
+  int                yDiff;
+  
+  order = [self compare:_date];
+  if (order == NSOrderedSame)
+    return 0;
+  else if (order == NSOrderedAscending) {
+    start = self;
+    end   = _date;
+  }
+  else {
+    start = _date;
+    end   = self;
+  }
+  yDiff = [end yearOfCommonEra] - [start yearOfCommonEra];
+  if (yDiff > 0) {
+    unsigned monthsRemaining, monthsToGo;
+    
+    monthsRemaining = 12 - [start monthOfYear];
+    monthsToGo      = [end monthOfYear];
+    yDiff          -= 1;
+    return monthsRemaining + monthsToGo + (12 * yDiff);
+  }
+  /* start and end in same year, calculate plain diff */
+  return [end monthOfYear] - [start monthOfYear];
+}
+
+- (unsigned)daysBetweenDate:(NSCalendarDate *)_date {
+  return ABS([self julianNumber] - [_date julianNumber]);
+}
+@end /* NSCalendarDate (iCalRecurrenceCalculatorExtensions) */
index c2c1f65e679caf7fb72d0039366e602e8ef12dd9..99d8f0dbc41ed455a2f8fac7aa56811d93e6edb1 100644 (file)
@@ -2,6 +2,7 @@
 
 MAJOR_VERSION=4
 MINOR_VERSION=5
-SUBMINOR_VERSION:=39
+SUBMINOR_VERSION:=40
 
+# v4.5.40 requires NGExtensions v4.5.145
 # v4.5.37 requires NGExtensions v4.5.140
index 2c7489a27d3aff32447ea8226d189b76535fee7a..59c2602a20223c64f63bf822a3fd7f9e769b91bd 100644 (file)
@@ -62,6 +62,7 @@
 - (BOOL)isAllDay;
 
 - (BOOL)isWithinCalendarDateRange:(NGCalendarDateRange *)_range;
+- (NSCalendarDate *)lastPossibleRecurrenceStartDate;
 
 /* calculating changes */
 
index bb60b10b62e1498ebb05bd0a2fe5bd6518c9908f..34217f02f23cb604d84ac8471df1d70037dca0e1 100644 (file)
   return NO;
 }
 
+- (NSCalendarDate *)lastPossibleRecurrenceStartDate {
+  NGCalendarDateRange *fir;
+
+  if (![self isRecurrent])
+    return nil;
+
+  fir = [NGCalendarDateRange calendarDateRangeWithStartDate:self->startDate
+                             endDate:self->endDate];
+  return [self lastPossibleRecurrenceStartDateUsingFirstInstanceCalendarDateRange:fir];
+}
+
 /* ical typing */
 
 - (NSString *)entityName {
index 49ca6b7f02f112493135ad7efa097c8a8549c9ef..60cf528961790d4e93f46efa0be9500d94fe914a 100644 (file)
@@ -48,6 +48,9 @@
 - (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r;
 - (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range;
 
+- (NGCalendarDateRange *)firstInstanceCalendarDateRange;
+- (NGCalendarDateRange *)lastInstanceCalendarDateRange; /* might be nil */
+  
 @end
 
 #endif /* __NGiCal_iCalRecurrenceCalculator_H_ */
index 63e7b91b9a515830fc3bf289a131d447b25db840..83b57d8dfddf34fb369264d58eaa87ff3e7ba863 100644 (file)
@@ -22,9 +22,9 @@
 #include "iCalRecurrenceCalculator.h"
 #include <NGExtensions/NGCalendarDateRange.h>
 #include "iCalRecurrenceRule.h"
+#include "NSCalendarDate+ICal.h"
 #include "common.h"
 
-
 /* class cluster */
 
 @interface iCalDailyRecurrenceCalculator : iCalRecurrenceCalculator
 }
 @end
 
-/* superclass */
+/* Private */
+
+@interface iCalRecurrenceCalculator (PrivateAPI)
+- (NSCalendarDate *)lastInstanceStartDate;
+@end
 
 @implementation iCalRecurrenceCalculator
 
@@ -109,76 +113,136 @@ static Class yearlyCalcClass  = Nil;
   return nil; /* subclass responsibility */
 }
 - (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
-  return NO; /* subclass responsibility */
+  NSArray *ranges;
+
+  ranges = [self recurrenceRangesWithinCalendarDateRange:_range];
+  return (ranges == nil || [ranges count] == 0) ? NO : YES;
 }
 
-@end /* iCalRecurrenceCalculator */
+- (NGCalendarDateRange *)firstInstanceCalendarDateRange {
+  return self->firstRange;
+}
 
+- (NGCalendarDateRange *)lastInstanceCalendarDateRange {
+  NSCalendarDate *start, *end;
 
-/* ZNeK: NOTE
-taeglich:
-alle events
-fuer all diese jeweils
-differenz der julian numbers durch interval teilen, wenn ok, ...
-*/
-@implementation iCalDailyRecurrenceCalculator
+  start = [self lastInstanceStartDate];
+  if (!start)
+    return nil;
+  end   = [start addTimeInterval:[self->firstRange duration]];
+  return [NGCalendarDateRange calendarDateRangeWithStartDate:start
+                              endDate:end];
+}
 
-- (unsigned)factor {
-  return 1;
+- (NSCalendarDate *)lastInstanceStartDate {
+  NSCalendarDate *until;
+  
+  /* NOTE: this is horribly inaccurate and doesn't even consider the use
+  of repeatCount. It MUST be implemented by subclasses properly! However,
+  it does the trick for SOGO 1.0 - that's why it's left here.
+  */
+  if ((until = [self->rrule untilDate]) != nil)
+    return until;
+  return nil;
 }
 
-- (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
-  long     i, jnFirst, jnStart, jnEnd, startEndCount;
-  unsigned interval;
+@end /* iCalRecurrenceCalculator */
 
-  jnFirst  = [[self->firstRange startDate] julianNumber];
-  jnEnd    = [[_range endDate] julianNumber];
 
-  if (jnFirst > jnEnd)
-    return NO;
+@implementation iCalDailyRecurrenceCalculator
 
-  jnStart  = [[_range startDate] julianNumber];
+- (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
+  NSMutableArray *ranges;
+  NSCalendarDate *firStart;
+  long           i, jnFirst, jnStart, jnEnd, startEndCount;
+  unsigned       interval;
+
+  firStart = [self->firstRange startDate];
+  jnFirst  = [firStart julianNumber];
+  jnEnd    = [[_r endDate] julianNumber];
+  
+  if (jnFirst > jnEnd)
+    return nil;
+  
+  jnStart  = [[_r startDate] julianNumber];
   interval = [self->rrule repeatInterval];
-
+  
   /* if rule is bound, check the bounds */
   if (![self->rrule isInfinite]) {
     NSCalendarDate *until;
     long           jnRuleLast;
-
+    
     until = [self->rrule untilDate];
     if (until) {
-      if ([until compare:[_range startDate]] == NSOrderedAscending)
-        return NO;
+      if ([until compare:[_r startDate]] == NSOrderedAscending)
+        return nil;
       jnRuleLast = [until julianNumber];
     }
     else {
       jnRuleLast = (interval * [self->rrule repeatCount] * [self factor])
-                   + jnFirst;
+      + jnFirst;
       if (jnRuleLast < jnStart)
-        return NO;
+        return nil;
     }
     /* jnStart < jnRuleLast < jnEnd ? */
     if (jnEnd > jnRuleLast)
       jnEnd = jnRuleLast;
   }
 
+  ranges        = [NSMutableArray arrayWithCapacity:5];
   startEndCount = (jnEnd - jnStart) + 1;
   for (i = 0 ; i < startEndCount; i++) {
     long jnTest;
 
     jnTest = (jnStart + i) - jnFirst;
-    if ((jnTest % interval) == 0)
-      return YES;
+    if ((jnTest % interval) == 0) {
+      NSCalendarDate      *start, *end;
+      NGCalendarDateRange *r;
+    
+      start = [NSCalendarDate dateForJulianNumber:jnStart + i];
+      start = [start hour:  [firStart hourOfDay]
+                     minute:[firStart minuteOfHour]
+                     second:[firStart secondOfMinute]];
+      end   = [start addTimeInterval:[self->firstRange duration]];
+      r     = [NGCalendarDateRange calendarDateRangeWithStartDate:start
+                                   endDate:end];
+      [ranges addObject:r];
+    }
+  }
+  
+  return ranges;
+}
+
+- (NSCalendarDate *)lastInstanceStartDate {
+  if ([self->rrule repeatCount] > 0) {
+    long           jnFirst, jnRuleLast;
+    NSCalendarDate *firStart, *until;
+
+    firStart   = [self->firstRange startDate];
+    jnFirst    = [firStart julianNumber];
+    jnRuleLast = ([self->rrule repeatInterval] *
+                  [self->rrule repeatCount] *
+                  [self factor]) +
+                  jnFirst;
+    until      = [NSCalendarDate dateForJulianNumber:jnRuleLast];
+    until      = [until hour:  [firStart hourOfDay]
+                        minute:[firStart minuteOfHour]
+                        second:[firStart secondOfMinute]];
+    return until;
   }
-  return NO;
+  return [super lastInstanceStartDate];
+}
+
+- (unsigned)factor {
+  return 1;
 }
 
 @end /* iCalDailyRecurrenceCalculator */
 
+
 /*
-  ZNeK: NOTE
-  woechentlich:
-  wie taeglich, aber teilen durch interval*7
+   TODO: If BYDAY is specified, lastInstanceStartDate and recurrences will
+         differ significantly!
 */
 @implementation iCalWeeklyRecurrenceCalculator
 
@@ -188,33 +252,122 @@ differenz der julian numbers durch interval teilen, wenn ok, ...
 
 @end /* iCalWeeklyRecurrenceCalculator */
 
-/*
-  ZNeK: NOTE
-  monatlich:
-  monatlich: differenz der monate / interval
-  alle events mit korrektem tag.
-  dann differenz der monate durch interval teilen, wenn ok, ...
-  (diffrenz der monate = 12 * 'volle' jahre plus monate im start jahr und monate im ziel jahr
- */
 @implementation iCalMonthlyRecurrenceCalculator
 
-- (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
-  return YES;
+- (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
+  NSMutableArray *ranges;
+  NSCalendarDate *firStart, *rStart, *rEnd, *until;
+  unsigned       i, count, interval, diff;
+
+  firStart = [self->firstRange startDate];
+  rStart   = [_r startDate];
+  rEnd     = [_r endDate];
+  interval = [self->rrule repeatInterval];
+  until    = [self lastInstanceStartDate];
+
+  if (until) {
+    if ([until compare:rStart] == NSOrderedAscending)
+      return nil;
+    if ([until compare:rEnd] == NSOrderedDescending)
+      rEnd = until;
+  }
+
+  diff   = [firStart monthsBetweenDate:rStart];
+  count  = [rStart monthsBetweenDate:rEnd] + 1;
+  ranges = [NSMutableArray arrayWithCapacity:count];
+  for (i = 0 ; i < count; i++) {
+    unsigned test;
+    
+    test = diff + i;
+    if ((test % interval) == 0) {
+      NSCalendarDate      *start, *end;
+      NGCalendarDateRange *r;
+      
+      start = [firStart dateByAddingYears:0
+                        months:diff + i
+                        days:0];
+      end   = [start addTimeInterval:[self->firstRange duration]];
+      r     = [NGCalendarDateRange calendarDateRangeWithStartDate:start
+                                   endDate:end];
+      [ranges addObject:r];
+    }
+  }
+  return ranges;
+}
+
+- (NSCalendarDate *)lastInstanceStartDate {
+  if ([self->rrule repeatCount] > 0) {
+    NSCalendarDate *until;
+    unsigned       months, interval;
+
+    interval = [self->rrule repeatInterval];
+    months   = [self->rrule repeatCount] * interval;
+    until    = [[self->firstRange startDate] dateByAddingYears:0
+                                             months:months
+                                             days:0];
+    return until;
+  }
+  return [super lastInstanceStartDate];
 }
 
 @end /* iCalMonthlyRecurrenceCalculator */
 
-/*
-  ZNeK: NOTE
-  jaehrlich:
-  alle events mit dem korrekten tag und monat.
-  fuer all diese jeweils
-  differenz in jahren durch interval teilen, wenn ok, dann event fuer den tag.
-*/
 @implementation iCalYearlyRecurrenceCalculator
 
-- (BOOL)doesRecurrWithinCalendarDateRange:(NGCalendarDateRange *)_range {
-  return YES;
+- (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r {
+  NSMutableArray *ranges;
+  NSCalendarDate *firStart, *rStart, *rEnd, *until;
+  unsigned       i, count, interval, diff;
+  
+  firStart = [self->firstRange startDate];
+  rStart   = [_r startDate];
+  rEnd     = [_r endDate];
+  interval = [self->rrule repeatInterval];
+  until    = [self lastInstanceStartDate];
+  
+  if (until) {
+    if ([until compare:rStart] == NSOrderedAscending)
+      return nil;
+    if ([until compare:rEnd] == NSOrderedDescending)
+      rEnd = until;
+  }
+  
+  diff   = [firStart yearsBetweenDate:rStart];
+  count  = [rStart yearsBetweenDate:rEnd] + 1;
+  ranges = [NSMutableArray arrayWithCapacity:count];
+  for (i = 0 ; i < count; i++) {
+    unsigned test;
+    
+    test = diff + i;
+    if ((test % interval) == 0) {
+      NSCalendarDate      *start, *end;
+      NGCalendarDateRange *r;
+      
+      start = [firStart dateByAddingYears:diff + i
+                        months:0
+                        days:0];
+      end   = [start addTimeInterval:[self->firstRange duration]];
+      r     = [NGCalendarDateRange calendarDateRangeWithStartDate:start
+                                   endDate:end];
+      [ranges addObject:r];
+    }
+  }
+  return ranges;
+}
+
+- (NSCalendarDate *)lastInstanceStartDate {
+  if ([self->rrule repeatCount] > 0) {
+    NSCalendarDate *until;
+    unsigned       years, interval;
+    
+    interval = [self->rrule repeatInterval];
+    years    = [self->rrule repeatCount] * interval;
+    until    = [[self->firstRange startDate] dateByAddingYears:years
+                                             months:0
+                                             days:0];
+    return until;
+  }
+  return [super lastInstanceStartDate];
 }
 
 @end /* iCalYearlyRecurrenceCalculator */
index 33509a074afbefb26816a2084e12ae6c2b2726e2..7fb90d6d1adc4005890949be2d21bc2d9812e600 100644 (file)
@@ -59,6 +59,9 @@
 - (BOOL)isWithinCalendarDateRange:(NGCalendarDateRange *)_range
   firstInstanceCalendarDateRange:(NGCalendarDateRange *)_fir;
 
+/* this is the outmost bound possible, not necessarily the real last date */
+- (NSCalendarDate *)lastPossibleRecurrenceStartDateUsingFirstInstanceCalendarDateRange:(NGCalendarDateRange *)_r;
+
 @end
 
 #endif /* __NGiCal_iCalRepeatableEntityObject_H_ */
index 3b4345e317d6d1c0872b1d2c18e48536e13d2853..01242e3f89ed9b953e2202cadb7d9dfb49a19edc 100644 (file)
   return YES;
 }
 
+/* this is the outmost bound possible, not necessarily the real last date */
+- (NSCalendarDate *)lastPossibleRecurrenceStartDateUsingFirstInstanceCalendarDateRange:(NGCalendarDateRange *)_r
+{
+  NSCalendarDate *date;
+  unsigned       i, count;
+  
+  count = [self->rRules count];
+  if (!count)
+    return nil;
+
+  date  = nil;
+  for (i = 0; i < count; i++) {
+    iCalRecurrenceRule       *rule;
+    iCalRecurrenceCalculator *calc;
+    NSCalendarDate           *rdate;
+
+    rule = [self->rRules objectAtIndex:i];
+    if ([rule isInfinite])
+      return nil; /* rule is not bound, hence no limit */
+    calc  = [iCalRecurrenceCalculator recurrenceCalculatorForRecurrenceRule:rule
+                                      withFirstInstanceCalendarDateRange:_r];
+    rdate = [[calc lastInstanceCalendarDateRange] startDate];
+    if (date == nil || [date compare:rdate] == NSOrderedAscending)
+      date = rdate;
+  }
+  return date;
+}
+
 @end
index a690503279ea6da106c72694e93403e6d1c01786..eb6719b4352ede87686c1193ebf99d2a68bea0ac 100644 (file)
@@ -29,6 +29,7 @@
   NSTimeZone          *gmt;
   NGCalendarDateRange *fir;
   NGCalendarDateRange *tr1;
+  NGCalendarDateRange *dr1;
 }
 
 - (iCalRecurrenceRule *)ruleWithICalString:(NSString *)_rule;
   self->tr1 = [[NGCalendarDateRange calendarDateRangeWithStartDate:sd
                                     endDate:ed] retain];
   
+
+  sd = [NSCalendarDate dateWithYear:2005
+                       month:2
+                       day:6
+                       hour:0
+                       minute:0
+                       second:0
+                       timeZone:self->gmt];
+  ed = [NSCalendarDate dateWithYear:2005
+                       month:2
+                       day:13
+                       hour:23
+                       minute:59
+                       second:59
+                       timeZone:self->gmt];
+  
+  self->dr1 = [[NGCalendarDateRange calendarDateRangeWithStartDate:sd
+                                    endDate:ed] retain];
 }
 
 - (void)tearDown {
   [self->gmt release];
   [self->fir release];
   [self->tr1 release];
+  [self->dr1 release];
 }
 
 /* Private Helper */
   iCalRecurrenceRule       *rule;
   iCalRecurrenceCalculator *calc;
   BOOL                     result;
-  
+  NSArray                  *ranges;
+
   /* recurrence outside of range */
   rule = [self ruleWithICalString:@"FREQ=DAILY;INTERVAL=2;COUNT=2"];
   calc = [iCalRecurrenceCalculator recurrenceCalculatorForRecurrenceRule:rule
-                                      withFirstInstanceCalendarDateRange:fir];
+                                   withFirstInstanceCalendarDateRange:fir];
   result = [calc doesRecurrWithinCalendarDateRange:self->tr1];
   STAssertFalse(result, @"recurrence!");
   
   /* recurrence within range */
   rule = [self ruleWithICalString:@"FREQ=DAILY;INTERVAL=2;UNTIL=20050212T120000Z"];
   calc = [iCalRecurrenceCalculator recurrenceCalculatorForRecurrenceRule:rule
-                                      withFirstInstanceCalendarDateRange:fir];
+                                   withFirstInstanceCalendarDateRange:fir];
   result = [calc doesRecurrWithinCalendarDateRange:self->tr1];
   STAssertTrue(result, @"didn't spot expected recurrence!");
-  
+  ranges = [calc recurrenceRangesWithinCalendarDateRange:self->tr1];
+  STAssertTrue([ranges count] == 1, @"didn't spot expected recurrence!");
+  ranges = [calc recurrenceRangesWithinCalendarDateRange:self->dr1];
+  STAssertTrue([ranges count] == 4, @"didn't spot expected recurrence!");
 }