]> err.no Git - sope/blob - sope-core/NGExtensions/NGCalendarDateRange.m
3e491fa70308539ca69bbb296716eee1d3b5722f
[sope] / sope-core / NGExtensions / NGCalendarDateRange.m
1 /*
2   Copyright (C) 2004 Marcus Mueller
3
4   This file is part of SOPE.
5
6   SOPE 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   SOPE 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 SOPE; 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   // [a;b[ ?< [c;d[
85   if ([b compare:c] == NSOrderedAscending)
86     return nil; // no intersection
87   // b ?< d
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!
92     else
93       return [NGCalendarDateRange calendarDateRangeWithStartDate:c endDate:b];
94   }
95   if([c compare:d] == NSOrderedSame)
96     return nil; // no real range, thus return nil!
97   // b !> d -> [c;d[
98   return [NGCalendarDateRange calendarDateRangeWithStartDate:c endDate:d];
99 }
100
101 - (BOOL)doesIntersectWithDateRange:(NGCalendarDateRange *)_other {
102   // TODO: improve
103   if (_other == nil) return NO;
104   return [self intersectionDateRange:_other] != nil ? YES : NO;
105 }
106
107 - (NGCalendarDateRange *)unionDateRange:(NGCalendarDateRange *)other {
108   NSCalendarDate *a, *b, *c, *d;
109     
110   if ([self compare:other] == NSOrderedAscending) {
111     a = self->startDate;
112     b = self->endDate;
113     c = [other startDate];
114     d = [other endDate];
115   }
116   else {
117     a = [other startDate];
118     b = [other endDate];
119     c = self->startDate;
120     d = self->endDate;
121   }
122   if ([b compare:d] == NSOrderedAscending)
123     return [NGCalendarDateRange calendarDateRangeWithStartDate:a endDate:d];
124   
125   return [NGCalendarDateRange calendarDateRangeWithStartDate:a endDate:b];
126 }
127
128 - (BOOL)containsDate:(NSCalendarDate *)_date {
129   NSComparisonResult result;
130   
131   result = [self->startDate compare:_date];
132   if (!((result == NSOrderedSame) || (result == NSOrderedAscending)))
133     return NO;
134   result = [self->endDate compare:_date];
135   if (result == NSOrderedAscending)
136     return NO;
137   return YES;
138 }
139
140 - (BOOL)containsDateRange:(NGCalendarDateRange *)_range {
141   NSComparisonResult result;
142
143   result = [self->startDate compare:[_range startDate]];
144   if (!((result == NSOrderedSame) || (result == NSOrderedAscending)))
145     return NO;
146   result = [self->endDate compare:[_range endDate]];
147   if (result == NSOrderedAscending)
148     return NO;
149   return YES;
150 }
151
152 - (NSTimeInterval)duration {
153   return [self->endDate timeIntervalSinceDate:self->startDate];
154 }
155
156 /* comparison */
157
158 - (BOOL)isEqual:(id)other {
159   if (other == nil)
160     return NO;
161   if (other == self)
162     return YES;
163   
164   if ([other isKindOfClass:self->isa] == NO)
165     return NO;
166   
167   return ([self->startDate isEqual:[other startDate]] && 
168           [self->endDate isEqual:[other endDate]]) ? YES : NO;
169 }
170
171 - (unsigned)hash {
172   return [self->startDate hash] ^ [self->endDate hash];
173 }
174
175 - (NSComparisonResult)compare:(NGCalendarDateRange *)other {
176   return [self->startDate compare:[other startDate]];
177 }
178
179 /* description */
180
181 - (NSString *)description {
182   NSMutableString *description;
183     
184   description = [NSMutableString stringWithCapacity:64];
185
186   [description appendFormat:@"<%@[0x%x]: startDate:%@ endDate: ", 
187                  NSStringFromClass(self->isa), self, self->startDate];
188   
189   if ([self->startDate isEqual:self->endDate])
190     [description appendString:@"== startDate"];
191   else
192     [description appendFormat:@"%@", self->endDate];
193   [description appendString:@">"];
194   return description;
195 }
196
197 @end /* NGCalendarDateRange */
198
199
200 @implementation NSArray(NGCalendarDateRanges)
201
202 - (NSArray *)arrayByCreatingDateRangesFromObjectsWithStartDateKey:(NSString *)s
203   andEndDateKey:(NSString *)e
204 {
205   NSMutableArray *ma;
206   unsigned i, count;
207   
208   count = [self count];
209   ma    = [NSMutableArray arrayWithCapacity:count];
210   for (i = 0; i < count; i++) {
211     NGCalendarDateRange *daterange;
212     NSCalendarDate *start, *end;
213     id object;
214     
215     object = [self objectAtIndex:i];
216     start  = [object valueForKey:s];
217     end    = [object valueForKey:e];
218     
219     /* skip invalid data */
220     if (![start isNotNull]) continue;
221     if (![end   isNotNull]) continue;
222     
223     daterange =
224       [[NGCalendarDateRange alloc] initWithStartDate:start endDate:end];
225     if (daterange) [ma addObject:daterange];
226     [daterange release];
227   }
228   return ma;
229 }
230
231 - (BOOL)dateRangeArrayContainsDate:(NSCalendarDate *)_date {
232   unsigned i, count;
233   
234   if (_date == nil) 
235     return NO;
236   if ((count = [self count]) == 0)
237     return NO;
238
239   for (i = 0; i < count; i++) {
240     if ([[self objectAtIndex:i] containsDate:_date])
241       return YES;
242   }
243   return NO;
244 }
245 - (unsigned)indexOfFirstIntersectingDateRange:(NGCalendarDateRange *)_range {
246   unsigned i, count;
247   
248   if (_range == nil)
249     return NO;
250   
251   if ((count = [self count]) == 0)
252     return NSNotFound;
253
254   for (i = 0; i < count; i++) {
255     if ([[self objectAtIndex:i] doesIntersectWithDateRange:_range])
256       return i;
257   }
258   return NSNotFound;
259 }
260
261 - (NSArray *)arrayByCompactingContainedDateRanges {
262   // TODO: this is a candidate for unit testing ...
263   // TODO: pretty "slow" algorithm, improve
264   NSMutableArray *ma;
265   unsigned i, count;
266   
267   count = [self count];
268   if (count < 2)
269     return [[self copy] autorelease];
270   
271   ma = [NSMutableArray arrayWithCapacity:count];
272   [ma addObject:[self objectAtIndex:0]]; /* add first range */
273   
274   for (i = 1; i < count; i++) {
275     NGCalendarDateRange *rangeToAdd;
276     NGCalendarDateRange *availRange;
277     NGCalendarDateRange *newRange;
278     unsigned idx;
279     
280     rangeToAdd = [self objectAtIndex:i];
281     idx = [ma indexOfFirstIntersectingDateRange:rangeToAdd];
282     
283     if (idx == NSNotFound) {
284       /* range not yet covered in array */
285       [ma addObject:rangeToAdd];
286       continue;
287     }
288     
289     /* union old range and replace the entry */
290     
291     availRange = [ma objectAtIndex:idx];
292     newRange   = [availRange unionDateRange:rangeToAdd];
293     
294     [ma replaceObjectAtIndex:idx withObject:newRange];
295   }
296   /* Note: we might want to join ranges up to some "closeness" (eg 1s)? */
297   return [ma sortedArrayUsingSelector:@selector(compare:)];
298 }
299
300 @end /* NSArray(NGCalendarDateRanges) */