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];
85 if ([b compare:c] == NSOrderedAscending)
86 return nil; // no intersection
88 if ([b compare:d] == NSOrderedAscending) {
89 // c !< b && b !< d -> [c;b[
90 if([c compare:b] == NSOrderedSame)
91 return nil; // no real range, thus return nil!
93 return [NGCalendarDateRange calendarDateRangeWithStartDate:c endDate:b];
95 if([c compare:d] == NSOrderedSame)
96 return nil; // no real range, thus return nil!
98 return [NGCalendarDateRange calendarDateRangeWithStartDate:c endDate:d];
101 - (BOOL)doesIntersectWithDateRange:(NGCalendarDateRange *)_other {
103 if (_other == nil) return NO;
104 return [self intersectionDateRange:_other] != nil ? YES : NO;
107 - (NGCalendarDateRange *)unionDateRange:(NGCalendarDateRange *)other {
108 NSCalendarDate *a, *b, *c, *d;
110 if ([self compare:other] == NSOrderedAscending) {
113 c = [other startDate];
117 a = [other startDate];
122 if ([b compare:d] == NSOrderedAscending)
123 return [NGCalendarDateRange calendarDateRangeWithStartDate:a endDate:d];
125 return [NGCalendarDateRange calendarDateRangeWithStartDate:a endDate:b];
128 - (BOOL)containsDate:(NSCalendarDate *)date {
129 return ([self->startDate earlierDate:date] == self->startDate &&
130 [self->endDate compare:date] == NSOrderedDescending) ? YES : NO;
135 - (BOOL)isEqual:(id)other {
141 if ([other isKindOfClass:self->isa] == NO)
144 return ([self->startDate isEqual:[other startDate]] &&
145 [self->endDate isEqual:[other endDate]]) ? YES : NO;
149 return [self->startDate hash] ^ [self->endDate hash];
152 - (NSComparisonResult)compare:(NGCalendarDateRange *)other {
153 return [self->startDate compare:[other startDate]];
158 - (NSString *)description {
159 NSMutableString *description;
161 description = [NSMutableString stringWithCapacity:64];
163 [description appendFormat:@"<%@[0x%x]: startDate:%@ endDate: ",
164 NSStringFromClass(self->isa), self, self->startDate];
166 if ([self->startDate isEqual:self->endDate])
167 [description appendString:@"== startDate"];
169 [description appendFormat:@"%@", self->endDate];
170 [description appendString:@">"];
174 @end /* NGCalendarDateRange */
176 @implementation NSArray(NGCalendarDateRanges)
178 - (NSArray *)arrayByCreatingDateRangesFromObjectsWithStartDateKey:(NSString *)s
179 andEndDateKey:(NSString *)e
184 count = [self count];
185 ma = [NSMutableArray arrayWithCapacity:count];
186 for (i = 0; i < count; i++) {
187 NGCalendarDateRange *daterange;
188 NSCalendarDate *start, *end;
191 object = [self objectAtIndex:i];
192 start = [object valueForKey:s];
193 end = [object valueForKey:e];
195 /* skip invalid data */
196 if (![start isNotNull]) continue;
197 if (![end isNotNull]) continue;
200 [[NGCalendarDateRange alloc] initWithStartDate:start endDate:end];
201 if (daterange) [ma addObject:daterange];
207 - (BOOL)dateRangeArrayContainsDate:(NSCalendarDate *)_date {
212 if ((count = [self count]) == 0)
215 for (i = 0; i < count; i++) {
216 if ([[self objectAtIndex:i] containsDate:_date])
221 - (unsigned)indexOfFirstIntersectingDateRange:(NGCalendarDateRange *)_range {
227 if ((count = [self count]) == 0)
230 for (i = 0; i < count; i++) {
231 if ([[self objectAtIndex:i] doesIntersectWithDateRange:_range])
237 - (NSArray *)arrayByCompactingContainedDateRanges {
238 // TODO: this is a candidate for unit testing ...
239 // TODO: pretty "slow" algorithm, improve
243 count = [self count];
245 return [[self copy] autorelease];
247 ma = [NSMutableArray arrayWithCapacity:count];
248 [ma addObject:[self objectAtIndex:0]]; /* add first range */
250 for (i = 1; i < count; i++) {
251 NGCalendarDateRange *rangeToAdd;
252 NGCalendarDateRange *availRange;
253 NGCalendarDateRange *newRange;
256 rangeToAdd = [self objectAtIndex:i];
257 idx = [ma indexOfFirstIntersectingDateRange:rangeToAdd];
259 if (idx == NSNotFound) {
260 /* range not yet covered in array */
261 [ma addObject:rangeToAdd];
265 /* union old range and replace the entry */
267 availRange = [ma objectAtIndex:idx];
268 newRange = [availRange unionDateRange:rangeToAdd];
270 [ma replaceObjectAtIndex:idx withObject:newRange];
272 /* Note: we might want to join ranges up to some "closeness" (eg 1s)? */
273 return [ma sortedArrayUsingSelector:@selector(compare:)];
276 @end /* NSArray(NGCalendarDateRanges) */