2 Copyright (C) 2004 Marcus Mueller
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include "NGCalendarDateRange.h"
23 #include <NGExtensions/NSCalendarDate+misc.h>
24 #include <NGExtensions/NSNull+misc.h>
27 @implementation NGCalendarDateRange
29 + (id)calendarDateRangeWithStartDate:(NSCalendarDate *)start
30 endDate:(NSCalendarDate *)end
32 return [[[self alloc] initWithStartDate:start endDate:end] autorelease];
35 - (id)initWithStartDate:(NSCalendarDate *)start endDate:(NSCalendarDate *)end {
36 NSAssert(start != nil, @"startDate MUST NOT be nil!");
37 NSAssert(end != nil, @"endDate MUST NOT be nil!");
39 if ((self = [super init])) {
40 if ([start compare:end] == NSOrderedAscending) {
41 self->startDate = [start copy];
42 self->endDate = [end copy];
45 self->startDate = [end copy];
46 self->endDate = [start copy];
54 - (id)copyWithZone:(NSZone *)zone {
55 /* object is immutable */
61 - (NSCalendarDate *)startDate {
62 return self->startDate;
65 - (NSCalendarDate *)endDate {
69 - (NGCalendarDateRange *)intersectionDateRange:(NGCalendarDateRange *)other {
70 NSCalendarDate *a, *b, *c, *d;
72 if ([self compare:other] == NSOrderedAscending) {
75 c = [other startDate];
79 a = [other startDate];
84 if ([b compare:c] == NSOrderedAscending)
85 return nil; // no intersection
86 if ([b compare:d] == NSOrderedAscending)
87 return [NGCalendarDateRange calendarDateRangeWithStartDate:c endDate:b];
89 return [NGCalendarDateRange calendarDateRangeWithStartDate:c endDate:d];
92 - (BOOL)doesIntersectWithDateRange:(NGCalendarDateRange *)_other {
94 if (_other == nil) return NO;
95 return [self intersectionDateRange:_other] != nil ? YES : NO;
98 - (NGCalendarDateRange *)unionDateRange:(NGCalendarDateRange *)other {
99 NSCalendarDate *a, *b, *c, *d;
101 if ([self compare:other] == NSOrderedAscending) {
104 c = [other startDate];
108 a = [other startDate];
113 if ([b compare:d] == NSOrderedAscending)
114 return [NGCalendarDateRange calendarDateRangeWithStartDate:a endDate:d];
116 return [NGCalendarDateRange calendarDateRangeWithStartDate:a endDate:b];
119 - (BOOL)containsDate:(NSCalendarDate *)date {
120 if([self->endDate isEqualToDate:date])
122 return ([self->startDate earlierDate:date] == self->startDate &&
123 [self->endDate laterDate:date] == self->endDate) ? YES : NO;
128 - (BOOL)isEqual:(id)other {
134 if ([other isKindOfClass:self->isa] == NO)
137 return ([self->startDate isEqual:[other startDate]] &&
138 [self->endDate isEqual:[other endDate]]) ? YES : NO;
142 return [self->startDate hash] ^ [self->endDate hash];
145 - (NSComparisonResult)compare:(NGCalendarDateRange *)other {
146 return [self->startDate compare:[other startDate]];
151 - (NSString *)description {
152 NSMutableString *description;
154 description = [NSMutableString stringWithCapacity:64];
156 [description appendFormat:@"<%@[0x%x]: startDate:%@ endDate: ",
157 NSStringFromClass(self->isa), self, self->startDate];
159 if ([self->startDate isEqual:self->endDate])
160 [description appendString:@"== startDate"];
162 [description appendFormat:@"%@", self->endDate];
163 [description appendString:@">"];
167 @end /* NGCalendarDateRange */
169 @implementation NSArray(NGCalendarDateRanges)
171 - (NSArray *)arrayByCreatingDateRangesFromObjectsWithStartDateKey:(NSString *)s
172 andEndDateKey:(NSString *)e
177 count = [self count];
178 ma = [NSMutableArray arrayWithCapacity:count];
179 for (i = 0; i < count; i++) {
180 NGCalendarDateRange *daterange;
181 NSCalendarDate *start, *end;
184 object = [self objectAtIndex:i];
185 start = [object valueForKey:s];
186 end = [object valueForKey:e];
188 /* skip invalid data */
189 if (![start isNotNull]) continue;
190 if (![end isNotNull]) continue;
193 [[NGCalendarDateRange alloc] initWithStartDate:start endDate:end];
194 if (daterange) [ma addObject:daterange];
200 - (BOOL)dateRangeArrayContainsDate:(NSCalendarDate *)_date {
205 if ((count = [self count]) == 0)
208 for (i = 0; i < count; i++) {
209 if ([[self objectAtIndex:i] containsDate:_date])
214 - (unsigned)indexOfFirstIntersectingDateRange:(NGCalendarDateRange *)_range {
220 if ((count = [self count]) == 0)
223 for (i = 0; i < count; i++) {
224 if ([[self objectAtIndex:i] doesIntersectWithDateRange:_range])
230 - (NSArray *)arrayByCompactingContainedDateRanges {
231 // TODO: this is a candidate for unit testing ...
232 // TODO: pretty "slow" algorithm, improve
236 count = [self count];
238 return [[self copy] autorelease];
240 ma = [NSMutableArray arrayWithCapacity:count];
241 [ma addObject:[self objectAtIndex:0]]; /* add first range */
243 for (i = 1; i < count; i++) {
244 NGCalendarDateRange *rangeToAdd;
245 NGCalendarDateRange *availRange;
246 NGCalendarDateRange *newRange;
249 rangeToAdd = [self objectAtIndex:i];
250 idx = [ma indexOfFirstIntersectingDateRange:rangeToAdd];
252 if (idx == NSNotFound) {
253 /* range not yet covered in array */
254 [ma addObject:rangeToAdd];
258 /* union old range and replace the entry */
260 availRange = [ma objectAtIndex:idx];
261 newRange = [availRange unionDateRange:rangeToAdd];
263 [ma replaceObjectAtIndex:idx withObject:newRange];
265 /* Note: we might want to join ranges up to some "closeness" (eg 1s)? */
266 return [ma sortedArrayUsingSelector:@selector(compare:)];
269 @end /* NSArray(NGCalendarDateRanges) */