1/* 2 Copyright (C) 2004-2007 Marcus Mueller 3 Copyright (C) 2007 Helge Hess 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#include "NGCalendarDateRange.h" 24#include <NGExtensions/NSCalendarDate+misc.h> 25#include <NGExtensions/NSNull+misc.h> 26#include "common.h" 27 28@implementation NGCalendarDateRange 29 30+ (id)calendarDateRangeWithStartDate:(NSCalendarDate *)start 31 endDate:(NSCalendarDate *)end 32{ 33 return [[[self alloc] initWithStartDate:start endDate:end] autorelease]; 34} 35 36- (id)initWithStartDate:(NSCalendarDate *)start endDate:(NSCalendarDate *)end { 37 NSAssert(start != nil, @"startDate MUST NOT be nil!"); 38 NSAssert(end != nil, @"endDate MUST NOT be nil!"); 39 40 if ((self = [super init])) { 41 if ([start compare:end] == NSOrderedAscending) { 42 self->startDate = [start copy]; 43 self->endDate = [end copy]; 44 } 45 else { 46 self->startDate = [end copy]; 47 self->endDate = [start copy]; 48 } 49 } 50 return self; 51} 52 53- (void)dealloc { 54 [self->startDate release]; 55 [self->endDate release]; 56 [super dealloc]; 57} 58 59/* NSCopying */ 60 61- (id)copyWithZone:(NSZone *)zone { 62 /* object is immutable */ 63 return [self retain]; 64} 65 66/* accessors */ 67 68- (NSCalendarDate *)startDate { 69 return self->startDate; 70} 71 72- (NSCalendarDate *)endDate { 73 return self->endDate; 74} 75 76- (NGCalendarDateRange *)intersectionDateRange:(NGCalendarDateRange *)other { 77 NSCalendarDate *b, *c, *d; 78 79 if ([self compare:other] == NSOrderedAscending) { 80 b = self->endDate; 81 c = [other startDate]; 82 d = [other endDate]; 83 } 84 else { 85 b = [other endDate]; 86 c = self->startDate; 87 d = self->endDate; 88 } 89 // [a;b[ ?< [c;d[ 90 if ([b compare:c] == NSOrderedAscending) 91 return nil; // no intersection 92 // b ?< d 93 if ([b compare:d] == NSOrderedAscending) { 94 // c !< b && b !< d -> [c;b[ 95 if([c compare:b] == NSOrderedSame) 96 return nil; // no real range, thus return nil! 97 else 98 return [NGCalendarDateRange calendarDateRangeWithStartDate:c endDate:b]; 99 } 100 // if([c compare:d] == NSOrderedSame) 101 // return nil; // no real range, thus return nil! 102 // b !> d -> [c;d[ 103 return [NGCalendarDateRange calendarDateRangeWithStartDate:c endDate:d]; 104} 105 106- (BOOL)doesIntersectWithDateRange:(NGCalendarDateRange *)_other { 107 // TODO: improve 108 if (_other == nil) return NO; 109 return [self intersectionDateRange:_other] != nil ? YES : NO; 110} 111 112- (NGCalendarDateRange *)unionDateRange:(NGCalendarDateRange *)other { 113 NSCalendarDate *a, *b, *d; 114 115 if ([self compare:other] == NSOrderedAscending) { 116 a = self->startDate; 117 b = self->endDate; 118 d = [other endDate]; 119 } 120 else { 121 a = [other startDate]; 122 b = [other endDate]; 123 d = self->endDate; 124 } 125 if ([b compare:d] == NSOrderedAscending) 126 return [NGCalendarDateRange calendarDateRangeWithStartDate:a endDate:d]; 127 128 return [NGCalendarDateRange calendarDateRangeWithStartDate:a endDate:b]; 129} 130 131- (BOOL)containsDate:(NSCalendarDate *)_date { 132 NSComparisonResult result; 133 134 result = [self->startDate compare:_date]; 135 if (!((result == NSOrderedSame) || (result == NSOrderedAscending))) 136 return NO; 137 result = [self->endDate compare:_date]; 138 if (result == NSOrderedAscending) 139 return NO; 140 return YES; 141} 142 143- (BOOL)containsDateRange:(NGCalendarDateRange *)_range { 144 NSComparisonResult result; 145 146 result = [self->startDate compare:[_range startDate]]; 147 if (!((result == NSOrderedSame) || (result == NSOrderedAscending))) 148 return NO; 149 result = [self->endDate compare:[_range endDate]]; 150 if (result == NSOrderedAscending) 151 return NO; 152 return YES; 153} 154 155- (NSTimeInterval)duration { 156 return [self->endDate timeIntervalSinceDate:self->startDate]; 157} 158 159/* comparison */ 160 161- (BOOL)isEqual:(id)other { 162 if (other == nil) 163 return NO; 164 if (other == self) 165 return YES; 166 167 if ([other isKindOfClass: object_getClass(self)] == NO) 168 return NO; 169 170 return ([self->startDate isEqual:[other startDate]] && 171 [self->endDate isEqual:[other endDate]]) ? YES : NO; 172} 173 174- (NSUInteger)hash { 175 return [self->startDate hash] ^ [self->endDate hash]; 176} 177 178- (NSComparisonResult)compare:(NGCalendarDateRange *)other { 179 return [self->startDate compare:[other startDate]]; 180} 181 182/* KVC */ 183 184- (id)valueForUndefinedKey:(NSString *)_key { 185 /* eg this is used in OGo on 'dateId' to probe for event objects */ 186 return nil; 187} 188 189/* description */ 190 191- (NSString *)description { 192 NSMutableString *description; 193 194 description = [NSMutableString stringWithCapacity:64]; 195 196 [description appendFormat:@"<%@[0x%p]: startDate:%@ endDate: ", 197 NSStringFromClass(object_getClass(self)), self, self->startDate]; 198 199 if ([self->startDate isEqual:self->endDate]) 200 [description appendString:@"== startDate"]; 201 else 202 [description appendFormat:@"%@", self->endDate]; 203 [description appendString:@">"]; 204 return description; 205} 206 207@end /* NGCalendarDateRange */ 208 209 210@implementation NSArray(NGCalendarDateRanges) 211 212- (NSArray *)arrayByCreatingDateRangesFromObjectsWithStartDateKey:(NSString *)s 213 andEndDateKey:(NSString *)e 214{ 215 NSMutableArray *ma; 216 NSUInteger i, count; 217 218 count = [self count]; 219 ma = [NSMutableArray arrayWithCapacity:count]; 220 for (i = 0; i < count; i++) { 221 NGCalendarDateRange *daterange; 222 NSCalendarDate *start, *end; 223 id object; 224 225 object = [self objectAtIndex:i]; 226 start = [object valueForKey:s]; 227 end = [object valueForKey:e]; 228 229 /* skip invalid data */ 230 if (![start isNotNull]) continue; 231 if (![end isNotNull]) continue; 232 233 daterange = 234 [[NGCalendarDateRange alloc] initWithStartDate:start endDate:end]; 235 if (daterange) [ma addObject:daterange]; 236 [daterange release]; 237 } 238 return ma; 239} 240 241- (BOOL)dateRangeArrayContainsDate:(NSCalendarDate *)_date { 242 NSUInteger i, count; 243 244 if (_date == nil) 245 return NO; 246 if ((count = [self count]) == 0) 247 return NO; 248 249 for (i = 0; i < count; i++) { 250 if ([[self objectAtIndex:i] containsDate:_date]) 251 return YES; 252 } 253 return NO; 254} 255- (NSUInteger)indexOfFirstIntersectingDateRange:(NGCalendarDateRange *)_range { 256 NSUInteger i, count; 257 258 if (_range == nil) 259 return NO; 260 261 if ((count = [self count]) == 0) 262 return NSNotFound; 263 264 for (i = 0; i < count; i++) { 265 if ([[self objectAtIndex:i] doesIntersectWithDateRange:_range]) 266 return i; 267 } 268 return NSNotFound; 269} 270 271- (NSArray *)arrayByCompactingContainedDateRanges { 272 // TODO: this is a candidate for unit testing ... 273 // TODO: pretty "slow" algorithm, improve 274 NSMutableArray *ma; 275 NSUInteger i, count; 276 277 count = [self count]; 278 if (count < 2) 279 return [[self copy] autorelease]; 280 281 ma = [NSMutableArray arrayWithCapacity:count]; 282 [ma addObject:[self objectAtIndex:0]]; /* add first range */ 283 284 for (i = 1; i < count; i++) { 285 NGCalendarDateRange *rangeToAdd; 286 NGCalendarDateRange *availRange; 287 NGCalendarDateRange *newRange; 288 NSUInteger idx; 289 290 rangeToAdd = [self objectAtIndex:i]; 291 idx = [ma indexOfFirstIntersectingDateRange:rangeToAdd]; 292 293 if (idx == NSNotFound) { 294 /* range not yet covered in array */ 295 [ma addObject:rangeToAdd]; 296 continue; 297 } 298 299 /* union old range and replace the entry */ 300 301 availRange = [ma objectAtIndex:idx]; 302 newRange = [availRange unionDateRange:rangeToAdd]; 303 304 [ma replaceObjectAtIndex:idx withObject:newRange]; 305 } 306 /* Note: we might want to join ranges up to some "closeness" (eg 1s)? */ 307 return [ma sortedArrayUsingSelector:@selector(compare:)]; 308} 309 310@end /* NSArray(NGCalendarDateRanges) */ 311