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    }