]> err.no Git - sope/blob - sope-core/NGExtensions/NGCalendarDateRange.m
Drop apache 1 build-dependency
[sope] / sope-core / NGExtensions / NGCalendarDateRange.m
1 /*
2   Copyright (C) 2004-2007 Marcus Mueller
3   Copyright (C) 2007      Helge Hess
4
5   This file is part of SOPE.
6
7   SOPE is free software; you can redistribute it and/or modify it under
8   the terms of the GNU Lesser General Public License as published by the
9   Free Software Foundation; either version 2, or (at your option) any
10   later version.
11
12   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
13   WARRANTY; without even the implied warranty of MERCHANTABILITY or
14   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
15   License for more details.
16
17   You should have received a copy of the GNU Lesser General Public
18   License along with SOPE; see the file COPYING.  If not, write to the
19   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
20   02111-1307, USA.
21 */
22
23 #include "NGCalendarDateRange.h"
24 #include <NGExtensions/NSCalendarDate+misc.h>
25 #include <NGExtensions/NSNull+misc.h>
26 #include "common.h"
27
28 @implementation NGCalendarDateRange
29
30 + (id)calendarDateRangeWithStartDate:(NSCalendarDate *)start
31   endDate:(NSCalendarDate *)end
32 {
33     return [[[self alloc] initWithStartDate:start endDate:end] autorelease];
34 }
35
36 - (id)initWithStartDate:(NSCalendarDate *)start endDate:(NSCalendarDate *)end {
37   NSAssert(start != nil, @"startDate MUST NOT be nil!");
38   NSAssert(end   != nil, @"endDate MUST NOT be nil!");
39   
40   if ((self = [super init])) {
41     if ([start compare:end] == NSOrderedAscending) {
42       self->startDate = [start copy];
43       self->endDate   = [end   copy];
44     }
45     else {
46       self->startDate = [end   copy];
47       self->endDate   = [start copy];
48     }
49   }
50   return self;
51 }
52
53 /* NSCopying */
54
55 - (id)copyWithZone:(NSZone *)zone {
56   /* object is immutable */
57   return [self retain];
58 }
59
60 /* accessors */
61
62 - (NSCalendarDate *)startDate {
63   return self->startDate;
64 }
65
66 - (NSCalendarDate *)endDate {
67   return self->endDate;
68 }
69
70 - (NGCalendarDateRange *)intersectionDateRange:(NGCalendarDateRange *)other {
71   NSCalendarDate *a, *b, *c, *d;
72     
73   if ([self compare:other] == NSOrderedAscending) {
74     a = self->startDate;
75     b = self->endDate;
76     c = [other startDate];
77     d = [other endDate];
78   }
79   else {
80     a = [other startDate];
81     b = [other endDate];
82     c = self->startDate;
83     d = self->endDate;
84   }
85   // [a;b[ ?< [c;d[
86   if ([b compare:c] == NSOrderedAscending)
87     return nil; // no intersection
88   // b ?< d
89   if ([b compare:d] == NSOrderedAscending) {
90     // c !< b  && b !< d -> [c;b[
91     if([c compare:b] == NSOrderedSame)
92       return nil; // no real range, thus return nil!
93     else
94       return [NGCalendarDateRange calendarDateRangeWithStartDate:c endDate:b];
95   }
96   if([c compare:d] == NSOrderedSame)
97     return nil; // no real range, thus return nil!
98   // b !> d -> [c;d[
99   return [NGCalendarDateRange calendarDateRangeWithStartDate:c endDate:d];
100 }
101
102 - (BOOL)doesIntersectWithDateRange:(NGCalendarDateRange *)_other {
103   // TODO: improve
104   if (_other == nil) return NO;
105   return [self intersectionDateRange:_other] != nil ? YES : NO;
106 }
107
108 - (NGCalendarDateRange *)unionDateRange:(NGCalendarDateRange *)other {
109   NSCalendarDate *a, *b, *c, *d;
110     
111   if ([self compare:other] == NSOrderedAscending) {
112     a = self->startDate;
113     b = self->endDate;
114     c = [other startDate];
115     d = [other endDate];
116   }
117   else {
118     a = [other startDate];
119     b = [other endDate];
120     c = self->startDate;
121     d = self->endDate;
122   }
123   if ([b compare:d] == NSOrderedAscending)
124     return [NGCalendarDateRange calendarDateRangeWithStartDate:a endDate:d];
125   
126   return [NGCalendarDateRange calendarDateRangeWithStartDate:a endDate:b];
127 }
128
129 - (BOOL)containsDate:(NSCalendarDate *)_date {
130   NSComparisonResult result;
131   
132   result = [self->startDate compare:_date];
133   if (!((result == NSOrderedSame) || (result == NSOrderedAscending)))
134     return NO;
135   result = [self->endDate compare:_date];
136   if (result == NSOrderedAscending)
137     return NO;
138   return YES;
139 }
140
141 - (BOOL)containsDateRange:(NGCalendarDateRange *)_range {
142   NSComparisonResult result;
143
144   result = [self->startDate compare:[_range startDate]];
145   if (!((result == NSOrderedSame) || (result == NSOrderedAscending)))
146     return NO;
147   result = [self->endDate compare:[_range endDate]];
148   if (result == NSOrderedAscending)
149     return NO;
150   return YES;
151 }
152
153 - (NSTimeInterval)duration {
154   return [self->endDate timeIntervalSinceDate:self->startDate];
155 }
156
157 /* comparison */
158
159 - (BOOL)isEqual:(id)other {
160   if (other == nil)
161     return NO;
162   if (other == self)
163     return YES;
164   
165   if ([other isKindOfClass:self->isa] == NO)
166     return NO;
167   
168   return ([self->startDate isEqual:[other startDate]] && 
169           [self->endDate isEqual:[other endDate]]) ? YES : NO;
170 }
171
172 - (unsigned)hash {
173   return [self->startDate hash] ^ [self->endDate hash];
174 }
175
176 - (NSComparisonResult)compare:(NGCalendarDateRange *)other {
177   return [self->startDate compare:[other startDate]];
178 }
179
180 /* KVC */
181
182 - (id)valueForUndefinedKey:(NSString *)_key {
183   /* eg this is used in OGo on 'dateId' to probe for event objects */
184   return nil;
185 }
186
187 /* description */
188
189 - (NSString *)description {
190   NSMutableString *description;
191     
192   description = [NSMutableString stringWithCapacity:64];
193
194   [description appendFormat:@"<%@[0x%x]: startDate:%@ endDate: ", 
195                  NSStringFromClass(self->isa), self, self->startDate];
196   
197   if ([self->startDate isEqual:self->endDate])
198     [description appendString:@"== startDate"];
199   else
200     [description appendFormat:@"%@", self->endDate];
201   [description appendString:@">"];
202   return description;
203 }
204
205 @end /* NGCalendarDateRange */
206
207
208 @implementation NSArray(NGCalendarDateRanges)
209
210 - (NSArray *)arrayByCreatingDateRangesFromObjectsWithStartDateKey:(NSString *)s
211   andEndDateKey:(NSString *)e
212 {
213   NSMutableArray *ma;
214   unsigned i, count;
215   
216   count = [self count];
217   ma    = [NSMutableArray arrayWithCapacity:count];
218   for (i = 0; i < count; i++) {
219     NGCalendarDateRange *daterange;
220     NSCalendarDate *start, *end;
221     id object;
222     
223     object = [self objectAtIndex:i];
224     start  = [object valueForKey:s];
225     end    = [object valueForKey:e];
226     
227     /* skip invalid data */
228     if (![start isNotNull]) continue;
229     if (![end   isNotNull]) continue;
230     
231     daterange =
232       [[NGCalendarDateRange alloc] initWithStartDate:start endDate:end];
233     if (daterange) [ma addObject:daterange];
234     [daterange release];
235   }
236   return ma;
237 }
238
239 - (BOOL)dateRangeArrayContainsDate:(NSCalendarDate *)_date {
240   unsigned i, count;
241   
242   if (_date == nil) 
243     return NO;
244   if ((count = [self count]) == 0)
245     return NO;
246
247   for (i = 0; i < count; i++) {
248     if ([[self objectAtIndex:i] containsDate:_date])
249       return YES;
250   }
251   return NO;
252 }
253 - (unsigned)indexOfFirstIntersectingDateRange:(NGCalendarDateRange *)_range {
254   unsigned i, count;
255   
256   if (_range == nil)
257     return NO;
258   
259   if ((count = [self count]) == 0)
260     return NSNotFound;
261
262   for (i = 0; i < count; i++) {
263     if ([[self objectAtIndex:i] doesIntersectWithDateRange:_range])
264       return i;
265   }
266   return NSNotFound;
267 }
268
269 - (NSArray *)arrayByCompactingContainedDateRanges {
270   // TODO: this is a candidate for unit testing ...
271   // TODO: pretty "slow" algorithm, improve
272   NSMutableArray *ma;
273   unsigned i, count;
274   
275   count = [self count];
276   if (count < 2)
277     return [[self copy] autorelease];
278   
279   ma = [NSMutableArray arrayWithCapacity:count];
280   [ma addObject:[self objectAtIndex:0]]; /* add first range */
281   
282   for (i = 1; i < count; i++) {
283     NGCalendarDateRange *rangeToAdd;
284     NGCalendarDateRange *availRange;
285     NGCalendarDateRange *newRange;
286     unsigned idx;
287     
288     rangeToAdd = [self objectAtIndex:i];
289     idx = [ma indexOfFirstIntersectingDateRange:rangeToAdd];
290     
291     if (idx == NSNotFound) {
292       /* range not yet covered in array */
293       [ma addObject:rangeToAdd];
294       continue;
295     }
296     
297     /* union old range and replace the entry */
298     
299     availRange = [ma objectAtIndex:idx];
300     newRange   = [availRange unionDateRange:rangeToAdd];
301     
302     [ma replaceObjectAtIndex:idx withObject:newRange];
303   }
304   /* Note: we might want to join ranges up to some "closeness" (eg 1s)? */
305   return [ma sortedArrayUsingSelector:@selector(compare:)];
306 }
307
308 @end /* NSArray(NGCalendarDateRanges) */