]> err.no Git - sope/blob - sope-core/NGExtensions/NGCalendarDateRange.m
fix to NGCalendarDateRange
[sope] / sope-core / NGExtensions / NGCalendarDateRange.m
1 /*
2   Copyright (C) 2004 Marcus Mueller
3
4   This file is part of OpenGroupware.org.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21
22 #include "NGCalendarDateRange.h"
23 #include <NGExtensions/NSCalendarDate+misc.h>
24 #include <NGExtensions/NSNull+misc.h>
25 #include "common.h"
26
27 @implementation NGCalendarDateRange
28
29 + (id)calendarDateRangeWithStartDate:(NSCalendarDate *)start
30   endDate:(NSCalendarDate *)end
31 {
32     return [[[self alloc] initWithStartDate:start endDate:end] autorelease];
33 }
34
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!");
38   
39   if ((self = [super init])) {
40     if ([start compare:end] == NSOrderedAscending) {
41       self->startDate = [start copy];
42       self->endDate   = [end   copy];
43     }
44     else {
45       self->startDate = [end   copy];
46       self->endDate   = [start copy];
47     }
48   }
49   return self;
50 }
51
52 /* NSCopying */
53
54 - (id)copyWithZone:(NSZone *)zone {
55   /* object is immutable */
56   return [self retain];
57 }
58
59 /* accessors */
60
61 - (NSCalendarDate *)startDate {
62   return self->startDate;
63 }
64
65 - (NSCalendarDate *)endDate {
66   return self->endDate;
67 }
68
69 - (NGCalendarDateRange *)intersectionDateRange:(NGCalendarDateRange *)other {
70   NSCalendarDate *a, *b, *c, *d;
71     
72   if ([self compare:other] == NSOrderedAscending) {
73     a = self->startDate;
74     b = self->endDate;
75     c = [other startDate];
76     d = [other endDate];
77   }
78   else {
79     a = [other startDate];
80     b = [other endDate];
81     c = self->startDate;
82     d = self->endDate;
83   }
84   if ([b compare:c] == NSOrderedAscending)
85     return nil; // no intersection
86   if ([b compare:d] == NSOrderedAscending)
87     return [NGCalendarDateRange calendarDateRangeWithStartDate:c endDate:b];
88   
89   return [NGCalendarDateRange calendarDateRangeWithStartDate:c endDate:d];
90 }
91
92 - (BOOL)doesIntersectWithDateRange:(NGCalendarDateRange *)_other {
93   // TODO: improve
94   if (_other == nil) return NO;
95   return [self intersectionDateRange:_other] != nil ? YES : NO;
96 }
97
98 - (NGCalendarDateRange *)unionDateRange:(NGCalendarDateRange *)other {
99   NSCalendarDate *a, *b, *c, *d;
100     
101   if ([self compare:other] == NSOrderedAscending) {
102     a = self->startDate;
103     b = self->endDate;
104     c = [other startDate];
105     d = [other endDate];
106   }
107   else {
108     a = [other startDate];
109     b = [other endDate];
110     c = self->startDate;
111     d = self->endDate;
112   }
113   if ([b compare:d] == NSOrderedAscending)
114     return [NGCalendarDateRange calendarDateRangeWithStartDate:a endDate:d];
115   
116   return [NGCalendarDateRange calendarDateRangeWithStartDate:a endDate:b];
117 }
118
119 - (BOOL)containsDate:(NSCalendarDate *)date {
120   if([self->endDate isEqualToDate:date])
121       return NO;
122   return ([self->startDate earlierDate:date] == self->startDate && 
123           [self->endDate laterDate:date] == self->endDate) ? YES : NO;
124 }
125
126 /* comparison */
127
128 - (BOOL)isEqual:(id)other {
129   if (other == nil)
130     return NO;
131   if (other == self)
132     return YES;
133   
134   if ([other isKindOfClass:self->isa] == NO)
135     return NO;
136   
137   return ([self->startDate isEqual:[other startDate]] && 
138           [self->endDate isEqual:[other endDate]]) ? YES : NO;
139 }
140
141 - (unsigned)hash {
142   return [self->startDate hash] ^ [self->endDate hash];
143 }
144
145 - (NSComparisonResult)compare:(NGCalendarDateRange *)other {
146   return [self->startDate compare:[other startDate]];
147 }
148
149 /* description */
150
151 - (NSString *)description {
152   NSMutableString *description;
153     
154   description = [NSMutableString stringWithCapacity:64];
155
156   [description appendFormat:@"<%@[0x%x]: startDate:%@ endDate: ", 
157                  NSStringFromClass(self->isa), self, self->startDate];
158   
159   if ([self->startDate isEqual:self->endDate])
160     [description appendString:@"== startDate"];
161   else
162     [description appendFormat:@"%@", self->endDate];
163   [description appendString:@">"];
164   return description;
165 }
166
167 @end /* NGCalendarDateRange */
168
169 @implementation NSArray(NGCalendarDateRanges)
170
171 - (NSArray *)arrayByCreatingDateRangesFromObjectsWithStartDateKey:(NSString *)s
172   andEndDateKey:(NSString *)e
173 {
174   NSMutableArray *ma;
175   unsigned i, count;
176   
177   count = [self count];
178   ma    = [NSMutableArray arrayWithCapacity:count];
179   for (i = 0; i < count; i++) {
180     NGCalendarDateRange *daterange;
181     NSCalendarDate *start, *end;
182     id object;
183     
184     object = [self objectAtIndex:i];
185     start  = [object valueForKey:s];
186     end    = [object valueForKey:e];
187     
188     /* skip invalid data */
189     if (![start isNotNull]) continue;
190     if (![end   isNotNull]) continue;
191     
192     daterange =
193       [[NGCalendarDateRange alloc] initWithStartDate:start endDate:end];
194     if (daterange) [ma addObject:daterange];
195     [daterange release];
196   }
197   return ma;
198 }
199
200 - (BOOL)dateRangeArrayContainsDate:(NSCalendarDate *)_date {
201   unsigned i, count;
202   
203   if (_date == nil) 
204     return NO;
205   if ((count = [self count]) == 0)
206     return NO;
207
208   for (i = 0; i < count; i++) {
209     if ([[self objectAtIndex:i] containsDate:_date])
210       return YES;
211   }
212   return NO;
213 }
214 - (unsigned)indexOfFirstIntersectingDateRange:(NGCalendarDateRange *)_range {
215   unsigned i, count;
216   
217   if (_range == nil)
218     return NO;
219   
220   if ((count = [self count]) == 0)
221     return NSNotFound;
222
223   for (i = 0; i < count; i++) {
224     if ([[self objectAtIndex:i] doesIntersectWithDateRange:_range])
225       return i;
226   }
227   return NSNotFound;
228 }
229
230 - (NSArray *)arrayByCompactingContainedDateRanges {
231   // TODO: this is a candidate for unit testing ...
232   // TODO: pretty "slow" algorithm, improve
233   NSMutableArray *ma;
234   unsigned i, count;
235   
236   count = [self count];
237   if (count < 2)
238     return [[self copy] autorelease];
239   
240   ma = [NSMutableArray arrayWithCapacity:count];
241   [ma addObject:[self objectAtIndex:0]]; /* add first range */
242   
243   for (i = 1; i < count; i++) {
244     NGCalendarDateRange *rangeToAdd;
245     NGCalendarDateRange *availRange;
246     NGCalendarDateRange *newRange;
247     unsigned idx;
248     
249     rangeToAdd = [self objectAtIndex:i];
250     idx = [ma indexOfFirstIntersectingDateRange:rangeToAdd];
251     
252     if (idx == NSNotFound) {
253       /* range not yet covered in array */
254       [ma addObject:rangeToAdd];
255       continue;
256     }
257     
258     /* union old range and replace the entry */
259     
260     availRange = [ma objectAtIndex:idx];
261     newRange   = [availRange unionDateRange:rangeToAdd];
262     
263     [ma replaceObjectAtIndex:idx withObject:newRange];
264   }
265   /* Note: we might want to join ranges up to some "closeness" (eg 1s)? */
266   return [ma sortedArrayUsingSelector:@selector(compare:)];
267 }
268
269 @end /* NSArray(NGCalendarDateRanges) */