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 return ([self->startDate earlierDate:date] == self->startDate &&
121 [self->endDate laterDate:date] == self->endDate) ? YES : NO;
126 - (BOOL)isEqual:(id)other {
132 if ([other isKindOfClass:self->isa] == NO)
135 return ([self->startDate isEqual:[other startDate]] &&
136 [self->endDate isEqual:[other endDate]]) ? YES : NO;
140 return [self->startDate hash] ^ [self->endDate hash];
143 - (NSComparisonResult)compare:(NGCalendarDateRange *)other {
144 return [self->startDate compare:[other startDate]];
149 - (NSString *)description {
150 NSMutableString *description;
152 description = [NSMutableString stringWithCapacity:64];
154 [description appendFormat:@"<%@[0x%x]: startDate:%@ endDate: ",
155 NSStringFromClass(self->isa), self, self->startDate];
157 if ([self->startDate isEqual:self->endDate])
158 [description appendString:@"== startDate"];
160 [description appendFormat:@"%@", self->endDate];
161 [description appendString:@">"];
165 @end /* NGCalendarDateRange */
167 @implementation NSArray(NGCalendarDateRanges)
169 - (NSArray *)arrayByCreatingDateRangesFromObjectsWithStartDateKey:(NSString *)s
170 andEndDateKey:(NSString *)e
175 count = [self count];
176 ma = [NSMutableArray arrayWithCapacity:count];
177 for (i = 0; i < count; i++) {
178 NGCalendarDateRange *daterange;
179 NSCalendarDate *start, *end;
182 object = [self objectAtIndex:i];
183 start = [object valueForKey:s];
184 end = [object valueForKey:e];
186 /* skip invalid data */
187 if (![start isNotNull]) continue;
188 if (![end isNotNull]) continue;
191 [[NGCalendarDateRange alloc] initWithStartDate:start endDate:end];
192 if (daterange) [ma addObject:daterange];
198 - (BOOL)dateRangeArrayContainsDate:(NSCalendarDate *)_date {
203 if ((count = [self count]) == 0)
206 for (i = 0; i < count; i++) {
207 if ([[self objectAtIndex:i] containsDate:_date])
212 - (unsigned)indexOfFirstIntersectingDateRange:(NGCalendarDateRange *)_range {
218 if ((count = [self count]) == 0)
221 for (i = 0; i < count; i++) {
222 if ([[self objectAtIndex:i] doesIntersectWithDateRange:_range])
228 - (NSArray *)arrayByCompactingContainedDateRanges {
229 // TODO: this is a candidate for unit testing ...
230 // TODO: pretty "slow" algorithm, improve
234 count = [self count];
236 return [[self copy] autorelease];
238 ma = [NSMutableArray arrayWithCapacity:count];
239 [ma addObject:[self objectAtIndex:0]]; /* add first range */
241 for (i = 1; i < count; i++) {
242 NGCalendarDateRange *rangeToAdd;
243 NGCalendarDateRange *availRange;
244 NGCalendarDateRange *newRange;
247 rangeToAdd = [self objectAtIndex:i];
248 idx = [ma indexOfFirstIntersectingDateRange:rangeToAdd];
250 if (idx == NSNotFound) {
251 /* range not yet covered in array */
252 [ma addObject:rangeToAdd];
256 /* union old range and replace the entry */
258 availRange = [ma objectAtIndex:idx];
259 newRange = [availRange unionDateRange:rangeToAdd];
261 [ma replaceObjectAtIndex:idx withObject:newRange];
263 /* Note: we might want to join ranges up to some "closeness" (eg 1s)? */
264 return [ma sortedArrayUsingSelector:@selector(compare:)];
267 @end /* NSArray(NGCalendarDateRanges) */