1 /*
2 * ==================================================================== The
3 * Apache Software License, Version 1.1
4 *
5 * Copyright (c) 2003 Digital Clash LLC. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met: 1.
9 * Redistributions of source code must retain the above copyright notice, this
10 * list of conditions and the following disclaimer. 2. Redistributions in
11 * binary form must reproduce the above copyright notice, this list of
12 * conditions and the following disclaimer in the documentation and/or other
13 * materials provided with the distribution. 3. The end-user documentation
14 * included with the redistribution, if any, must include the following
15 * acknowledgment: "This product includes software developed by the ChronicJ
16 * team (http://www.chronicj.org/)." Alternately, this acknowledgment may
17 * appear in the software itself, if and wherever such third-party
18 * acknowledgments normally appear. 4. The names "ChronicJ" and "Digital Clash"
19 * not be used to endorse or promote products derived from this software
20 * without prior written permission. For written permission, please contact
21 * info@digitalclash.com. 5. Products derived from this software may not be
22 * called "ChronicJ", "Digital Clash", nor may "ChronicJ" or "Digital Clash"
23 * appear in their name, without prior written permission of Digital Clash LLC.
24 *
25 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
26 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
27 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28 * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
29 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
30 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
32 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
34 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 * ==================================================================== This
36 * product includes software developed by the by the Apache Software Foundation
37 * (http://www.apache.org/).
38 * ====================================================================
39 */
40 package org.chronicj;
41
42 import java.util.Arrays;
43 import java.util.Date;
44
45
46 /***
47 * Defines basic requirements and utility methods for classes that model
48 * periods of time. Based directly on the <a
49 * href="http://martinfowler.com/ap2/range.html">Range</a> pattern described
50 * by Martin Fowler. This is a value object.
51 *
52 * @author <a href="mlipper@US-ABP.com">Matthew Lipper</a>
53 *
54 * @see org.chronicj.TimePoint
55 * @see <a href="http://martinfowler.com/ap2/range.html">Range</a>
56 */
57 public class DateRange implements Comparable
58 {
59 /*** Specifies an empty range which can be used as a constant */
60 public static final DateRange EMTPY = new DateRange(new TimePoint(1969, 5, 8),
61 new TimePoint(1968, 12, 12));
62 private TimePoint end;
63 private TimePoint start;
64
65 /***
66 * Construct a DateRange using the given start and end dates. The {@link
67 * java.util.Date} arguments are wrapped using {@link TimePoint}s, and as
68 * such, they are normalized using minute precision (this is the default
69 * precision for a TimePoint). If the start date is greater than the end
70 * date, the range is considered empty. Currently, this class does not
71 * support open ranges, i.e. since or until.
72 *
73 * @param aStartDate
74 * the Date that is the lower end of the range
75 * @param anEndDate
76 * the Date that is the upper end of the range
77 *
78 * @throws NullPointerException
79 * is either argument is null
80 */
81 public DateRange(Date aStartDate, Date anEndDate)
82 {
83 this((aStartDate != null) ? new TimePoint(aStartDate) : null,
84 (anEndDate != null) ? new TimePoint(anEndDate) : null);
85 }
86
87 /***
88 * Construct a DateRange using the supplied start and end <code>TimePoint</code>s.
89 * If the start date is greater than the end date, the range is considered
90 * empty. Currently, this class does not support open ranges, i.e. since or
91 * until.
92 *
93 * @param aStartDate
94 * the TimePoint that is the lower end of the range
95 * @param anEndDate
96 * the TimePoint that is the upper end of the range
97 *
98 * @throws NullPointerException
99 * is either argument is null
100 */
101 public DateRange(TimePoint aStartDate, TimePoint anEndDate)
102 {
103 if (aStartDate == null)
104 {
105 throw new NullPointerException("Lower range cannot be null.");
106 }
107
108 if (anEndDate == null)
109 {
110 throw new NullPointerException("Upper range cannot be null.");
111 }
112
113 start = aStartDate;
114 end = anEndDate;
115 }
116
117 public static boolean isContiguous(DateRange[] args)
118 {
119 Arrays.sort(args);
120
121 for (int i = 0; i < (args.length - 1); i++)
122 {
123 if (!args[i].abuts(args[i + 1]))
124 {
125 return false;
126 }
127 }
128
129 return true;
130 }
131
132 /***
133 * The {@link DatePrecision}of this range. If the start and end of this
134 * range are of different precisions, the greater precision is returned.
135 *
136 * @return DatePrecision of this range
137 */
138 public DatePrecision getDatePrecision()
139 {
140 if (start.getDatePrecision().greaterThan(end.getDatePrecision()))
141 {
142 return start.getDatePrecision();
143 }
144
145 //Less than or equal
146 return end.getDatePrecision();
147 }
148
149 /***
150 * Used mostly by other date range calculations to indicate an empty set.
151 *
152 * @return true or false, indicating whether the start date occurs after
153 * the end date
154 */
155 public boolean isEmpty()
156 {
157 return start.after(end);
158 }
159
160 /***
161 * Used to detect whether two date ranges abut each other.
162 *
163 * @param anotherRange
164 * the range to check against
165 *
166 * @return true if the supplied DateRange argument abuts this range either
167 * occurring before or after
168 */
169 public boolean abuts(DateRange anotherRange)
170 {
171 return !overlaps(anotherRange) && gap(anotherRange).isEmpty();
172 }
173
174 public static DateRange combination(DateRange[] args)
175 {
176 Arrays.sort(args);
177
178 if (!isContiguous(args))
179 {
180 throw new IllegalArgumentException("Unable to combine date ranges");
181 }
182
183 return new DateRange(args[0].start(), args[args.length - 1].end());
184 }
185
186 /***
187 * Compare the current DateRange instance to the one provided. The
188 * following algorithms are used if the start dates are not equal:
189 *
190 * <ul>
191 * <li>whichever instance has a later start date (as determined by {@link
192 * #start()} is the greater value</li>
193 * </ul>
194 *
195 * if the start dates are equal then:
196 *
197 * <ul>
198 * <li>whichever instance has a later end date (as determined by {@link
199 * #end()} is the greater value</li>
200 * <li>if the end dates are equal, then the instances are equal</li>
201 * </ul>
202 *
203 * <b>NOTE:</b> results are non-deterministic if either DateRange
204 * isEmpty()
205 *
206 * @param arg
207 * Object to compare
208 *
209 * @return <ul>
210 * <li>-1 if this TimePoint occurs after the one supplied</li>
211 * <li>0 if this TimePoint occurs at the same time as the one
212 * provided</li>
213 * <li>1 if this TimePoint occurs after the one provided</li>
214 * </ul>
215 *
216 * @throws ClassCastException
217 * if argument is not a DateRange
218 * @throws NullPointerException
219 * if argument is null
220 *
221 * @see {@link Comparable#compareTo(java.lang.Object)}
222 */
223 public int compareTo(Object arg)
224 {
225 DateRange other = (DateRange) arg;
226
227 if (!start.equals(other.start()))
228 {
229 return start.compareTo(other.start());
230 }
231
232 return end.compareTo(other.end());
233 }
234
235 /***
236 * Accessor method for the <code>TimePoint</code> denoting the upper end
237 * of this range.
238 *
239 * @return the TimePoint which holds the current value of the end field
240 */
241 public TimePoint end()
242 {
243 return end;
244 }
245
246 /*
247 * True if the supplied argument ia an instance of DateRange whose start()
248 * method returns a value equal this.start and whose end() method returns a
249 * value equal to this.end.
250 *
251 * @see java.lang.Object#equals(java.lang.Object)
252 */
253 public boolean equals(Object arg)
254 {
255 if (!(arg instanceof DateRange))
256 {
257 return false;
258 }
259
260 DateRange other = (DateRange) arg;
261
262 return start.equals(other.start()) && end.equals(other.end());
263 }
264
265 /***
266 * Used to determine the duration of time (expressed as another DateRange)
267 * between the current DateRange instance and another DateRange. For now,
268 * the default precision, {@link DatePrecision#MINUTE}, is used.
269 *
270 * @param arg the other DateRange against which to determine an intervening gap
271 *
272 * @return anotherRange DateRange delimiting beginning and ending times of
273 * the gap
274 */
275 public DateRange gap(DateRange arg)
276 {
277 if (this.overlaps(arg))
278 {
279 return DateRange.EMTPY;
280 }
281
282 DateRange lower;
283 DateRange higher;
284
285 if (this.compareTo(arg) < 0)
286 {
287 lower = this;
288 higher = arg;
289 }
290 else
291 {
292 lower = arg;
293 higher = this;
294 }
295
296 return new DateRange(lower.end().addMinutes(1),
297 higher.start().addMinutes(-1));
298 }
299
300 /*
301 * Uses hashCode() of the start field.
302 *
303 * @see java.lang.Object#hashCode()
304 */
305 public int hashCode()
306 {
307 return start.hashCode();
308 }
309
310 /***
311 * Used to check whether a given <code>TimePoint</code> occurs within the
312 * current range.
313 *
314 * @param aDate
315 * the TimePoint that is to be checked for occurence within the
316 * current range
317 *
318 * @return true or false, indicating whether the supplied Date occurs
319 * within the current range
320 */
321 public boolean includes(TimePoint aDate)
322 {
323 return !aDate.before(start) && !aDate.after(end);
324 }
325
326 /***
327 * Used to check whether a given <code>DateRange</code> occurs within the
328 * current range.
329 *
330 * @param arg
331 * the DateRange that is to be checked for occurence within the
332 * current range
333 *
334 * @return true or false, indicating whether the supplied DateRange occurs
335 * within the current range
336 */
337 public boolean includes(DateRange arg)
338 {
339 return this.includes(arg.start()) && this.includes(arg.end());
340 }
341
342 /***
343 * Used mostly by other date range calculations to indicate an empty set.
344 *
345 * @param anotherRange
346 * the other DateRange against which to determine an intervening
347 * gap
348 *
349 * @return true or false, indicating whether the start date occurs after
350 * the end date
351 */
352 public boolean overlaps(DateRange anotherRange)
353 {
354 return anotherRange.includes(start) || anotherRange.includes(end) ||
355 includes(anotherRange);
356 }
357
358 /***
359 * Used to check whether a group of ranges completely partition the current
360 * DateRange. For this to be true, the given set of DateRange
361 * arguments,must be contiguous with respect to each other and, when
362 * combined, equal the current range.
363 *
364 * @param otherRanges
365 * the set of DateRange objects to check against
366 *
367 * @return true if the supplied ranges completely partition <code>this</code>
368 * range, false otherwise
369 */
370 public boolean partitionedBy(DateRange[] otherRanges)
371 {
372 if (!DateRange.isContiguous(otherRanges))
373 {
374 return false;
375 }
376
377 return this.equals(DateRange.combination(otherRanges));
378 }
379
380 /***
381 * Accessor method for the <code>TimePoint</code> denoting the lower end
382 * of this range.
383 *
384 * @return the TimePoint which holds the current value of the start field
385 */
386 public TimePoint start()
387 {
388 return start;
389 }
390
391 /*
392 * @see java.lang.Object#toString()
393 */
394 public String toString()
395 {
396 if (isEmpty())
397 {
398 return "Empty Date Range";
399 }
400
401 return start.toString() + " - " + end.toString();
402 }
403 }
This page was automatically generated by Maven