001    package edu.nrao.sss.measure;
002    
003    import static edu.nrao.sss.math.MathUtil.MC_FINAL_CALC;
004    import static edu.nrao.sss.math.MathUtil.MC_INTERM_CALCS;
005    
006    import java.math.BigDecimal;
007    import java.math.RoundingMode;
008    
009    import javax.xml.bind.annotation.XmlAccessType;
010    import javax.xml.bind.annotation.XmlAccessorType;
011    import javax.xml.bind.annotation.XmlElement;
012    import javax.xml.bind.annotation.XmlType;
013    
014    import edu.nrao.sss.math.MathUtil;
015    import edu.nrao.sss.util.StringUtil;
016    
017    /**
018     * A time of day.
019     * <p>
020     * This class represents the time of day in hours, minutes, and seconds,
021     * without regard to any particular date.
022     * The default length of a day is 24 hours, or 86,400 seconds.
023     * However, clients may create days whose lengths are equal to any positive
024     * number of seconds.</p>
025     * <p>
026     * <b><u>Rollover</u></b><br/>
027     * Many operations on a time-of-day object can lead to a time that is less
028     * than zero or greater than the length of the day.  These operations may
029     * choose not to raise an exception, but to instead roll past midnight in
030     * either direction.  For example, using a 24-hour day, the {@code add}
031     * methods might take a time of 22:00:00 and an addend of 5 hours 43
032     * minutes to give a new time of day of 03:43:00.  In general, rollover
033     * is the preferred behavior for methods of this class.</p>
034     * <p>
035     * <b><u>Start of Day vs. End of Day</u></b><br/>
036     * Related to rollover is the representation of an end-of-day time.
037     * If we use a 24-hour day, midnight can be represented by both
038     * 00:00:00.0 and 24:00:00.0.  <i>In general this class represents
039     * midnight </i>only<i> by 00:00:00.0.</i>  For example the following
040     * method call, {@code myTime.set(24, 0, 0.0);}, will result in a time
041     * of day that behaves as a beginning-of-day value, not as an
042     * end-of-day value.  Likewise, any addition or subtraction that
043     * lands exactly on 24:00:00.0 will behave like 00:00:00.0.</p>
044     * <p>
045     * There is, however, one way to set an end-of-day value, and that
046     * is by calling the {@code setToEndOfDay} method.  After a call
047     * to this method, the time of day will act like an end-of-day value
048     * and will have a text representation of 24:00:00.0.</p>
049     * <p>
050     * <b>Version Info:</b>
051     * <table style="margin-left:2em">
052     *   <tr><td>$Revision: 1707 $</td></tr>
053     *   <tr><td>$Date: 2008-11-14 10:23:59 -0700 (Fri, 14 Nov 2008) $</td></tr>
054     *   <tr><td>$Author: dharland $</td></tr>
055     * </table></p>
056     *  
057     * @author David M. Harland
058     * @since 2006-07-27
059     */
060    @XmlAccessorType(XmlAccessType.NONE)
061    @XmlType(propOrder= {"xmlTime","xmlDayLength"})
062    public class TimeOfDay
063      implements Cloneable, Comparable<TimeOfDay>
064    {
065      private static final char TEXT_SEPARATOR = ':';
066      
067      private static final BigDecimal THOUSAND = new BigDecimal("1000.0");
068      
069      private static final int PRECISION = MC_FINAL_CALC.getPrecision();
070      
071      private static final BigDecimal DEFAULT_TIME = BigDecimal.ZERO;
072    
073      /**
074       * The popular notion that a day is 24 hours long.  This
075       * corresponds to 86,400.0 SI seconds.
076       */
077      public static final BigDecimal STANDARD_DAY_LENGTH = new BigDecimal("86400.0");
078      
079      /**
080       * The length of a sidereal day, in SI seconds.
081       * The is value is 86,164.0905382.
082       */
083      public static final BigDecimal SIDEREAL_DAY_LENGTH = new BigDecimal("86164.0905382");
084      
085      private BigDecimal secondsSinceMidnight;
086    
087      //Note: the TimeOfDayInterval relies on the fact that this value is
088      //set at construction time and may not be altered subsequently.
089      private BigDecimal secondsPerDay;
090    
091      //============================================================================
092      // CONSTRUCTION
093      //============================================================================
094    
095      /**
096       * Creates instance whose length of day is the standard twenty four hours.
097       * The time of day is 00:00:00.0. 
098       */
099      public TimeOfDay()
100      {
101        this(STANDARD_DAY_LENGTH);
102      }
103      
104      /**
105       * Creates instance whose length of day is given by
106       * {@code secondsInDay}. 
107       * The time of day is 00:00:00.0. 
108       * 
109       * @param secondsInOneDay the number of seconds in one day.
110       */
111      public TimeOfDay(BigDecimal secondsInOneDay)
112      {
113        //Need positive, finite, number of seconds
114        if (secondsInOneDay == null ||
115            secondsInOneDay.signum() <= 0 ||
116            MathUtil.doubleValueIsInfinite(secondsInOneDay))
117          throw new IllegalArgumentException(
118            "A day must have a positive, finite, number of seconds.  " +
119            secondsInOneDay + " is not a valid value.");
120      
121        secondsPerDay = rescale(secondsInOneDay);
122        
123        setAndRescale(DEFAULT_TIME);
124      }
125      
126      private BigDecimal rescale(BigDecimal original)
127      {
128        BigDecimal rescaled;
129        
130        int precision = original.precision();
131        
132        if (precision < PRECISION)
133        {
134          int newScale =
135            (original.signum() == 0) ? 1 : PRECISION - precision + original.scale();
136          
137          rescaled = original.setScale(newScale);
138        }
139        else if (precision > PRECISION)
140        {
141          rescaled = original.round(MC_FINAL_CALC);
142        }
143        else
144        {
145          rescaled = original;
146        }
147        
148        return rescaled;
149      }
150    
151      /**
152       * Creates a new time of day based on {@code timeText}.
153       * <p>
154       * The parsed text can be in any of the forms supported by
155       * {@link Longitude#parse(String)}.  The most commonly used
156       * forms are <tt>hh:mm:ss.s</tt> and <tt>99h 99m 99.9s</tt>.</p>
157       * 
158       * @param timeText the text to be parsed and converted into a time of day.
159       *                 If this value is <i>null</i> or <tt>""</tt> (the empty
160       *                 string), a new time corresponding to the start of the
161       *                 day is returned.
162       *
163       * @return a new time of day based on {@code timeText}.
164       * 
165       * @throws IllegalArgumentException if {@code timeText} cannot be parsed.
166       * 
167       * @see #parse(String, BigDecimal)
168       */
169      public static TimeOfDay parse(String timeText)
170      {
171        return TimeOfDay.parse(timeText, TimeOfDay.STANDARD_DAY_LENGTH);
172      }
173      
174      /**
175       * Creates a new time of day by parsing {@code timeText}.
176       * <p>
177       * The parsed text can be in any of the forms supported by
178       * {@link Longitude#parse(String)}.  The most commonly used
179       * forms are <tt>hh:mm:ss.s</tt> and <tt>99h 99m 99.9s</tt>.</p>
180       * 
181       * @param timeText the text to be parsed and converted into a time of day.
182       *                 If this value is <i>null</i> or <tt>""</tt> (the empty
183       *                 string), a new time corresponding to the start of the
184       *                 day is returned.
185       *                  
186       * @param secondsInOneDay the length of a day, in seconds.  This class has two
187       *                        constants that express time in SI seconds,
188       *                        {@link #STANDARD_DAY_LENGTH} and
189       *                        {@link #SIDEREAL_DAY_LENGTH}, that my be used here.
190       *                  
191       * @return a new time of day based on {@code timeText}.
192       * 
193       * @throws IllegalArgumentException if {@code timeText} cannot be parsed.
194       */
195      public static TimeOfDay parse(String timeText, BigDecimal secondsInOneDay)
196      {
197        TimeOfDay result = new TimeOfDay(secondsInOneDay);
198        
199        result.parseTimeOfDay(timeText);
200        
201        return result;
202      }
203    
204      /** Does the actual parsing. */
205      private void parseTimeOfDay(String timeText)
206      {
207        //The parsing mechanism of Longitude is very nearly what we want
208        //here, so use it as a helper.
209        try
210        {
211          //Don't let longitude force to a 24hr day, since this class allows
212          //arbitrary day lengths.
213          Longitude helper = Longitude.parse(timeText, false);
214          Number[] pieces = helper.toHms();
215          this.set(pieces[0].intValue(),
216                   pieces[1].intValue(), (BigDecimal)pieces[2]);
217        }
218        //We want to accept one form that Longitude does not:
219        //hours and minutes w/ colon (hh:mm)
220        catch (Exception ex)
221        {
222          //We also support hh:mm, so see if we have that now.
223          String[] pieces = timeText.split(Character.toString(TEXT_SEPARATOR),-1);
224          if (pieces.length == 2)
225          {
226            int hours   = Integer.parseInt(pieces[0]);
227            int minutes = Integer.parseInt(pieces[1]);
228            this.set(hours, minutes, BigDecimal.ZERO);
229          }
230          else
231          {
232            throw new IllegalArgumentException("Could not parse " + timeText + ".");
233          }
234        }
235      }
236    
237      //============================================================================
238      // SETTING THE TIME OF DAY
239      //============================================================================
240      
241      /**
242       * Sets the time of day.
243       * <p>
244       * While there are no restrictions on the values of the individual
245       * parameters, their combination must result in a positive finite number
246       * of seconds.</p>
247       * 
248       * @param hourOfDay meant to represent the hour of the day.  Typically this
249       *                  is a value in the range [0,23].
250       *                  
251       * @param minuteOfHour meant to represent the minute of an hour.  Typically
252       *                     this is a value in the range [0,59].
253       *                     
254       * @param secondOfMinute meant to represent the second of a minute.
255       *                       Typically this is a value in the range [0.0,60.0).
256       * 
257       * @throws IllegalArgumentException if the number of seconds calculated from
258       *           the parameters is negative, infinite, or not a number.
259       */
260      public void set(int hourOfDay, int minuteOfHour, BigDecimal secondOfMinute)
261      {
262        BigDecimal secFromMin =
263          TimeUnits.MINUTE.convertTo(TimeUnits.SECOND, new BigDecimal(minuteOfHour));
264        
265        BigDecimal secFromHr =
266          TimeUnits.HOUR.convertTo(TimeUnits.SECOND, new BigDecimal(hourOfDay));
267          
268        set(secondOfMinute.add(secFromMin).add(secFromHr));
269      }
270      
271      /**
272       * Sets the time of day.
273       * <p>
274       * While there are no restrictions on the values of the individual
275       * parameters, their combination must result in a positive finite number
276       * of seconds.</p>
277       * 
278       * @param hourOfDay meant to represent the hour of the day.  Typically this
279       *                  is a value in the range [0,23].
280       *                  
281       * @param minuteOfHour meant to represent the minute of an hour.  Typically
282       *                     this is a value in the range [0,59].
283       *                     
284       * @param secondOfMinute meant to represent the second of a minute.
285       *                       Typically this is a value in the range [0.0,60.0).
286       * 
287       * @throws IllegalArgumentException if the number of seconds calculated from
288       *           the parameters is negative, infinite, or not a number.
289       */
290      public void set(int hourOfDay, int minuteOfHour, String secondOfMinute)
291      {
292        set(hourOfDay, minuteOfHour, new BigDecimal(secondOfMinute));
293      }
294      
295      /**
296       * Sets the time of day to the given seconds-of-day.
297       * <p>
298       * The {@code secondOfDay} parameter must be a non-negative finite value.
299       * If it is greater than the length of a day, it will be rolled passed
300       * as many midnights as it takes to make it less than the length of a
301       * day.</p>
302       * 
303       * @param secondOfDay the number of seconds that have elapsed since
304       *                    midnight to the desired time of day.
305       * 
306       * @throws IllegalArgumentException if {@code secondOfDay} is negative,
307       *           infinite, or not a number.
308       */
309      public void set(BigDecimal secondOfDay)
310      {
311        //Need positive, finite, number of seconds
312        if (secondOfDay == null ||
313            secondOfDay.signum() < 0 ||
314            MathUtil.doubleValueIsInfinite(secondOfDay))
315          throw new IllegalArgumentException(
316            "The time of day must be a non-negative, finite, number of seconds.  " +
317            secondOfDay + " is not a valid time of day.");
318        
319        normalizeAndSet(secondOfDay);
320      }
321      
322      /**
323       * Sets this time of day to the end of the day.
324       * <p>
325       * This end-of-day value can be reached in no other way.  It can
326       * not be set by the other setters, and it can not be reached via
327       * addition or subtraction.</p>
328       * <p>
329       * After this method has been called, this time of day behaves
330       * like an end-of-day value.  All other values will be before, and
331       * less than, this value.  Note that  
332       * any non-negative increment to this time of day -- even an increment
333       * of zero -- will result in rollover.</p>
334       */
335      public void setToEndOfDay()
336      {
337        secondsSinceMidnight = secondsPerDay;
338      }
339    
340      /**
341       * Sets the value and units of this time of day based on {@code timeText}.
342       * See {@link #parse(String)} for the expected format of
343       * {@code timeText}.
344       * <p>
345       * If the parsing fails, this time of day will be kept in its current
346       * state.</p>
347       *                                  
348       * @param timeText a string that will be converted into
349       *                 a time of day.
350       * 
351       * @throws IllegalArgumentException if {@code timeText} is not in
352       *                                  the expected form.
353       */
354      public void set(String timeText)
355      {
356        if ((timeText == null) || timeText.equals(""))
357        {
358          set(DEFAULT_TIME);
359        }
360        else
361        {
362          BigDecimal oldTime = secondsSinceMidnight;
363          
364          try {
365            this.parseTimeOfDay(timeText);
366          }
367          catch (Exception ex) {
368            secondsSinceMidnight = oldTime;
369            throw new IllegalArgumentException("Could not parse " + timeText, ex);
370          }
371        }
372      }
373      
374      private void setAndRescale(BigDecimal newSecondsSinceMidnight)
375      {
376        secondsSinceMidnight = rescale(newSecondsSinceMidnight);
377      }
378    
379      //============================================================================
380      // TIME OF DAY QUERIES
381      //============================================================================
382    
383      /**
384       * Returns the hour of the day.
385       * For example, if the time of day is 12:34:56.789,
386       * the value returned will be 12.
387       * Contrast this with the call:<pre>
388       *   getTimeSinceMidnight().toUnits(TimeUnits.HOUR);</pre>
389       * which would return 12.58244138888889.
390       * 
391       * @return the hour of this time of day's day.
392       */
393      public int getHourOfDay()
394      {
395        return TimeUnits.SECOND.convertTo(TimeUnits.HOUR,
396                                          secondsSinceMidnight).intValue();
397      }
398      
399      /**
400       * Returns the minute of this time of day's hour.
401       * For example, if the time of day is 12:34:56.789,
402       * the value returned will be 34.
403       * Contrast this with the call:<pre>
404       *   getTimeSinceMidnight().toUnits(TimeUnits.MINUTE);</pre>
405       * which would return 754.9464833333333.
406       * 
407       * @return the minute of this time of day's hour.
408       */
409      public int getMinuteOfHour()
410      {
411        int wholeHours = getHourOfDay();
412        
413        int hourMinutes =
414          TimeUnits.HOUR.convertTo(TimeUnits.MINUTE,
415                                   new BigDecimal(wholeHours)).intValue();
416        
417        int wholeMinutes =
418          TimeUnits.SECOND.convertTo(TimeUnits.MINUTE,
419                                     secondsSinceMidnight).intValue();
420        
421        return wholeMinutes - hourMinutes;
422      }
423      
424      /**
425       * Returns the second of this time of day's minute.
426       * For example, if the time of day is 12:34:56.789,
427       * the value returned will be 56.789.
428       * Contrast this with the call:<pre>
429       *   getTimeSinceMidnight().toUnits(TimeUnits.SECOND);</pre>
430       * which would return 45,296.789, and to:<pre>
431       *   getSecondOfMinuteWhole();</pre>
432       * which would return 56;
433       * 
434       * @return the second of this time of day's minute.
435       */
436      public BigDecimal getSecondOfMinute()
437      {
438        int wholeMinutes =
439          TimeUnits.SECOND.convertTo(TimeUnits.MINUTE,
440                                     secondsSinceMidnight).intValue();
441        
442        BigDecimal minuteSeconds =
443          TimeUnits.MINUTE.convertTo(TimeUnits.SECOND, new BigDecimal(wholeMinutes));
444        
445        return secondsSinceMidnight.subtract(minuteSeconds);
446      }
447      
448      /**
449       * Returns the whole number of seconds of this time of day's minute.
450       * For example, if the time of day is 12:34:56.789,
451       * the value returned will be 56.
452       * Contrast this with the call:<pre>
453       *   getTimeSinceMidnight().toUnits(TimeUnits.SECOND);</pre>
454       * which would return 45,296.789, and to:<pre>
455       *   getSecondOfMinute();</pre>
456       * which would return 56.789;
457       * 
458       * @return the whole number of seconds in this time of day's minute.
459       * 
460       * @since 2008-09-26
461       */
462      public int getSecondOfMinuteWhole()
463      {
464        return getSecondOfMinute().intValue();
465      }
466      
467      /**
468       * Returns the number of milliseconds in this time of day's seconds.
469       * For example, if the time of day is 12:34:56.7890123,
470       * the value returned will be 789.0123.
471       * 
472       * @return the number of milliseconds in this time of day's seconds.
473       * 
474       * @since 2008-09-26
475       */
476      public BigDecimal getMilliOfSecond()
477      {
478        BigDecimal decimalSeconds = getSecondOfMinute();
479        BigDecimal intSeconds     = new BigDecimal(decimalSeconds.intValue());
480        
481        return rescale(decimalSeconds.subtract(intSeconds).multiply(THOUSAND));
482      }
483      
484      /**
485       * Returns the amount of time that has elapsed from midnight until
486       * this time of day.
487       * 
488       * @return the amount of time since the previous midnight.
489       */
490      public TimeDuration getTimeSinceMidnight()
491      {
492        return new TimeDuration(secondsSinceMidnight, TimeUnits.SECOND);
493      }
494      
495      /**
496       * Returns the amount of time that will elapse from this time of day
497       * until midnight.
498       * 
499       * @return the amount of time until the next midnight.
500       */
501      public TimeDuration getTimeUntilMidnight()
502      {
503        BigDecimal secondsUntilMidnight =
504          secondsPerDay.subtract(secondsSinceMidnight);
505        
506        return new TimeDuration(secondsUntilMidnight, TimeUnits.SECOND);
507      }
508      
509      /**
510       * Returns <i>true</i> if this time of day is earlier than {@code other}.
511       * @param other the time to be tested against this one.
512       * @return <i>true</i> if this time of day is earlier than {@code other}.
513       */
514      public boolean isBefore(TimeOfDay other)
515      {
516        return this.compareTo(other) < 0;
517      }
518      
519      /**
520       * Returns <i>true</i> if this time of day is later than {@code other}.
521       * @param other the time to be tested against this one.
522       * @return <i>true</i> if this time of day is later than {@code other}.
523       */
524      public boolean isAfter(TimeOfDay other)
525      {
526        return this.compareTo(other) > 0;
527      }
528    
529      /**
530       * Returns <i>true</i> if this time of day is in its default state,
531       * no matter how it got there.
532       * <p>
533       * A time of day is in its <i>default state</i> if both its current time
534       * and the length of its day are the same as those of a time of day
535       * newly created via the {@link #TimeOfDay() no-argument constructor}.</p>
536       * 
537       * @return <i>true</i> if this time of day is in its default state.
538       */
539      public boolean isInDefaultState()
540      {
541        return secondsSinceMidnight.equals(DEFAULT_TIME) &&
542               secondsPerDay.equals(STANDARD_DAY_LENGTH);
543      }
544    
545      /**
546       * Returns the amount of time until this time of day equals {@code other}.
547       * <p>
548       * If the other time of day is equal to this one, the duration returned
549       * will have a length of zero.  If the other time is later than this one,
550       * the duration will be a simple subtraction of this from other.  If the
551       * other time, though, is earlier than this one, the duration will represent
552       * the distance from this time, through midnight, to the other time traveling
553       * forward in time.
554       * That is, the direction of time is always from this one to the other,
555       * even if that represents a longer duration than traversing time in the
556       * opposite direction.</p>
557       * <p>
558       * <b>Note</b> that the other time must have the same length of day
559       * as this one.</p>
560       * 
561       * @param other the time of day to be reached from this one.
562       * 
563       * @return the duration from this time of day to {@code other}.
564       * 
565       * @throws IllegalArgumentException if {@code other}'s length of day is not
566       *           equal to this object's length of day.
567       */
568      public TimeDuration timeUntil(TimeOfDay other)
569      {
570        //Make sure both times have same length of day
571        if (!other.secondsPerDay.equals(this.secondsPerDay))
572          throw new IllegalArgumentException(
573            "The other time must have the same length of day as this one.");
574        
575        BigDecimal deltaSeconds =
576          other.secondsSinceMidnight.subtract(this.secondsSinceMidnight);
577    
578        //Need to go from now, to midnight, then to other
579        if (deltaSeconds.signum() < 0)
580        {
581          deltaSeconds =
582            this.secondsPerDay.subtract(this.secondsSinceMidnight)
583                              .add(other.secondsSinceMidnight); 
584        }
585    
586        return new TimeDuration(deltaSeconds, TimeUnits.SECOND);
587      }
588    
589      /**
590       * Returns the time of day as a fraction of the length of
591       * day.  The length of day is set at the time this time
592       * of day is constructed.
593       * 
594       * @return the fraction of the day that has passed from
595       *         midnight to this time of day.
596       */
597      public BigDecimal toFractionOfDay()
598      {
599        return secondsSinceMidnight.divide(secondsPerDay, MC_INTERM_CALCS);
600      }
601      
602      /**
603       * Non-public method that returns a value of unspecified time units
604       * that have passed from midnight to this time of day.  This method
605       * is useful for comparing one time to another.  Clients should make
606       * no assumptions, though, about the units of time being returned.
607       */
608      BigDecimal size()
609      {
610        return secondsSinceMidnight;
611      }
612    
613      //============================================================================
614      // LENGTH OF DAY
615      //============================================================================
616    
617      /**
618       * Returns the length of day on which this time of day is based.
619       * @return the length of day on which this time of day is based.
620       */
621      public TimeDuration getLengthOfDay()
622      {
623        return new TimeDuration(secondsPerDay, TimeUnits.SECOND);
624      }
625    
626      //============================================================================
627      // ARITHMETIC
628      //============================================================================
629    
630      /**
631       * Adds the given amount of time to this time of day.
632       * If the addition causes a time less than zero or greater than
633       * the last moment of the day, rollovers will occur until the
634       * result is a valid time of day.
635       * 
636       * @param value an amount of time.
637       * @param units the units in which {@code value} is expressed.
638       * @return this time of day, after the addition has been performed.
639       * @throws IllegalArgumentException if {@code value} is infinite.
640       */
641      public TimeOfDay add(String value, TimeUnits units)
642      {
643        return add(new BigDecimal(value), units);
644      }
645    
646      /**
647       * Adds the given amount of time to this time of day.
648       * If the addition causes a time less than zero or greater than
649       * the last moment of the day, rollovers will occur until the
650       * result is a valid time of day.
651       * 
652       * @param value an amount of time.
653       * @param units the units in which {@code value} is expressed.
654       * @return this time of day, after the addition has been performed.
655       * @throws IllegalArgumentException if {@code value} is infinite.
656       */
657      public TimeOfDay add(BigDecimal value, TimeUnits units)
658      {
659        //Do not accept +/-infinity or NaN
660        if (MathUtil.doubleValueIsInfinite(value))
661          throw new IllegalArgumentException("Cannot add " + value + 
662            units.getSymbol() +
663            " to time of day.  Value must be a real, finite, number.");
664        
665        normalizeAndSet(secondsSinceMidnight.add(units.convertTo(TimeUnits.SECOND,
666                                                                 value)));
667        return this;
668      }
669    
670      /**
671       * Adds the given amount of time to this time of day.
672       * If the addition causes a time less than zero or greater than
673       * the last moment of the day, rollovers will occur until the
674       * result is a valid time of day.
675       * 
676       * @param duration a length of time.
677       * @return this time of day, after the addition has been performed.
678       * @throws IllegalArgumentException if {@code duration}'s value is
679       *           infinite or NaN.
680       */
681      public TimeOfDay add(TimeDuration duration)
682      {
683        return add(duration.getValue(), duration.getUnits());
684      }
685      
686      /**
687       * Subtracts the given amount of time from this time of day.
688       * If the subtraction causes a time less than zero or greater than
689       * the last moment of the day, rollovers will occur until the
690       * result is a valid time of day.
691       * 
692       * @param value an amount of time.
693       * @param units the units in which {@code value} is expressed.
694       * @return this time of day, after the subtraction has been performed.
695       * @throws IllegalArgumentException if {@code value} is infinite.
696       */
697      public TimeOfDay subtract(String value, TimeUnits units)
698      {
699        return subtract(new BigDecimal(value), units);
700      }
701      
702      /**
703       * Subtracts the given amount of time from this time of day.
704       * If the subtraction causes a time less than zero or greater than
705       * the last moment of the day, rollovers will occur until the
706       * result is a valid time of day.
707       * 
708       * @param value an amount of time.
709       * @param units the units in which {@code value} is expressed.
710       * @return this time of day, after the subtraction has been performed.
711       * @throws IllegalArgumentException if {@code value} is infinite.
712       */
713      public TimeOfDay subtract(BigDecimal value, TimeUnits units)
714      {
715        return add(value.negate(), units);
716      }
717    
718      /**
719       * Subtracts the given amount of time from this time of day.
720       * If the subtraction causes a time less than zero or greater than
721       * the last moment of the day, rollovers will occur until the
722       * result is a valid time of day.
723       * 
724       * @param duration a length of time.
725       * @return this time of day, after the subtraction has been performed.
726       * @throws IllegalArgumentException if {@code duration}'s value is
727       *           infinite.
728       */
729      public TimeOfDay subtract(TimeDuration duration)
730      {
731        return add(duration.getValue().negate(), duration.getUnits());
732      }
733      
734      /**
735       * Transforms {@code seconds} into a non-negative value that is less
736       * than the number of seconds in a day and uses the transformed amount
737       * to set this time of day.
738       * 
739       * This private method does NOT check for infinity or NaN; you must do
740       * that prior to the call.
741       */
742      private void normalizeAndSet(BigDecimal seconds)
743      {
744        //Put seconds into range [0.0 - secondsPerDay)
745        if (seconds.signum() < 0)
746        {
747          //Mimic multiples = Math.ceil(seconds/ -secondsPerDay)
748          BigDecimal[] divAndRem =
749            seconds.divideAndRemainder(secondsPerDay.negate(), MC_INTERM_CALCS);
750          
751          BigDecimal multiples = divAndRem[0]; //equiv to floor(x)
752          if (divAndRem[1].signum() < 0)
753            multiples = multiples.add(BigDecimal.ONE);
754          
755          seconds = seconds.add(secondsPerDay.multiply(multiples));
756        }
757        else if (seconds.compareTo(secondsPerDay) >= 0)
758        {
759          BigDecimal multiples =
760            seconds.divideToIntegralValue(secondsPerDay, MC_INTERM_CALCS);
761    
762          seconds = seconds.subtract(secondsPerDay.multiply(multiples));
763        }
764    
765        //Exactly hitting top of clock
766        if (seconds.compareTo(secondsPerDay) == 0)
767          seconds = BigDecimal.ZERO;
768        
769        setAndRescale(seconds);
770        
771        assert secondsSinceMidnight.compareTo(BigDecimal.ZERO) >= 0 : secondsSinceMidnight;
772        assert secondsSinceMidnight.compareTo(secondsPerDay) < 0 : secondsSinceMidnight;
773      }
774    
775      //============================================================================
776      // CONVERSION TO OTHER FORMS
777      //============================================================================
778    
779      /**
780       * Creates an angular representation of this time of day.
781       * The angle returned is based on the ratio of the time of day to the
782       * length of a day.  The best way to understand this is to picture a round
783       * clock with a single hand and with the zero point, or start of the day,
784       * at the top.  The angle made, in a clockwise direction, between the current
785       * position of the hand and the top of the clock is the value returned.
786       * 
787       * @return
788       *   this time of day, expressed as an angle.
789       */
790      public Angle toAngle()
791      {
792        BigDecimal fraction = secondsSinceMidnight.divide(secondsPerDay,
793                                                          MC_INTERM_CALCS);
794        return
795          new Angle(fraction.multiply(new BigDecimal("100.0")), ArcUnits.PERCENT);
796      }
797      
798      /**
799       * Creates a text representation of this time of day.
800       * The format of the returned string is <tt>H:M:S</tt>,
801       * where <tt>H, M,</tt> and <tt>S</tt> are as described
802       * in {@link #parse(String)}.
803       * 
804       * @see #toString(int, int)
805       */
806      public String toString()
807      {
808        return toString(0, -1);
809      }
810      
811      /**
812       * Creates a text representation of this time of day.
813       * The format of the returned string is <tt>H:M:S</tt>,
814       * where <tt>H, M,</tt> and <tt>S</tt> are as described
815       * in {@link #parse(String)}.
816       * 
817       * @param minFracDigits the minimum number of places after the decimal point
818       *                      for the seconds field.
819       *                      
820       * @param maxFracDigits the maximum number of places after the decimal point
821       *                      for the seconds field.
822       */
823      public String toString(int minFracDigits, int maxFracDigits)
824      {
825        BigDecimal seconds = getSecondOfMinute();
826        int        minutes = getMinuteOfHour();
827        int        hours   = getHourOfDay();
828        
829        //Rollover logic
830        //We have to worry about things like 59.999 being rounded to 60.0
831        if (maxFracDigits >= 0)
832        {
833          if (seconds.setScale(maxFracDigits, RoundingMode.HALF_UP).doubleValue() == 60.0)
834          {
835            seconds = BigDecimal.ZERO;
836            minutes++;
837          }
838          
839          if (minutes == 60)
840          {
841            minutes = 0;
842            hours++;
843          }
844          
845          if (hours == 24)
846            hours = 0;
847        }
848        
849        //Formatting logic
850        StringBuilder buff = new StringBuilder();
851        
852        if (hours < 10)
853          buff.append('0');
854        buff.append(hours).append(TEXT_SEPARATOR);
855        
856        if (minutes < 10)
857          buff.append('0');
858        buff.append(minutes).append(TEXT_SEPARATOR);
859    
860        if (seconds.compareTo(BigDecimal.TEN) < 0)
861          buff.append('0');
862        
863        if (maxFracDigits >= 0)
864          buff.append(StringUtil.getInstance().formatNoScientificNotation(
865                      seconds, minFracDigits, maxFracDigits));
866        else //no rounding or truncation
867          buff.append(
868            StringUtil.getInstance().formatNoScientificNotation(seconds));
869        
870        return buff.toString();
871      }
872    
873      //===========================================================================
874      // HELPERS FOR PERSISTENCE MECHANISMS
875      //===========================================================================
876    
877      @XmlElement(name="secondsPerDay")
878      @SuppressWarnings("unused")
879      private BigDecimal getXmlDayLength()
880      {
881        return secondsPerDay.compareTo(STANDARD_DAY_LENGTH) == 0 ?
882               null : secondsPerDay.stripTrailingZeros();
883      }
884      @SuppressWarnings("unused")
885     private void setXmlDayLength(BigDecimal v)
886      {
887        secondsPerDay = (v == null) ? STANDARD_DAY_LENGTH : rescale(v);
888      }
889    
890      @XmlElement(name="secondsSinceMidnight")
891      @SuppressWarnings("unused")
892      private BigDecimal getXmlTime()
893      {
894        return secondsSinceMidnight.stripTrailingZeros();
895      }
896      @SuppressWarnings("unused")
897      private void setXmlTime(BigDecimal v)
898      {
899        setAndRescale(v == null ? BigDecimal.ZERO : v);
900      }
901    
902      //============================================================================
903      // 
904      //============================================================================
905    
906      /**
907       *  Returns a time of day that is equal to this one.
908       *  <p>
909       *  If anything goes wrong during the cloning procedure,
910       *  a {@code RuntimeException} will be thrown.</p>
911       */
912      public TimeOfDay clone()
913      {
914        TimeOfDay clone = null;
915    
916        try
917        {
918          //This line takes care of the primitive fields properly
919          clone = (TimeOfDay)super.clone();
920        }
921        catch (Exception ex)
922        {
923          throw new RuntimeException(ex);
924        }
925        
926        return clone;
927      }
928    
929      /** Returns <i>true</i> if {@code o} is equal to this time of day. */
930      public boolean equals(Object o)
931      {
932        //Quick exit if o is this
933        if (o == this)
934          return true;
935        
936        //Quick exit if o is null
937        if (o == null)
938          return false;
939        
940        //Quick exit if classes are different
941        if (!o.getClass().equals(this.getClass()))
942          return false;
943        
944        TimeOfDay other = (TimeOfDay)o;
945        
946        return compareTo(other) == 0;
947      }
948    
949      /** Returns a hash code value for this time of day. */
950      public int hashCode()
951      {
952        //Taken from the Effective Java book by Joshua Bloch.
953        //The constants 17 & 37 are arbitrary & carry no meaning.
954        int result = 17;
955        
956        result = 37 * result + secondsSinceMidnight.hashCode();
957        result = 37 * result + secondsPerDay.hashCode();
958        
959        return result;
960      }
961      
962      /**
963       * Compares this time of day to {@code other} for order.
964       * <p>
965       * One time is deemed to be "less than" the other if it is proportionately
966       * closer to the beginning of its day than the other.</p>
967       * 
968       * @param other the time to which this one is compared.
969       * 
970       * @return a negative integer, zero, or a positive integer as this time
971       *         is less than, equal to, or greater than the other time.
972       */
973      public int compareTo(TimeOfDay other)
974      {
975        int answer;
976        
977        //If the days are the same length, do straight comparsion
978        if (this.secondsPerDay.compareTo(other.secondsPerDay) == 0)
979        {
980          answer = this.secondsSinceMidnight.compareTo(other.secondsSinceMidnight);
981        }
982        //Otherwise, compare fraction-of-day values
983        else
984        {
985          answer = this.toFractionOfDay().compareTo(other.toFractionOfDay());
986        }
987        
988        return answer;
989      }
990      
991      //============================================================================
992      // 
993      //============================================================================
994    
995      /*
996      public static void main(String[] args)
997      {
998        for (String arg : args)
999        {
1000          System.out.print("timeText = " + arg);
1001          String output, roundedOutput;
1002          try
1003          {
1004            TimeOfDay tod = TimeOfDay.parse(arg);
1005            output = tod.toString();
1006            roundedOutput = tod.toString(2, 2);
1007          }
1008          catch (Exception ex)
1009          {
1010            output = ex.toString();
1011            roundedOutput = "not computed";
1012          }
1013          System.out.print(", time of day = " + output);
1014          System.out.println(", rounded(2,2) = " + roundedOutput);
1015        }
1016      }
1017      */
1018      /*
1019      //This method is here for quick, manual, testing.
1020      public static void main(String[] args)
1021      {
1022        System.out.println();
1023    
1024        TimeOfDay midDay = new TimeOfDay(TimeOfDay.STANDARD_DAY_LENGTH);
1025        midDay.set(12, 0, "0.0");
1026        
1027        TimeOfDay endDay = new TimeOfDay(TimeOfDay.STANDARD_DAY_LENGTH);
1028        endDay.setToEndOfDay();
1029        
1030        TimeOfDay startDay = new TimeOfDay(TimeOfDay.STANDARD_DAY_LENGTH);
1031        
1032        for (String arg : args)
1033        {
1034          TimeOfDay tod = TimeOfDay.parse(arg, TimeOfDay.STANDARD_DAY_LENGTH);
1035          display("arg = " + arg, tod);
1036          
1037          TimeOfDay todClone = tod.clone();
1038          display("clone", todClone);
1039          
1040          compare("Compare orig to clone", tod, todClone);
1041          compare("Compare orig to midDay", tod, midDay);
1042          compare("Compare orig to endDay", tod, endDay);
1043          compare("Compare midDay to endDay", midDay, endDay);
1044          compare("Compare startDay to endDay", startDay, endDay);
1045        }
1046      }
1047      private static void display(String title, TimeOfDay tod)
1048      {
1049        System.out.println(title);
1050        System.out.println("  Time of Day: " + tod);
1051        System.out.println();
1052        
1053        System.out.println("  Hour of Day:      " + tod.getHourOfDay());
1054        System.out.println("  Minute of Hour:   " + tod.getMinuteOfHour());
1055        System.out.println("  Second of Minute: " + tod.getSecondOfMinute());
1056        System.out.println();
1057        
1058        System.out.println("  Hours:   " + tod.getTimeSinceMidnight().toUnits(TimeUnits.HOUR));
1059        System.out.println("  Minutes: " + tod.getTimeSinceMidnight().toUnits(TimeUnits.MINUTE));
1060        System.out.println("  Seconds: " + tod.getTimeSinceMidnight().toUnits(TimeUnits.SECOND));
1061        System.out.println();
1062        
1063        System.out.println("  Fraction of Day: " + tod.toFractionOfDay());
1064        System.out.println("  Angle (degrees): " + tod.toAngle().toUnits(ArcUnits.DEGREE));
1065        System.out.println("  Hash Code      : " + tod.hashCode());
1066        System.out.println();
1067      }
1068      private static void compare(String title, TimeOfDay tod1, TimeOfDay tod2)
1069      {
1070        System.out.println(title);
1071    
1072        System.out.println("  1: " + tod1 + ", 2: " + tod2);
1073        System.out.println();
1074        System.out.println("  1.equals(2):    " + tod1.equals(tod2));
1075        System.out.println("  2.equals(1):    " + tod2.equals(tod1));
1076        System.out.println("  1.compareTo(2): " + tod1.compareTo(tod2));
1077        System.out.println("  2.compareTo(1): " + tod2.compareTo(tod1));
1078        System.out.println("  1.isBefore(2):  " + tod1.isBefore(tod2));
1079        System.out.println("  2.isBefore(1):  " + tod2.isBefore(tod1));
1080        System.out.println("  1.isAfter(2):   " + tod1.isAfter(tod2));
1081        System.out.println("  2.isAfter(1):   " + tod2.isAfter(tod1));
1082        System.out.println("  1.timeUntil(2): " + tod1.timeUntil(tod2));
1083        System.out.println("  2.timeUntil(1): " + tod2.timeUntil(tod1));
1084        System.out.println();
1085      }
1086      */
1087    }