001 package edu.nrao.sss.measure; 002 003 import java.math.BigDecimal; 004 import java.math.MathContext; 005 import java.math.RoundingMode; 006 007 import javax.xml.bind.annotation.XmlElement; 008 import javax.xml.bind.annotation.XmlType; 009 010 import edu.nrao.sss.util.FormatString; 011 012 /** 013 * An interval from one time of day to another. 014 * The two times of day are assumed to be either in the same day or 015 * consecutive days. 016 * <p> 017 * There are two endpoints to an interval. In most intervals, the 018 * starting endpoint is less than the ending endpoint. However, 019 * time of day is best viewed as a circle (like a clock). This 020 * means that the starting point could be numerically larger than 021 * the ending point. Nonetheless, the interval always proceeds 022 * from the starting point to the ending point. In order to 023 * make this traversal, it sometimes happens that the reset, 024 * or midnight, time is crossed.</p> 025 * <p> 026 * This interval is a <i>half-open</i> interval. That is, the interval 027 * includes the starting point but not the ending point.</p> 028 * <p> 029 * <b><u>Caveat</u></b><br/><tt> 030 * July 2006. It might be that some of the query methods that compare 031 * one interval to another, or to a point, are not accurate for the 032 * degenerate case where one or both intervals have zero length. 033 * This possibility will be tested, and corrected, in the future. 034 * --DMH</tt></p> 035 * <p> 036 * <b>Version Info:</b> 037 * <table style="margin-left:2em"> 038 * <tr><td>$Revision: 1708 $</td></tr> 039 * <tr><td>$Date: 2008-11-14 10:31:42 -0700 (Fri, 14 Nov 2008) $</td></tr> 040 * <tr><td>$Author: dharland $</td></tr> 041 * </table></p> 042 * 043 * @author David M. Harland 044 * @since 2006-07-27 045 */ 046 @XmlType(propOrder= {"start","end"}) 047 public class TimeOfDayInterval 048 implements Cloneable 049 { 050 private static final MathContext PRECISION = 051 new MathContext(MathContext.DECIMAL128.getPrecision(),RoundingMode.HALF_UP); 052 053 private static final BigDecimal BD_TWO = new BigDecimal("2.0", PRECISION); 054 055 @XmlElement private TimeOfDay start; 056 @XmlElement private TimeOfDay end; 057 058 //============================================================================ 059 // CREATION & MANIPULATION 060 //============================================================================ 061 062 /** 063 * Creates a new interval equal to a full day, where the length of the day 064 * is a {@link TimeOfDay#STANDARD_DAY_LENGTH standard 24-hour} day. 065 */ 066 public TimeOfDayInterval() 067 { 068 start = new TimeOfDay(); 069 end = new TimeOfDay(); 070 071 end.setToEndOfDay(); 072 } 073 074 /** 075 * Creates a new interval using the given times of day. 076 * <p> 077 * The description of the {@link #set(TimeOfDay, TimeOfDay)} method applies 078 * to this constructor as well.</p> 079 * 080 * @param from the starting point of this interval. The starting 081 * point is included in the interval. 082 * 083 * @param to the ending point of this interval. The ending 084 * point is <i>not</i> included in the interval. 085 */ 086 public TimeOfDayInterval(TimeOfDay from, TimeOfDay to) 087 { 088 setInterval(from, to); 089 } 090 091 /** 092 * Resets this interval to its default state. 093 * <p> 094 * A reset interval has the same state as one newly created by 095 * the {@link #TimeOfDayInterval() no-argument constructor}. 096 * Specifically, it has a length of a standard 24-hour day.</p> 097 */ 098 public void reset() 099 { 100 //If we're using the std day length, just reset the times 101 if (start.getLengthOfDay().toUnits(TimeUnits.SECOND) == 102 TimeOfDay.STANDARD_DAY_LENGTH) 103 { 104 start.set("0.0"); 105 } 106 //Otherwise, make new ones 107 else 108 { 109 start = new TimeOfDay(); 110 end = new TimeOfDay(); 111 } 112 113 end.setToEndOfDay(); 114 } 115 116 /** 117 * Sets the starting and ending points of this interval. 118 * <p> 119 * It is acceptable for then starting time of day to be 120 * later than the ending time of day. The interval still 121 * starts at {@code from}; it is just that, in order to 122 * reach {@code through}, it must cross the midnight point.</p> 123 * <p> 124 * If either parameter is <i>null</i>, an 125 * {@code IllegalArgumentException} will be thrown.</p> 126 * <p> 127 * This class will maintain references to {@code from} and 128 * {@code through}; it will not make copies. This means that 129 * any changes made by clients to the parameter objects after 130 * calling this method will be reflected in this object.</p> 131 * 132 * @param from the starting point of this interval. The starting 133 * point is included in the interval. 134 * 135 * @param to the ending point of this interval. The ending 136 * point is <i>not</i> included in the interval. 137 */ 138 public void set(TimeOfDay from, TimeOfDay to) 139 { 140 setInterval(from, to); 141 } 142 143 /** 144 * Sets the endpoints of this interval based on {@code intervalText}. 145 * If {@code intervalText} is <i>null</i> or <tt>""</tt> (the empty string), 146 * the {@link #reset()} method is called. Otherwise, the parsing is 147 * delegated to {@link #parse(String, String)}. See that method 148 * for details related to parsing. 149 */ 150 public void set(String intervalText, String separator) 151 { 152 if (intervalText == null || intervalText.equals("")) 153 { 154 reset(); 155 } 156 else 157 { 158 try { 159 parseInterval(intervalText, separator); 160 } 161 catch (IllegalArgumentException ex) { 162 //If the parseInterval method threw an exception it never reached 163 //the point of updating this interval, so we don't need to restore 164 //to pre-parsing values. 165 throw ex; 166 } 167 } 168 } 169 170 /** 171 * Sets the endpoints of this interval based on {@code intervalText}. 172 * This is an convenience method equivalent to calling 173 * {@link #set(String, String) 174 * set(intervalText, FormatString.ISO8601_TIME_INTERVAL_SEPARATOR)}. 175 */ 176 public void set(String intervalText) 177 { 178 set(intervalText, FormatString.ISO8601_TIME_INTERVAL_SEPARATOR); 179 } 180 181 /** Called from constructor & public set method. */ 182 private void setInterval(TimeOfDay startTime, TimeOfDay endTime) 183 { 184 if ((startTime == null) || (endTime == null)) 185 throw new IllegalArgumentException( 186 "Cannot configure TimeOfDayInterval with null TimeOfDay."); 187 188 if (!startTime.getLengthOfDay().equals(endTime.getLengthOfDay())) 189 throw new IllegalArgumentException( 190 "The endpoints must both have the same length of day. Found start=" 191 + startTime.getLengthOfDay() + ", end=" + endTime.getLengthOfDay()); 192 193 //Since we don't care about the NUMERICAL ordering of these two 194 //elements, we do NOT need to make clones. Contrast this to 195 //what we needed to do in the TimeInterval class. 196 start = startTime; 197 end = endTime; 198 } 199 200 /** 201 * Exchanges the starting and ending points of this interval. 202 */ 203 public void switchEndpoints() 204 { 205 TimeOfDay temp = start; 206 start = end; 207 end = temp; 208 } 209 210 /** 211 * Creates a new interval whose starting point is this interval's ending 212 * point and whose ending point is this interval's starting point. 213 * 214 * @return a new interval with endpoints opposite to those of this interval. 215 */ 216 public TimeOfDayInterval toComplement() 217 { 218 TimeOfDayInterval result = this.clone(); 219 220 result.switchEndpoints(); 221 222 return result; 223 } 224 225 /** 226 * Returns two new intervals that were formed by splitting this one at the 227 * given point. This interval is not altered by this method. 228 * <p> 229 * <b>Scenario One.</b> If this interval contains {@code pointOfSplit}, then 230 * the first interval in the array runs from this interval's starting point 231 * to {@code pointOfSplit}, and the second interval runs from 232 * {@code pointOfSplit} to this interval's ending point.</p> 233 * <p> 234 * <b>Scenario Two.</b> If this interval does not contain 235 * {@code pointOfSplit}, then the first interval in the array will be equal 236 * to this one, and the second interval will be a zero length interval whose 237 * starting and ending endpoints are both {@code pointOfSplit}.</p> 238 * 239 * @param pointOfSplit the point at which to split this interval in two. 240 * 241 * @return an array of two intervals whose combined length equals this 242 * interval's length. 243 */ 244 public TimeOfDayInterval[] split(TimeOfDay pointOfSplit) 245 { 246 TimeOfDayInterval[] result = new TimeOfDayInterval[2]; 247 248 result[0] = new TimeOfDayInterval(); 249 result[1] = new TimeOfDayInterval(); 250 251 if (this.contains(pointOfSplit)) 252 { 253 result[0].set(this.start, pointOfSplit.clone()); 254 //TODO Put special logic here: 255 // if point of split is zero, change result[0].end to midnigh? 256 // If do above, maybe elim splitAtMidnight? 257 result[1].set(pointOfSplit.clone(), this.end); 258 } 259 else //can't use point to split, as its not in this interval 260 { 261 result[0].set(this.start, this.end); 262 result[1].set(pointOfSplit.clone(), pointOfSplit.clone()); 263 } 264 265 return result; 266 } 267 268 /** 269 * Returns two new intervals that were formed by splitting this one at 270 * midnight. This interval is not altered by this method. 271 * <p> 272 * See {@link #split(TimeOfDay)} for details about the splitting. 273 * This method behaves similarly to calling that method with a 274 * time of day equal to midnight. The one difference is that, if 275 * this interval contains midnight, the first interval in the 276 * returned array will have its endpoint set to 277 * {@link TimeOfDay#setToEndOfDay()}.</p> 278 * 279 * @return an array of two intervals whose combined length equals this 280 * interval's length. 281 */ 282 public TimeOfDayInterval[] splitAtMidnight() 283 { 284 //TODO Take a look below. Is the logic correct? Do we have test cases? 285 // Should esp test intervals that begin at t=0 and intervals 286 // that end at t=24h 287 TimeOfDay midnight = new TimeOfDay(); 288 289 TimeOfDayInterval[] result = this.split(midnight); 290 291 if (this.contains(midnight)) 292 result[0].getEnd().setToEndOfDay(); 293 294 return result; 295 } 296 297 //============================================================================ 298 // QUERIES 299 //============================================================================ 300 301 /** 302 * Returns this interval's starting time of day. 303 * Note that the start time is included in this interval. 304 * <p> 305 * The returned time of day, which is guaranteed to be non-null, 306 * is the actual time held by this interval, 307 * so changes made to it will be reflected in this object.</p> 308 * 309 * @return this interval's starting time. 310 * 311 * @see #set(TimeOfDay, TimeOfDay) 312 */ 313 public TimeOfDay getStart() 314 { 315 return start; 316 } 317 318 /** 319 * Returns this interval's ending time of day. 320 * Note that the end time is <i>not</i> included in this interval. 321 * <p> 322 * The returned time of day, which is guaranteed to be non-null, 323 * is the actual time held by this interval, 324 * so changes made to it will be reflected in this object.</p> 325 * 326 * @return this interval's ending time. 327 * 328 * @see #set(TimeOfDay, TimeOfDay) 329 */ 330 public TimeOfDay getEnd() 331 { 332 return end; 333 } 334 335 /** 336 * Returns the time of day that is midway between the endpoints of this 337 * interval. 338 * <p> 339 * Understand that while the returned value will always be between 340 * the endpoints (or coincident with them, if they are identical), 341 * the center may be numerically smaller than the starting point 342 * of this interval. For example, if this interval starts at 343 * 23:00:00.0 and ends at 09:00:00.0, the duration of this interval 344 * is ten hours and the center is at 04:00:00.0, which is a smaller 345 * value than that of -- but not "before" -- the starting point.</p> 346 * 347 * @return the center of this interval. 348 */ 349 public TimeOfDay getCenterTime() 350 { 351 TimeOfDay center = start.clone(); 352 353 BigDecimal halfDurationSeconds = 354 getDuration().toUnits(TimeUnits.SECOND).divide(BD_TWO, PRECISION); 355 356 center.add(halfDurationSeconds, TimeUnits.SECOND); 357 358 return center; 359 } 360 361 /** 362 * Returns the time duration represented by this interval. 363 * @return the time duration represented by this interval. 364 */ 365 public TimeDuration getDuration() 366 { 367 return start.timeUntil(end); 368 } 369 370 /** 371 * Returns <i>true</i> if {@code time} is contained in this interval. 372 * <p> 373 * Note that this interval is half-open; it includes the starting point, 374 * but not the ending point.</p> 375 * <p> 376 * <b>ISSUE:</b> Right now we allow the starting and ending points to 377 * be coincident, result in an interval of zero duration. One could 378 * argue that this is a point, not an interval. If the starting point 379 * is in the interval, but the ending point is not, what is the situation 380 * for a time, T, that is both the starting and ending point? Is it, or 381 * is it not, in the interval? Right now this class says it is not. 382 * One could argue that we should not allow the starting and ending points 383 * to be coincident. When we resolve the issue, we will update this 384 * class's documentation.</p> 385 * 386 * @param time the time to be tested for containment. 387 * 388 * @return <i>true</i> if {@code time} is contained in this interval. 389 */ 390 public boolean contains(TimeOfDay time) 391 { 392 if (!time.getLengthOfDay().equals(start.getLengthOfDay())) 393 throw new IllegalArgumentException( 394 "The tested time must have the same length of day as the endpoints."); 395 396 boolean result; 397 398 //"Normal" order: end > start 399 if (start.isBefore(end)) 400 { 401 result = (start.equals(time) || start.isBefore(time)) && time.isBefore(end); 402 } 403 //Crossing midnight: start > end 404 else if (start.isAfter(end)) 405 { 406 result = (start.equals(time) || start.isBefore(time)) || time.isBefore(end); 407 } 408 //A half-open interval with identical endpoints contains nothing 409 else //start==end 410 { 411 result = false; 412 } 413 414 return result; 415 } 416 417 /** 418 * Returns <i>true</i> if this interval contains {@code other}. 419 * <p> 420 * Note that an interval that is equal to this one is <i>not</i> 421 * contained by this one. The best analogy is that of a rigid box 422 * with infinitely thin walls: a box that is exactly the same as 423 * another cannot fit inside it.</p> 424 * 425 * @param other an interval that might be contained by this one. 426 * 427 * @return <i>true</i> if this interval contains {@code other}. 428 */ 429 public boolean contains(TimeOfDayInterval other) 430 { 431 //This works because the interval is half open. If other has the 432 //same endpoints as this, this will NOT contain other.getEnd(), 433 //and the proper value of "false" will be returned. 434 return this.contains(other.getStart()) && 435 this.contains(other.getEnd()); 436 } 437 438 /** 439 * Returns <i>true</i> if the {@code other} interval contains this one. 440 * <p> 441 * See {@link #contains(TimeOfDayInterval)} for the definition of 442 * containment.</p> 443 * 444 * @param other an interval that might contain this one. 445 * 446 * @return <i>true</i> if the {@code other} interval contains this one. 447 */ 448 public boolean isContainedBy(TimeOfDayInterval other) 449 { 450 return other.contains(this); 451 } 452 453 /** 454 * Returns <i>true</i> if this interval ends before the {@code other} 455 * one starts. 456 * @param other an interval that might come after this one. 457 * @return <i>true</i> if this interval ends before {@code other} starts. 458 */ 459 public boolean isBefore(TimeOfDayInterval other) 460 { 461 //Remember that the starting point of an interval is IN the interval, 462 //while the ending point is OUTSIDE the interval. This means that 463 //if other has a start that equals this interval's end, it is after 464 //this one. 465 466 return isBefore(other.start); 467 } 468 469 /** 470 * Returns <i>true</i> if this interval starts after the {@code other} 471 * one ends. 472 * @param other an interval that might come after this one. 473 * @return <i>true</i> if this interval starts after {@code other} ends. 474 */ 475 public boolean isAfter(TimeOfDayInterval other) 476 { 477 //Remember that the starting point of an interval is IN the interval, 478 //while the ending point is OUTSIDE the interval. This means that 479 //if other has a start that equals this interval's end, it is after 480 //this one. 481 482 //Don't use this.isAfter(TimeOfDay) because other.end is NOT actually in 483 //the other interval. We would need to pass other.end less epsilon. 484 return this.start.isAfter(other.end) || this.start.equals(other.end); 485 } 486 487 /** 488 * Returns <i>true</i> if this interval ends before the given time. 489 * @param time a time of day that might come after this interval. 490 * @return <i>true</i> if this interval ends before the given time. 491 */ 492 public boolean isBefore(TimeOfDay time) 493 { 494 //Remember that the ending point of an interval is OUTSIDE the interval. 495 //This means that if time is exactly equal to the ending point, it 496 //is after the interval. 497 498 return this.end.isBefore(time) || this.end.equals(time); 499 } 500 501 /** 502 * Returns <i>true</i> if this interval starts after the given time. 503 * @param time a time of day that might come after this interval. 504 * @return <i>true</i> if this interval starts after the given time. 505 */ 506 public boolean isAfter(TimeOfDay time) 507 { 508 //Remember that the starting point of an interval is IN the interval. 509 //This means that if time is exactly equal to the starting point, 510 //it is NOT before the interval. 511 512 return this.start.isAfter(time); 513 } 514 515 /** 516 * Returns <i>true</i> if this interval and the {@code other} form a contiguous 517 * non-overlapping time interval. Since a time interval is half open, this 518 * method returns <i>true</i> when either this interval starts at the same time 519 * the other one ends, or ends at the same time the other starts. 520 * 521 * @param other an interval that might be contiguous with this one. 522 * 523 * @return <i>true</i> if this interval and the {@code other} form a contiguous 524 * non-overlapping time interval. 525 */ 526 public boolean isContiguousWith(TimeOfDayInterval other) 527 { 528 return this.start.equals(other.end) || this.end.equals(other.start); 529 } 530 531 /** 532 * Returns <i>true</i> if this interval overlaps the {@code other} interval. 533 * <p> 534 * Note that equal intervals overlap, as do intervals that have a 535 * container-contained relationship.</p> 536 * 537 * @param other an interval that might overlap this one. 538 * 539 * @return <i>true</i> if this interval overlaps the {@code other} interval. 540 */ 541 public boolean overlaps(TimeOfDayInterval other) 542 { 543 return this.contains(other.start) || this.contains(other.end) || 544 other.contains(this.start) || other.contains(this.end); 545 } 546 547 /** 548 * Returns <i>true</i> if this interval is in its default state, 549 * no matter how it got there. 550 * <p> 551 * An interval is in its <i>default state</i> if both its endpoints 552 * are the same as those of an interval newly created via 553 * the {@link #TimeOfDayInterval() no-argument constructor}. 554 * An interval whose most recent update came via the 555 * {@link #reset() reset} method is also in its default state.</p> 556 * 557 * @return <i>true</i> if this interval is in its default state. 558 */ 559 public boolean isInDefaultState() 560 { 561 return 562 start.toFractionOfDay().compareTo(BigDecimal.ZERO) == 0 && 563 end.toFractionOfDay().compareTo(BigDecimal.ONE) == 0 && 564 end.getLengthOfDay().toUnits(TimeUnits.SECOND) 565 .compareTo(TimeOfDay.STANDARD_DAY_LENGTH) == 0; 566 } 567 568 //============================================================================ 569 // TRANSLATION TO & FROM TEXT 570 //============================================================================ 571 572 /** 573 * Returns a string representation of this interval. 574 * The separator of the endpoints 575 * {@link FormatString#ISO8601_TIME_INTERVAL_SEPARATOR}. 576 * 577 * @return a string representation of this interval. 578 */ 579 public String toString() 580 { 581 return toString(FormatString.ISO8601_TIME_INTERVAL_SEPARATOR); 582 } 583 584 /** 585 * Returns a string representation of this interval. 586 * See the {@link TimeOfDay#toString() toString} method in 587 * {@code TimeOfDay} for information about how the endpoints 588 * are formatted. The {@code endPointSeparator} is used to 589 * separate the two endpoints in the returned string. 590 * 591 * @param endPointSeparator text that separates one endpoint from another 592 * in the returned string. Using a <i>null</i> 593 * or empty-string value here is a bad idea. 594 * @return a string representation of this interval. 595 */ 596 public String toString(String endPointSeparator) 597 { 598 StringBuilder buff = new StringBuilder(); 599 600 buff.append(start.toString()); 601 buff.append(endPointSeparator); 602 buff.append(end.toString()); 603 604 return buff.toString(); 605 } 606 607 /** 608 * Creates a new time of day interval by parsing {@code intervalText}. 609 * <p> 610 * This is a convenience method that is equivalent to:<pre> 611 * parse(intervalText, FormatString.ISO8601_TIME_INTERVAL_SEPARATOR);</pre> 612 * That is, the string separating the two endpoints is assumed to 613 * be {@link FormatString#ISO8601_TIME_INTERVAL_SEPARATOR}. 614 * 615 * @param intervalText the text to be parsed and converted into a time of day 616 * interval. If this value is <i>null</i> or <tt>""</tt> 617 * (the empty string), a new interval of length zero is 618 * returned. 619 * 620 * @return a new time interval based on {@code iso8601Interval}. If 621 * {@code iso8601Interval} is <i>null</i> or <tt>""</tt> (the empty 622 * string), a new time interval of length zero is returned. 623 * 624 * @throws IllegalArgumentException if {@code intervalText} cannot be parsed. 625 */ 626 public static TimeOfDayInterval parse(String intervalText) 627 { 628 return TimeOfDayInterval.parse(intervalText, 629 FormatString.ISO8601_TIME_INTERVAL_SEPARATOR); 630 } 631 632 /** 633 * Creates a new time of day interval by parsing {@code intervalText}. 634 * The general form of {@code intervalText} is 635 * <tt>StartTimeOfDaySeparatorEndTimeOfDay</tt>, 636 * with the particulars of the time of day format described by 637 * the {@link TimeOfDay#parse(String)} method of {@code TimeOfDay}. 638 * 639 * @param intervalText the text to be parsed and converted into a time of day 640 * interval. If this value is <i>null</i> or <tt>""</tt> 641 * (the empty string), a new interval of length zero is 642 * returned. 643 * 644 * @param endPointSeparator the text that separates the starting time of day 645 * from the ending time of day in 646 * {@code intervalText}. 647 * 648 * @return a new time of day interval based on {@code intervalText}. 649 * 650 * @throws IllegalArgumentException if {@code intervalText} cannot be parsed 651 * using {@code endPointSeparator}. 652 */ 653 public static TimeOfDayInterval parse(String intervalText, 654 String endPointSeparator) 655 { 656 TimeOfDayInterval newInterval = new TimeOfDayInterval(); 657 658 //null & "" are permissible 659 if ((intervalText != null) && !intervalText.equals("")) 660 { 661 try { 662 newInterval.parseInterval(intervalText, endPointSeparator); 663 } 664 catch (IllegalArgumentException ex) { 665 throw ex; 666 } 667 } 668 669 return newInterval; 670 } 671 672 /** Does the actual parsing. */ 673 private void parseInterval(String intervalText, String endPointSeparator) 674 { 675 //Find the endpoints of the interval 676 String[] endPoints = intervalText.split(endPointSeparator, -1); 677 678 if (endPoints.length == 2) 679 { 680 setInterval(TimeOfDay.parse(endPoints[0]), 681 TimeOfDay.parse(endPoints[1])); 682 } 683 else //bad # of endpoints 684 { 685 throw new IllegalArgumentException("Could not parse " + intervalText + 686 " using " + endPointSeparator + ". Found + endPoints.length +" + 687 " endpoints. There should be 2."); 688 } 689 } 690 691 //============================================================================ 692 // 693 //============================================================================ 694 695 /** 696 * Returns an interval that is equal to this one. 697 * <p> 698 * If anything goes wrong during the cloning procedure, 699 * a {@code RuntimeException} will be thrown.</p> 700 */ 701 public TimeOfDayInterval clone() 702 { 703 TimeOfDayInterval clone = null; 704 705 try 706 { 707 clone = (TimeOfDayInterval)super.clone(); 708 709 clone.start = this.start.clone(); 710 clone.end = this.end.clone(); 711 } 712 catch (Exception ex) 713 { 714 throw new RuntimeException(ex); 715 } 716 717 return clone; 718 } 719 720 /** Returns <i>true</i> if {@code o} is equal to this interval. */ 721 public boolean equals(Object o) 722 { 723 //Quick exit if o is this 724 if (o == this) 725 return true; 726 727 //Quick exit if o is null 728 if (o == null) 729 return false; 730 731 //Quick exit if classes are different 732 if (!o.getClass().equals(this.getClass())) 733 return false; 734 735 TimeOfDayInterval otherTime = (TimeOfDayInterval)o; 736 737 return otherTime.start.equals(this.start) && 738 otherTime.end.equals(this.end); 739 } 740 741 /** Returns a hash code value for this interval. */ 742 public int hashCode() 743 { 744 //Taken from the Effective Java book by Joshua Bloch. 745 //The constants 17 & 37 are arbitrary & carry no meaning. 746 int result = 17; 747 748 result = 37 * result + start.hashCode(); 749 result = 37 * result + end.hashCode(); 750 751 return result; 752 } 753 754 //============================================================================ 755 // 756 //============================================================================ 757 /* 758 //This method is here for quick, manual, testing. 759 public static void main(String[] args) 760 { 761 //Need 2 cmd line args 762 System.out.println(); 763 System.out.println("args[0]: " + args[0]); 764 System.out.println("args[1]: " + args[1]); 765 System.out.println(); 766 767 TimeOfDay t1 = TimeOfDay.parse(args[0]); 768 TimeOfDay t2 = TimeOfDay.parse(args[1]); 769 770 TimeOfDayInterval work = new TimeOfDayInterval(t1, t2); 771 TimeOfDayInterval home = work.toComplement(); 772 773 System.out.println("At work during: " + work); 774 System.out.println("At work for: " + work.getDuration()); 775 System.out.println("At home during: " + home); 776 System.out.println("At home for: " + home.getDuration()); 777 System.out.println(); 778 System.out.println("clone of work: " + work.clone()); 779 System.out.println("parse of work's string: " + TimeOfDayInterval.parse(work.toString(" - "), " - ")); 780 System.out.println(); 781 782 TimeOfDay lunch = new TimeOfDay(); 783 lunch.set(12,0,0.0); 784 785 TimeOfDay breakfast = new TimeOfDay(); 786 breakfast.set(6,5,0.0); 787 788 System.out.println("breakfast at: " + breakfast); 789 System.out.println("lunch at: " + lunch); 790 System.out.println(); 791 792 System.out.println("Breakfast at work? " + work.contains(breakfast)); 793 System.out.println("Lunch at work? " + work.contains(lunch)); 794 System.out.println("Breakfast at home? " + home.contains(breakfast)); 795 System.out.println("Lunch at home? " + home.contains(lunch)); 796 System.out.println(); 797 798 TimeOfDayInterval[] pmAm = home.splitAtMidnight(); 799 System.out.println("Split home into PM & AM:"); 800 System.out.println(" PM: " + pmAm[0]); 801 System.out.println(" AM: " + pmAm[1]); 802 System.out.println(); 803 } 804 */ 805 }