1/* 2 Copyright (C) 2004-2005 SKYRIX Software AG 3 Copyright (C) 2006-2012 Inverse inc. 4 5 This file is part of SOPE. 6 7 SOPE is free software; you can redistribute it and/or modify it under 8 the terms of the GNU Lesser General Public License as published by the 9 Free Software Foundation; either version 2, or (at your option) any 10 later version. 11 12 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY 13 WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 15 License for more details. 16 17 You should have received a copy of the GNU Lesser General Public 18 License along with SOPE; see the file COPYING. If not, write to the 19 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 20 02111-1307, USA. 21*/ 22 23#import <NGExtensions/NSCalendarDate+misc.h> 24#import <NGExtensions/NSObject+Logs.h> 25 26#import "iCalRecurrenceCalculator.h" 27 28@interface iCalWeeklyRecurrenceCalculator : iCalRecurrenceCalculator 29@end 30 31#import <NGExtensions/NGCalendarDateRange.h> 32#import "iCalByDayMask.h" 33#import "NSCalendarDate+ICal.h" 34 35@interface iCalRecurrenceCalculator (PrivateAPI) 36 37- (NSCalendarDate *) lastInstanceStartDate; 38 39- (unsigned) offsetFromSundayForJulianNumber: (long) _jn; 40- (unsigned) offsetFromSundayForWeekDay: (iCalWeekDay) _weekDay; 41- (unsigned) offsetFromSundayForCurrentWeekStart; 42 43- (iCalWeekDay) weekDayForJulianNumber: (long) _jn; 44 45@end 46 47@implementation iCalWeeklyRecurrenceCalculator 48 49/** 50 * TODO : Unsupported conditions for WEEKLY recurrences : 51 * 52 * BYYEAR 53 * BYYEARDAY 54 * BYWEEKNO 55 * BYMONTH 56 * BYMONTHDAY 57 * BYHOUR 58 * BYMINUTE 59 * WKST 60 * 61 * There's no GUI to defined such conditions, so there's no 62 * problem for now. 63 */ 64- (NSArray *) recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r 65{ 66 NSMutableArray *ranges; 67 NSCalendarDate *firStart, *startDate, *endDate, *currentStartDate, *currentEndDate; 68 long i, repeatCount, count; 69 unsigned interval; 70 iCalByDayMask *dayMask; 71 BOOL hasRepeatCount; 72 73 //[self logWithFormat: @"Recurrence rule is %@", rrule]; 74 75 firStart = [firstRange startDate]; 76 startDate = [_r startDate]; 77 endDate = [_r endDate]; 78 dayMask = nil; 79 repeatCount = 0; 80 hasRepeatCount = [rrule hasRepeatCount]; 81 82 if (!firstRange) 83 // Can happen when event/todo has a rrule with no dtstart 84 return nil; 85 86 if ([endDate compare: firStart] == NSOrderedAscending) 87 // Range ends before first occurrence 88 return nil; 89 90 interval = [rrule repeatInterval]; 91 92 if ([[rrule byDay] length]) 93 dayMask = [rrule byDayMask]; 94 95 // If rule is bound, check the bounds 96 if (![rrule isInfinite]) 97 { 98 NSCalendarDate *until, *lastDate; 99 100 lastDate = nil; 101 until = [rrule untilDate]; 102 if (until) 103 lastDate = until; 104 else 105 { 106 repeatCount = [rrule repeatCount]; 107 if (dayMask == nil) 108 // When there's no BYxxx mask, we can find the date of the last 109 // occurrence. 110 lastDate = [firStart dateByAddingYears: 0 months: 0 111 days: (interval 112 * (repeatCount - 1) * 7)]; 113 } 114 115 if (lastDate != nil) 116 { 117 if ([lastDate compare: startDate] == NSOrderedAscending) 118 // Range starts after last occurrence 119 return nil; 120 if ([lastDate compare: endDate] == NSOrderedAscending) 121 // Range ends after last occurence; adjust end date 122 endDate = [lastDate addTimeInterval: [firstRange duration]]; 123 } 124 } 125 126 currentStartDate = [firStart copy]; 127 [currentStartDate autorelease]; 128 ranges = [NSMutableArray array]; 129 count = 0; 130 131 if (dayMask == nil) 132 { 133 i = 0; 134 while ([currentStartDate compare: endDate] == NSOrderedAscending || 135 [currentStartDate compare: endDate] == NSOrderedSame) 136 { 137 currentEndDate = [currentStartDate addTimeInterval: [firstRange duration]]; 138 if ([startDate compare: currentEndDate] == NSOrderedAscending || 139 ([firstRange duration] == 0 && [startDate compare: currentEndDate] == NSOrderedSame)) 140 { 141 NGCalendarDateRange *r; 142 143 r = [NGCalendarDateRange calendarDateRangeWithStartDate: currentStartDate 144 endDate: currentEndDate]; 145 [ranges addObject: r]; 146 } 147 i++; 148 currentStartDate = [firStart dateByAddingYears: 0 149 months: 0 150 days: (interval * i * 7)]; 151 } 152 } 153 else 154 { 155 NGCalendarDateRange *r; 156 157 i = [currentStartDate dayOfWeek]; // Set the first day of the week as Sunday and ignore WKST 158 while ([currentStartDate compare: endDate] == NSOrderedAscending || 159 [currentStartDate compare: endDate] == NSOrderedSame) 160 { 161 BOOL isRecurrence = NO; 162 NSInteger week; 163 164 currentEndDate = [currentStartDate addTimeInterval: [firstRange duration]]; 165 if (hasRepeatCount || 166 [startDate compare: currentEndDate] == NSOrderedAscending || 167 ([startDate compare: currentEndDate] == NSOrderedSame && [firstRange duration] == 0)) 168 { 169 // If the rule count is defined, stop once the count is reached. 170 if ([currentStartDate compare: firStart] == NSOrderedSame) 171 { 172 // Always add the start date of the recurring event if within 173 // the lookup range. 174 isRecurrence = YES; 175 } 176 else 177 { 178 week = i / 7; 179 180 if ((week % interval) == 0 && 181 [dayMask occursOnDay: [currentStartDate dayOfWeek]]) 182 isRecurrence = YES; 183 } 184 185 if (isRecurrence) 186 { 187 count++; 188 if (repeatCount > 0 && count > repeatCount) 189 break; 190 r = [NGCalendarDateRange calendarDateRangeWithStartDate: currentStartDate 191 endDate: currentEndDate]; 192 if ([_r doesIntersectWithDateRange: r]) 193 { 194 [ranges addObject: r]; 195 // [self logWithFormat: @"Add range %@ - %@", [r startDate], [r endDate]]; 196 } 197 } 198 } 199 currentStartDate = [currentStartDate dateByAddingYears: 0 200 months: 0 201 days: 1]; 202 i++; 203 } 204 } 205 206 return ranges; 207} 208 209- (NSCalendarDate *) lastInstanceStartDate 210{ 211 NSCalendarDate *firStart, *lastInstanceStartDate; 212 NGCalendarDateRange *r; 213 NSArray *instances; 214 215 lastInstanceStartDate = nil; 216 if ([rrule repeatCount] > 0) 217 { 218 firStart = [firstRange startDate]; 219 r = [NGCalendarDateRange calendarDateRangeWithStartDate: firStart 220 endDate: [NSCalendarDate distantFuture]]; 221 instances = [self recurrenceRangesWithinCalendarDateRange: r]; 222 if ([instances count]) 223 lastInstanceStartDate = [(NGCalendarDateRange *)[instances lastObject] startDate]; 224 } 225 else 226 lastInstanceStartDate = [super lastInstanceStartDate]; 227 228 return lastInstanceStartDate; 229} 230 231@end /* iCalWeeklyRecurrenceCalculator */ 232