001    package edu.nrao.sss.measure;
002    
003    import java.math.BigDecimal;
004    import java.text.ParseException;
005    import java.text.SimpleDateFormat;
006    import java.util.Date;
007    
008    import javax.xml.bind.annotation.XmlElement;
009    import javax.xml.bind.annotation.XmlType;
010    
011    import static edu.nrao.sss.util.FormatString.ISO8601_DATE_TIME_TIMEZONE;
012    import static edu.nrao.sss.util.FormatString.ISO8601_TIME_INTERVAL_SEPARATOR;
013    
014    import edu.nrao.sss.util.DateUtility;
015    
016    /**
017     * An interval from one point in time to another.
018     * <p>
019     * This interval is half-open; it includes the starting point
020     * but does <i>not</i> include the ending point.</p>
021     * <p>
022     * <b><u>Caveat</u></b><br/><tt>
023     * July 2006. It might be that some of the query methods that compare
024     * one interval to another, or to a point, are not accurate for the
025     * degenerate case where one or both intervals have zero length.
026     * This possibility will be tested, and corrected, in the future.
027     * --DMH</tt></p>
028     * <p>
029     * <b>Version Info:</b>
030     * <table style="margin-left:2em">
031     *   <tr><td>$Revision: 1708 $</td></tr>
032     *   <tr><td>$Date: 2008-11-14 10:31:42 -0700 (Fri, 14 Nov 2008) $</td></tr>
033     *   <tr><td>$Author: dharland $</td></tr>
034     * </table></p>
035     * 
036     * @author David M. Harland
037     * @since 2006-03-24
038     */
039    @XmlType(propOrder= {"start","end"})
040    public class TimeInterval
041      implements Cloneable, Comparable<TimeInterval>
042    {
043      private static final long DEFAULT_START_TIME =
044        DateUtility.createEarlyDate().getTime();
045    
046      private static final long DEFAULT_END_TIME =
047        DateUtility.createLateDate().getTime();
048      
049      @XmlElement private Date start;
050      @XmlElement private Date end;
051    
052      //===========================================================================
053      // CONSTRUCTORS
054      //===========================================================================
055    
056      /**
057       * Creates a new large interval.
058       * The newly created interval begins prior to the current century
059       * and ends after the current millenium.
060       */
061      public TimeInterval()
062      {
063        start = new Date(DEFAULT_START_TIME);
064        end   = new Date(DEFAULT_END_TIME);
065      }
066      
067      /**
068       * Creates a new interval using the given times.
069       * <p>
070       * The description of the {@link #set(Date, Date)} method applies
071       * to this constructor as well.</p>
072       * 
073       * @param from the starting point of this interval.  The starting
074       *             point is included in the interval.
075       *             
076       * @param to the ending point of this interval.  The ending
077       *           point is <i>not</i> included in the interval.
078       */
079      public TimeInterval(Date from, Date to)
080      {
081        setInterval(from, to);
082      }
083      
084      /**
085       *  Resets this interval to its initial state.
086       *  <p>
087       *  A reset interval has the same state as one newly created by
088       *  the {@link #TimeInterval() no-argument constructor}.</p> 
089       */
090      public void reset()
091      {
092        start.setTime(DEFAULT_START_TIME);
093        end.setTime(DEFAULT_END_TIME);
094      }
095    
096      //===========================================================================
097      // GETTING & SETTING THE PROPERTIES
098      //===========================================================================
099    
100      /**
101       * Sets the starting and ending points of this interval.
102       * <p>
103       * By convention, the earlier date should be passed as
104       * the {@code from} parameter.  However, this method will
105       * correct the situation where {@code from} is later than
106       * {@code to}, and
107       * use the earlier of the two parameters as the starting
108       * point of this interval.  The later of the two
109       * times will be used as the ending point.
110       * Note that this range goes up to, but does not include,
111       * the ending point.  That is, this interval is half-open,
112       * with the ending point excluded from the interval.</p>
113       * <p>
114       * If either parameter is <i>null</i>, a
115       * {@code IllegalArgumentException} will be thrown.</p>
116       * <p>
117       * Note that this method makes copies of the parameters; it
118       * does not maintain a reference to either parameter.  This
119       * is done in order to maintain the integrity of the relationship
120       * between the starting and ending points of this interval.</p>
121       * 
122       * @param from the starting point of this interval.  The starting
123       *             point is included in the interval.
124       *             
125       * @param to the ending point of this interval.  The ending
126       *           point is <i>not</i> included in the interval.
127       */
128      public void set(Date from, Date to)
129      {
130        setInterval(from, to);
131      }
132      
133      /**
134       * Sets the endpoints of this interval based on {@code intervalText}.
135       * If {@code intervalText} is <i>null</i> or <tt>""</tt> (the empty string),
136       * the {@link #reset()} method is called.  Otherwise, the parsing is
137       * delegated to {@link #parse(String, String, String)}.  See that method
138       * for details related to parsing.
139       */
140      public void set(String intervalText, String dateFormat, String separator)
141      {
142        if (intervalText == null || intervalText.equals(""))
143        {
144          reset();
145        }
146        else
147        {
148          try {
149            parseInterval(intervalText, dateFormat, separator);
150          }
151          catch (IllegalArgumentException ex) {
152            //If the parseInterval method threw an exception it never reached
153            //the point of updating this interval, so we don't need to restore
154            //to pre-parsing values.
155            throw ex;
156          }
157        }
158      }
159      
160      public void set(String iso8601Interval)
161      {
162        if (iso8601Interval == null || iso8601Interval.equals(""))
163        {
164          reset();
165        }
166        else
167        {
168          try {
169            parseInterval(iso8601Interval, ISO8601_DATE_TIME_TIMEZONE,
170                                           ISO8601_TIME_INTERVAL_SEPARATOR);
171          }
172          catch (IllegalArgumentException ex) {
173            //If the parseInterval method threw an exception it never reached
174            //the point of updating this interval, so we don't need to restore
175            //to pre-parsing values.
176            throw ex;
177          }
178        }
179      }
180      
181      /** Called from constructor & public set method. */
182      private void setInterval(Date time1, Date time2)
183      {
184        if ((time1 == null) || (time2 == null))
185          throw new IllegalArgumentException("Cannot configure TimeInterval with null Date.");
186        
187        if (time2.before(time1))
188        {
189          start = (Date)time2.clone();
190          end   = (Date)time1.clone();
191        }
192        else
193        {
194          start = (Date)time1.clone();
195          end   = (Date)time2.clone();
196        }
197      }
198    
199      /**
200       * Returns a copy of this interval's starting time.
201       * Note that the start time is included in this interval.
202       * 
203       * @return a copy of this interval's starting time.
204       * 
205       * @see #set(Date, Date)
206       */
207      public Date getStart()
208      {
209        return (Date)start.clone();
210      }
211    
212      /**
213       * Returns a copy of this interval's ending time.
214       * Note that the end time is <i>not</i> included in this interval.
215       * 
216       * @return a copy of this interval's ending time.
217       * 
218       * @see #set(Date, Date)
219       */
220      public Date getEnd()
221      {
222        return (Date)end.clone();
223      }
224    
225      //===========================================================================
226      // DERIVED QUERIES
227      //===========================================================================
228    
229      /**
230       * Returns the time that is midway between the endpoints of this interval.
231       * @return the center of this interval.
232       */
233      public Date getCenterTime()
234      {
235        double center = (double)(end.getTime() + start.getTime()) / 2.0;
236        
237        return new Date(Math.round(center));
238      }
239      
240      /**
241       * Returns the time duration represented by this interval.
242       * @return the time duration represented by this interval.
243       */
244      public TimeDuration getDuration()
245      {
246        TimeDuration td = new TimeDuration();
247        
248        td.set(new BigDecimal(end.getTime() - start.getTime()),
249               TimeUnits.MILLISECOND);
250        
251        return td;
252      }
253    
254      /**
255       * Returns <i>true</i> if {@code time} is contained in this interval.
256       * <p>
257       * Note that this interval is half-open; it does not include its
258       * ending point.</p>
259       * 
260       * @param time the time to be tested for containment.
261       * 
262       * @return <i>true</i> if {@code time} is contained in this interval.
263       */
264      public boolean contains(Date time)
265      {
266        return (time.equals(start) || time.after(start)) && time.before(end);
267      }
268      
269      /**
270       * Returns <i>true</i> if this interval contains {@code other}.
271       * <p>
272       * Note that an interval that is equal to this one is <i>not</i>
273       * contained by this one.  The best analogy is that of a rigid box
274       * with infinitely thin walls: a box that is exactly the same as
275       * another cannot fit inside it.</p>
276       * 
277       * @param other an interval that might be contained by this one.
278       * 
279       * @return <i>true</i> if this interval contains {@code other}.
280       */
281      public boolean contains(TimeInterval other)
282      {
283        if (this.equals(other))
284          return false;
285        else
286          return (start.equals(other.start) || start.before(other.start))  &&
287                 (end.equals(other.end) || end.after(other.end));
288      }
289      
290      /**
291       * Returns <i>true</i> if the {@code other} interval contains this one.
292       * <p>
293       * See {@link #contains(TimeInterval)} for the definition of containment.</p>
294       * 
295       * @param other a time interval that might contain this one.
296       * 
297       * @return <i>true</i> if the {@code other} interval contains this one.
298       */
299      public boolean isContainedBy(TimeInterval other)
300      {
301        return other.contains(this);
302      }
303    
304      /**
305       * Returns <i>true</i> if this interval ends before the {@code other}
306       * one starts.
307       * @param other an interval that might come after this one.
308       * @return <i>true</i> if this interval ends before {@code other} starts.
309       */
310      public boolean isBefore(TimeInterval other)
311      {
312        //Remember that the starting point of an interval is IN the interval,
313        //while the ending point is OUTSIDE the interval.  This means that
314        //if other has a start that equals this interval's end, it is after
315        //this one.
316        
317        return isBefore(other.start);
318      }
319      
320      /**
321       * Returns <i>true</i> if this interval starts after the {@code other}
322       * one ends.
323       * @param other an interval that might come after this one.
324       * @return <i>true</i> if this interval starts after {@code other} ends.
325       */
326      public boolean isAfter(TimeInterval other)
327      {
328        //Remember that the starting point of an interval is IN the interval,
329        //while the ending point is OUTSIDE the interval.  This means that
330        //if other has a start that equals this interval's end, it is after
331        //this one.
332        
333        //Don't use isAfter(Date) because other.end is NOT actually in the
334        //other interval.  We would need to pass other.end less epsilon.
335        return this.start.after(other.end) || this.start.equals(other.end);
336      }
337    
338      /**
339       * Returns <i>true</i> if this interval ends before the given time.
340       * @param time a point in time that might come after this interval.
341       * @return <i>true</i> if this interval ends before the given time.
342       */
343      public boolean isBefore(Date time)
344      {
345        //Remember that the ending point of an interval is OUTSIDE the interval.
346        //This means that if time is exactly equal to the ending point, it
347        //is after the interval.
348        
349        return this.end.before(time) || this.end.equals(time);
350      }
351      
352      /**
353       * Returns <i>true</i> if this interval starts after the given time.
354       * @param time a point in time that might come after this interval.
355       * @return <i>true</i> if this interval starts after the given time.
356       */
357      public boolean isAfter(Date time)
358      {
359        //Remember that the starting point of an interval is IN the interval.
360        //This means that if time is exactly equal to the starting point,
361        //it is NOT before the interval.
362        
363        return this.start.after(time);
364      }
365      
366      /**
367       * Returns <i>true</i> if this interval and the {@code other} form a contiguous
368       * non-overlapping time interval.  Since a time interval is half open, this
369       * method returns <i>true</i> when either this interval starts at the same time
370       * the other one ends, or ends at the same time the other starts.
371       * 
372       * @param other an interval that might be contiguous with this one.
373       * 
374       * @return <i>true</i> if this interval and the {@code other} form a contiguous
375       *         non-overlapping time interval.
376       */
377      public boolean isContiguousWith(TimeInterval other)
378      {
379        return this.start.equals(other.end) || this.end.equals(other.start);
380      }
381      
382      /**
383       * Returns <i>true</i> if this interval overlaps the {@code other} interval.
384       * <p>
385       * Note that equal intervals overlap, as do intervals that have a
386       * container-contained relationship.</p>
387       * 
388       * @param other an interval that might overlap this one.
389       * 
390       * @return <i>true</i> if this interval overlaps the {@code other} interval.
391       */
392      public boolean overlaps(TimeInterval other)
393      {
394        TimeInterval early, late;
395        
396        //This bit of ordering makes the subsequent if statement easier to read
397        if (other.start.before(this.start))
398        {
399          early = other;
400          late  = this;
401        }
402        else
403        {
404          early = this;
405          late  = other;
406        }
407        
408        //Remember that the starting point of an interval is IN the interval,
409        //while the ending point is OUTSIDE the interval.  This means that
410        //if the starting point of one interval is the same as the ending
411        //point of another, they do NOT overlap.
412        
413        return late.start.before(early.end);
414      }
415    
416      /**
417       * Returns <i>true</i> if this interval is in its default state,
418       * no matter how it got there.
419       * <p>
420       * An interval is in its <i>default state</i> if both its endpoints
421       * are the same as those of an interval newly created via
422       * the {@link #TimeInterval() no-argument constructor}.
423       * An interval whose most recent update came via the
424       * {@link #reset() reset} method is also in its default state.</p>
425       * 
426       * @return <i>true</i> if this interval is in its default state.
427       */
428      public boolean isInDefaultState()
429      {
430        return start.getTime() == DEFAULT_START_TIME &&
431               end.getTime()   == DEFAULT_END_TIME;
432      }
433    
434      //===========================================================================
435      // CONVERSION TO, AND EXPRESSION IN, OTHER FORMS
436      //===========================================================================
437    
438      /**
439       * Returns two new intervals that were formed by splitting this one at the
440       * given point.  This interval is not altered by this method.
441       * <p>
442       * <b>Scenario One.</b> If this interval contains {@code pointOfSplit}, then
443       * the first interval in the array runs from this interval's starting point
444       * to {@code pointOfSplit}, and the second interval runs from 
445       * {@code pointOfSplit} to this interval's ending point.</p>
446       * <p>
447       * <b>Scenario Two.</b> If this interval does not contain
448       * {@code pointOfSplit}, then the first interval in the array will be equal
449       * to this one, and the second interval will be a zero length interval whose
450       * starting and ending endpoints are both {@code pointOfSplit}.</p>
451       * 
452       * @param pointOfSplit the point at which to split this interval in two.
453       * 
454       * @return an array of two intervals whose combined length equals this
455       *         interval's length.
456       */
457      public TimeInterval[] split(Date pointOfSplit)
458      {
459        TimeInterval[] result = new TimeInterval[2];
460        
461        result[0] = new TimeInterval();
462        result[1] = new TimeInterval();
463        
464        if (this.contains(pointOfSplit))
465        {
466          result[0].set(this.start, pointOfSplit);
467          result[1].set(pointOfSplit, this.end);
468        }
469        else //can't use point to split, as its not in this interval
470        {
471          result[0].set(this.start, this.end);
472          result[1].set(pointOfSplit, pointOfSplit);
473        }
474        
475        return result;
476      }
477    
478      /**
479       * Returns a new time interval that exactly plugs the gap between this
480       * interval and the {@code other}.
481       * <p>
482       * If this interval is contiguous with, or overlaps, {@code other},
483       * <i>null</i> is returned.  Otherwise, a new interval is created
484       * and valued such that it fills the gap between this interval and
485       * the other.  Note that the gap is filled regardless of whether
486       * {@code other} is before or after this interval.</p>
487       * 
488       * @param other an interval that might not overlap, or be contiguous with,
489       *              this interval, and for which a gap-filling interval is sought.
490       *              
491       * @return a new interval that fills the gap between this one and
492       *         {@code other}, or <i>null</i> if there is no gap.
493       */
494      public TimeInterval makeGapFiller(TimeInterval other)
495      {
496        TimeInterval gapFiller = null;
497        
498        //Test for gap
499        if (this.isAfter(other) || this.isBefore(other))
500        {
501          Date gapStart = this.end.before(other.end)    ? this.end   : other.end;
502          Date gapEnd   = this.start.after(other.start) ? this.start : other.start;
503          
504          gapFiller = new TimeInterval(gapStart, gapEnd);
505        }
506    
507        return gapFiller;
508      }
509      
510      //===========================================================================
511      // PARSING
512      //===========================================================================
513    
514      /**
515       * Creates a new time interval based on the parameter text.
516       * <p>
517       * This is a convenience method that is equivalent to:<pre>
518       *   parse(iso8601Interval, FormatString.ISO8601_DATE_TIME_TIMEZONE,
519       *                          FormatString.ISO8601_TIME_INTERVAL_SEPARATOR);</pre>
520       * 
521       * @param iso8601Interval the text to be parsed and converted into a time
522       *        interval.
523       *
524       * @return a new time interval based on {@code iso8601Interval}.  If
525       *         {@code iso8601Interval} is <i>null</i> or <tt>""</tt> (the empty
526       *         string), a new time interval of length zero is returned.
527       * 
528       * @see #parse(String, String, String)
529       */
530      public static TimeInterval parse(String iso8601Interval)
531      {
532        return parse(iso8601Interval, ISO8601_DATE_TIME_TIMEZONE,
533                                      ISO8601_TIME_INTERVAL_SEPARATOR);
534      }
535    
536      /**
537       * Creates a new time interval by parsing {@code interval} with the given
538       * formatter and separator.  The general form is
539       * <tt>StartDateSeparatorEndDate</tt>, with the particulars of the date
540       * format determined by {@code dateFormat}.
541       * 
542       * @param interval the text to be parsed and converted into a time
543       *        interval.  If this value is <i>null</i> or <tt>""</tt> (the empty
544       *        string), a new time interval of length zero is returned.
545       *         
546       * @param dateFormat the text that determines the format of the start and end
547       *                   dates in {@code interval}.  These are the same formats
548       *                   used in {@link java.text.SimpleDateFormat}.  A list of
549       *                   ISO 8601 format strings may be found in
550       *                   {@link edu.nrao.sss.util.FormatString}.
551       *                   
552       * @param separator the text that separates the start date from the end date
553       *                  in {@code interval}.
554       *                  
555       * @return a new time interval based on the text of {@code interval}.
556       * 
557       * @throws IllegalArgument if {@code interval} cannot be parsed using
558       *                         {@code dateFormat} and {@code separator}.
559       */
560      public static TimeInterval parse(String interval, String dateFormat,
561                                       String separator)
562      {
563        TimeInterval newInterval = new TimeInterval();
564        
565        //null & "" are permissible
566        if ((interval != null) && !interval.equals(""))
567        {
568          try {
569            newInterval.parseInterval(interval, dateFormat, separator);
570          }
571          catch (IllegalArgumentException ex) {
572            throw ex;
573          }
574        }
575        
576        return newInterval;
577      }
578    
579      /** Does the actual parsing. */
580      private void parseInterval(String interval,
581                                 String dateFormat,
582                                 String separator)
583      {
584        String errMsg = null;
585    
586        //Find the endpoints of the interval
587        String[] endPoints = interval.split(separator, -1);
588    
589        if (endPoints.length == 2)
590        {
591          //Parse the endpoints and set the interval
592          SimpleDateFormat parser = new SimpleDateFormat(dateFormat);
593    
594          try
595          {
596            setInterval(parser.parse(endPoints[0]), parser.parse(endPoints[1]));
597          }
598          catch (ParseException ex)
599          {
600            errMsg = ex.getMessage();
601          }
602          
603        }
604        else //bad # of endpoints
605        {
606          errMsg = "Found " + endPoints.length + " endpoints. There should be 2.";
607        }
608    
609        //Error message
610        if (errMsg != null)
611        {
612          errMsg = errMsg + " [Params: interval=" + interval + ", dateFormat=" +
613                   dateFormat + ", separator=" + separator + "]";
614          
615          throw new IllegalArgumentException(errMsg);
616        }
617      }
618    
619      //===========================================================================
620      // UTILITY METHODS
621      //===========================================================================
622    
623      /**
624       *  Creates a text representation of this time interval.
625       *  The format of the returned string is the full ISO 8601
626       *  representation of a time interval and looks something like this:
627       *  <tt>yyyy-mm-ddThh:mm:ss.sss+zzzz/yyyy-mm-ddThh:mm:ss.sss+zzzz</tt>,
628       *  where the 'T' and '/' are literals and the '+' is either a '+' or '-'.
629       *  <p>
630       *  A call to this method is equivalent to:<pre>
631       *    toString(FormatString.ISO8601_DATE_TIME_TIMEZONE,
632       *             FormatString.ISO8601_TIME_INTERVAL_SEPARATOR);</pre></p>
633       */
634      public String toString()
635      {
636        return toString(ISO8601_DATE_TIME_TIMEZONE,
637                        ISO8601_TIME_INTERVAL_SEPARATOR);
638      }
639      
640      /**
641       * Returns a text representation of this interval based on the
642       * formatting parameters.  For a description of these parameters,
643       * see {@link #parse(String, String, String)}.
644       */
645      public String toString(String dateFormat, String separator)
646      {
647        SimpleDateFormat df = new SimpleDateFormat(dateFormat);
648    
649        StringBuilder buff = new StringBuilder();
650        
651        buff.append(df.format(start)).append(separator).append(df.format(end));
652        
653        return buff.toString();
654      }
655    
656      /**
657       *  Returns a time interval that is equal to this one.
658       *  <p>
659       *  If anything goes wrong during the cloning procedure,
660       *  a {@code RuntimeException} will be thrown.</p>
661       */
662      public TimeInterval clone()
663      {
664        TimeInterval clone = null;
665    
666        try
667        {
668          //This line takes care of the primitive fields properly
669          clone = (TimeInterval)super.clone();
670          
671          clone.start = (Date)this.start.clone();
672          clone.end   = (Date)this.end.clone();
673        }
674        catch (Exception ex)
675        {
676          throw new RuntimeException(ex);
677        }
678        
679        return clone;
680      }
681    
682      /** Returns <i>true</i> if {@code o} is equal to this time interval. */
683      public boolean equals(Object o)
684      {
685        //Quick exit if o is this
686        if (o == this)
687          return true;
688        
689        //Quick exit if o is null
690        if (o == null)
691          return false;
692        
693        //Quick exit if classes are different
694        if (!o.getClass().equals(this.getClass()))
695          return false;
696        
697        TimeInterval otherTime = (TimeInterval)o;
698        
699        return otherTime.start.equals(this.start) &&
700               otherTime.end.equals(this.end);
701      }
702    
703      /** Returns a hash code value for this time interval. */
704      public int hashCode()
705      {
706        //Taken from the Effective Java book by Joshua Bloch.
707        //The constants 17 & 37 are arbitrary & carry no meaning.
708        int result = 17;
709        
710        result = 37 * result + start.hashCode();
711        result = 37 * result + end.hashCode();
712        
713        return result;
714      }
715      
716      /**
717       * Compares this interval to {@code other} for order.
718       * <p>
719       * One interval is deemed to be "less than" the other if it begins
720       * before the other.  In the case that two intervals start at the
721       * same time, the one that ends first is considered to be less
722       * than the other.</p>
723       * 
724       * @param other the time interval to which this one is compared.
725       * 
726       * @return a negative integer, zero, or a positive integer as this interval
727       *         is less than, equal to, or greater than the other interval.
728       */
729      public int compareTo(TimeInterval other)
730      {
731        int answer = this.start.compareTo(other.start);
732        
733        //If the intervals start at the same time, compare the end times
734        if (answer == 0)
735          answer = this.end.compareTo(other.end);
736        
737        return answer;
738      }
739      
740      /*public static void main(String[] args)
741      {
742        TimeInterval time = new TimeInterval();
743    
744        time.set(new Date(), new Date());
745        
746        System.out.println(time.toString());
747        System.out.println(time.toString(FormatString.ISO8601_CALENDAR_DATE, " - "));
748        System.out.println(time.toString(FormatString.ISO8601_DATE_TIME, " - "));
749        System.out.println(time.toString(FormatString.ISO8601_HOUR_MIN_SEC, " - "));
750        System.out.println(time.toString(FormatString.ISO8601_HOUR_MIN_SEC_MILLI, " - "));
751        System.out.println(time.toString());
752        System.out.println();
753    
754        try {
755          time = TimeInterval.parse("2006-07-03T01:02:03.04-0600/2006-09-16T01:23:45.678-0600");
756          System.out.println(time);
757        }
758        catch (Exception ex)  { System.out.println(ex.getMessage()); }
759    
760        try {
761          time = TimeInterval.parse("2006-07-03T01:02:03.04-0600 TO 2006-09-16T01:23:45.678-0600", FormatString.ISO8601_CALENDAR_DATE, " TO ");
762          System.out.println(time);
763        }
764        catch (Exception ex)  { System.out.println(ex.getMessage()); }
765    
766        try {
767          time = TimeInterval.parse("2006-07-03T01:02:03.04-0600 TO 2006-09-16T01:23:45.678-0600", FormatString.ISO8601_DATE_TIME, " TO ");
768          System.out.println(time);
769        }
770        catch (Exception ex)  { System.out.println(ex.getMessage()); }
771    
772        try {
773          time = TimeInterval.parse("01:02:03.04 TO 01:23:45.678", FormatString.ISO8601_HOUR_MIN_SEC, " TO ");
774          System.out.println(time);
775        }
776        catch (Exception ex)  { System.out.println(ex.getMessage()); }
777    
778        try {
779          time = TimeInterval.parse("01:02:03.04 TO 01:23:45.678", FormatString.ISO8601_HOUR_MIN_SEC_MILLI, " TO ");
780          System.out.println(time);
781        }
782        catch (Exception ex)  { System.out.println(ex.getMessage()); }
783        
784        
785        try {
786          time = TimeInterval.parse("2006-07-03T01:02:03.04-0600/2006-09-16T01:23:45.678-0600");
787          System.out.println(time);
788        }
789        catch (Exception ex)  { System.out.println(ex.getMessage()); }
790        System.out.println();
791        System.out.println(time.toString());
792        System.out.println(time.toString(FormatString.ISO8601_HOUR_MIN_SEC, "..."));
793        System.out.println(time.toString(FormatString.ISO8601_HOUR_MIN_SEC_MILLI, " - "));
794        System.out.println(time.toString(FormatString.ISO8601_CALENDAR_DATE, " until "));
795        System.out.println(time.toString(FormatString.ISO8601_DATE_TIME, "~"));
796        System.out.println(time.toString(FormatString.ISO8601_DATE_TIME_TIMEZONE, " all the way to "));
797    
798        System.out.println();
799        time.set("2006-11-05 to 2007-01-01", FormatString.ISO8601_CALENDAR_DATE, " to ");
800        System.out.println(time);
801      }*/
802    }