2 Copyright (C) 2004-2007 Marcus Mueller
3 Copyright (C) 2007 Helge Hess
5 This file is part of SOPE.
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
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.
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
23 #include "NGCalendarDateRange.h"
24 #include <NGExtensions/NSCalendarDate+misc.h>
25 #include <NGExtensions/NSNull+misc.h>
28 @implementation NGCalendarDateRange
30 + (id)calendarDateRangeWithStartDate:(NSCalendarDate *)start
31 endDate:(NSCalendarDate *)end
33 return [[[self alloc] initWithStartDate:start endDate:end] autorelease];
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!");
40 if ((self = [super init])) {
41 if ([start compare:end] == NSOrderedAscending) {
42 self->startDate = [start copy];
43 self->endDate = [end copy];
46 self->startDate = [end copy];
47 self->endDate = [start copy];
55 - (id)copyWithZone:(NSZone *)zone {
56 /* object is immutable */
62 - (NSCalendarDate *)startDate {
63 return self->startDate;
66 - (NSCalendarDate *)endDate {
70 - (NGCalendarDateRange *)intersectionDateRange:(NGCalendarDateRange *)other {
71 NSCalendarDate *a, *b, *c, *d;
73 if ([self compare:other] == NSOrderedAscending) {
76 c = [other startDate];
80 a = [other startDate];
86 if ([b compare:c] == NSOrderedAscending)
87 return nil; // no intersection
89 if ([b compare:d] == NSOrderedAscending) {
90 // c !< b && b !< d -> [c;b[
91 if([c compare:b] == NSOrderedSame)
92 return nil; // no real range, thus return nil!
94 return [NGCalendarDateRange calendarDateRangeWithStartDate:c endDate:b];
96 if([c compare:d] == NSOrderedSame)
97 return nil; // no real range, thus return nil!
99 return [NGCalendarDateRange calendarDateRangeWithStartDate:c endDate:d];
102 - (BOOL)doesIntersectWithDateRange:(NGCalendarDateRange *)_other {
104 if (_other == nil) return NO;
105 return [self intersectionDateRange:_other] != nil ? YES : NO;
108 - (NGCalendarDateRange *)unionDateRange:(NGCalendarDateRange *)other {
109 NSCalendarDate *a, *b, *c, *d;
111 if ([self compare:other] == NSOrderedAscending) {
114 c = [other startDate];
118 a = [other startDate];
123 if ([b compare:d] == NSOrderedAscending)
124 return [NGCalendarDateRange calendarDateRangeWithStartDate:a endDate:d];
126 return [NGCalendarDateRange calendarDateRangeWithStartDate:a endDate:b];
129 - (BOOL)containsDate:(NSCalendarDate *)_date {
130 NSComparisonResult result;
132 result = [self->startDate compare:_date];
133 if (!((result == NSOrderedSame) || (result == NSOrderedAscending)))
135 result = [self->endDate compare:_date];
136 if (result == NSOrderedAscending)
141 - (BOOL)containsDateRange:(NGCalendarDateRange *)_range {
142 NSComparisonResult result;
144 result = [self->startDate compare:[_range startDate]];
145 if (!((result == NSOrderedSame) || (result == NSOrderedAscending)))
147 result = [self->endDate compare:[_range endDate]];
148 if (result == NSOrderedAscending)
153 - (NSTimeInterval)duration {
154 return [self->endDate timeIntervalSinceDate:self->startDate];
159 - (BOOL)isEqual:(id)other {
165 if ([other isKindOfClass:self->isa] == NO)
168 return ([self->startDate isEqual:[other startDate]] &&
169 [self->endDate isEqual:[other endDate]]) ? YES : NO;
173 return [self->startDate hash] ^ [self->endDate hash];
176 - (NSComparisonResult)compare:(NGCalendarDateRange *)other {
177 return [self->startDate compare:[other startDate]];
182 - (id)valueForUndefinedKey:(NSString *)_key {
183 /* eg this is used in OGo on 'dateId' to probe for event objects */
189 - (NSString *)description {
190 NSMutableString *description;
192 description = [NSMutableString stringWithCapacity:64];
194 [description appendFormat:@"<%@[0x%x]: startDate:%@ endDate: ",
195 NSStringFromClass(self->isa), self, self->startDate];
197 if ([self->startDate isEqual:self->endDate])
198 [description appendString:@"== startDate"];
200 [description appendFormat:@"%@", self->endDate];
201 [description appendString:@">"];
205 @end /* NGCalendarDateRange */
208 @implementation NSArray(NGCalendarDateRanges)
210 - (NSArray *)arrayByCreatingDateRangesFromObjectsWithStartDateKey:(NSString *)s
211 andEndDateKey:(NSString *)e
216 count = [self count];
217 ma = [NSMutableArray arrayWithCapacity:count];
218 for (i = 0; i < count; i++) {
219 NGCalendarDateRange *daterange;
220 NSCalendarDate *start, *end;
223 object = [self objectAtIndex:i];
224 start = [object valueForKey:s];
225 end = [object valueForKey:e];
227 /* skip invalid data */
228 if (![start isNotNull]) continue;
229 if (![end isNotNull]) continue;
232 [[NGCalendarDateRange alloc] initWithStartDate:start endDate:end];
233 if (daterange) [ma addObject:daterange];
239 - (BOOL)dateRangeArrayContainsDate:(NSCalendarDate *)_date {
244 if ((count = [self count]) == 0)
247 for (i = 0; i < count; i++) {
248 if ([[self objectAtIndex:i] containsDate:_date])
253 - (unsigned)indexOfFirstIntersectingDateRange:(NGCalendarDateRange *)_range {
259 if ((count = [self count]) == 0)
262 for (i = 0; i < count; i++) {
263 if ([[self objectAtIndex:i] doesIntersectWithDateRange:_range])
269 - (NSArray *)arrayByCompactingContainedDateRanges {
270 // TODO: this is a candidate for unit testing ...
271 // TODO: pretty "slow" algorithm, improve
275 count = [self count];
277 return [[self copy] autorelease];
279 ma = [NSMutableArray arrayWithCapacity:count];
280 [ma addObject:[self objectAtIndex:0]]; /* add first range */
282 for (i = 1; i < count; i++) {
283 NGCalendarDateRange *rangeToAdd;
284 NGCalendarDateRange *availRange;
285 NGCalendarDateRange *newRange;
288 rangeToAdd = [self objectAtIndex:i];
289 idx = [ma indexOfFirstIntersectingDateRange:rangeToAdd];
291 if (idx == NSNotFound) {
292 /* range not yet covered in array */
293 [ma addObject:rangeToAdd];
297 /* union old range and replace the entry */
299 availRange = [ma objectAtIndex:idx];
300 newRange = [availRange unionDateRange:rangeToAdd];
302 [ma replaceObjectAtIndex:idx withObject:newRange];
304 /* Note: we might want to join ranges up to some "closeness" (eg 1s)? */
305 return [ma sortedArrayUsingSelector:@selector(compare:)];
308 @end /* NSArray(NGCalendarDateRanges) */