View Javadoc
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