001    package edu.nrao.sss.measure;
002    
003    import java.math.BigDecimal;
004    import java.math.RoundingMode;
005    import java.util.Calendar;
006    import java.util.Date;
007    import java.util.GregorianCalendar;
008    import java.util.TimeZone;
009    
010    import static edu.nrao.sss.math.MathUtil.MC_INTERM_CALCS;
011    
012    import edu.nrao.sss.util.StringUtil;
013    
014    /**
015     * A Julian Date.
016     * <p/>
017     * <a href="http://en.wikipedia.org/wiki/Julian_date">Wikipedia</a>'s
018     * definition:
019     * <blockquote><i>
020     * The <b>Julian day</b> or <b>Julian day number (JDN)</b>
021     * is the integer number of days that
022     * have elapsed since the initial epoch defined as noon Universal Time (UT)
023     * Monday, January 1, 4713 BC in the proleptic Julian calendar...The <b>Julian
024     * date (JD)</b> is a continuous count of days and fractions elapsed since the
025     * same initial epoch.
026     * </i></blockquote>
027     * <p>
028     * As is the case with the {@link java.util.Date} class, this class models
029     * a date/time concept, not a pure date.</p>
030     * <p>
031     * <b>Note on the use of the Gregorian Calendar</b><br/>
032     * This class uses java's {@link java.util.GregorianCalendar} class to handle
033     * historical oddities in day tracking.  That class sets the year for the
034     * switch over from the Julian to the Gregorian calendar as 1582; England
035     * and its colonies, including America, did not convert until 1752.  That
036     * class also uses the Julian calendar for dates prior to the change to
037     * the Gregorian, and then a proleptic Julian for dates prior to 1 March 4 AD.
038     * What this means for us is that the conversion of a Julian Date to the
039     * more familiar form may not produce the expected results for dates prior
040     * to 1753.  Since our main use for this class is for more recent dates, and
041     * since this class is backed by java's standard calendar, we should not
042     * be overly concerned with conversion of dates in the distant past.</p>
043     * <p> 
044     * <b>Note on Precision</b><br/>
045     * The java {@link java.util.Date Date} class has a resolution of one
046     * millisecond (1ms).  This class employs techniques to keep the resolution
047     * of this class as close to that of <tt>Date</tt> as possible.  The goal
048     * is to make code like this work without complaint.</p>
049     * <pre>
050     *   Date       origDate  = someFuncThatReturnsDate();
051     *   JulianDate jd1       = new JulianDate(origDate);
052     *   JulianDate jd2       = new JulianDate(jd1.value());
053     *   Date       otherDate = jd2.toDate();
054     *   
055     *   if (!origDate.equals(otherDate))
056     *     complainVigorously();
057     * </pre>
058     * <p>
059     * Without taking pains in the class to make certain the above works
060     * properly, the two times could have been off by a millisecond.</p>
061     * <p>
062     * <b>Version Info:</b>
063     * <table style="margin-left:2em">
064     *   <tr><td>$Revision: 1578 $</td></tr>
065     *   <tr><td>$Date: 2008-09-24 13:34:32 -0600 (Wed, 24 Sep 2008) $</td></tr>
066     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
067     * </table></p>
068     * 
069     * @author David M. Harland
070     * @since 2007-08-01
071     */
072    //TODO deal w/ infinity
073    public class JulianDate
074      implements Cloneable, Comparable<JulianDate>
075    {
076      private static final BigDecimal JULIAN_DATE_AT_MJD_ZERO = new BigDecimal("2400000.5");
077      private static final Date          UTC_DATE_AT_MJD_ZERO;
078      private static final long      MILLISECONDS_AT_MJD_ZERO;
079      
080      //If we ever want to do threading, these  calendar variables
081      //could be changed to instance variables.
082      private static final GregorianCalendar GREGORIAN_UTC_CALENDAR =
083        new GregorianCalendar(TimeZone.getTimeZone("GMT+0"));
084      
085      private static final Calendar LOCAL_CALENDAR = Calendar.getInstance();
086      private static final Calendar CALENDAR       = Calendar.getInstance();
087      
088      static
089      {
090        //November 17, 1858 (rem: java uses Jan=0,...,Nov=10,...)
091        GREGORIAN_UTC_CALENDAR.clear(); //Lets us set milliseconds to zero.
092        GREGORIAN_UTC_CALENDAR.set(1858, 10, 17, 0, 0, 0);
093        
094        UTC_DATE_AT_MJD_ZERO     = GREGORIAN_UTC_CALENDAR.getTime();
095        MILLISECONDS_AT_MJD_ZERO = UTC_DATE_AT_MJD_ZERO.getTime();
096      }
097      
098      private Date        gd;
099      private BigDecimal  jd;
100      private BigDecimal mjd;
101      
102      /**
103       * Creates a new instance for the current time.
104       * 
105       * See the {@link JulianDate class comments} for an explanation of
106       * the use of the {@link java.util.Date} class.
107       */ 
108      public JulianDate()
109      {
110        this(new Date());
111      }
112      
113      /**
114       * Creates a new instance for the given Gregorian calendar date.
115       * 
116       * See the {@link JulianDate class comments} for an explanation of
117       * the use of the {@link java.util.Date} class.
118       */ 
119      public JulianDate(Date gregorianCalendarDate)
120      {
121        setValues(gregorianCalendarDate);
122      }
123      
124      /**
125       * Creates a new instance for the given Julian Date.
126       * <p>
127       * The actual value used by this object may differ from the parameter value
128       * by as much as 10<sup>-7</sup>.  This is because this class attempts to
129       * mimic the millisecond resolution of java's <tt>Date</tt> class.
130       * See the {@link JulianDate class comments} for a longer explanation.</p>
131       */ 
132      public JulianDate(BigDecimal julianDate)
133      {
134        setValues(julianDate);
135      }
136      
137      /**
138       * Creates a new instance for the given Julian Date.
139       * <p>
140       * The actual value used by this object may differ from the parameter value
141       * by as much as 10<sup>-7</sup>.  This is because this class attempts to
142       * mimic the millisecond resolution of java's <tt>Date</tt> class.
143       * See the {@link JulianDate class comments} for a longer explanation.</p>
144       * 
145       * @since 2008-08-14
146       */ 
147      public JulianDate(String julianDate)
148      {
149        setValues(julianDate);
150      }
151      
152      /** Creates a new instance for the given Julian Day Number. */ 
153      public JulianDate(int julianDayNumber)
154      {
155        setValues(julianDayNumber);
156      }
157      
158      /**
159       * Returns a new <tt>JulianDate</tt> whose time is set to the instance
160       * that MJD equals zero.
161       * 
162       * @return
163       *   a <tt>JulianDate</tt> where the {@link #modifiedValue()} is zero.
164       *   
165       * @since
166       *   2008-09-23
167       */
168      public static JulianDate makeMjdZero()
169      {
170        return new JulianDate(JULIAN_DATE_AT_MJD_ZERO);
171      }
172      
173      private static JulianDate CLONE_BASE = null;
174      
175      /**
176       * Returns a new <tt>JulianDate</tt> for the given MJD.
177       * 
178       * @param modifiedJulianDate
179       *   the intial MJD for this Julian Date.
180       *   
181       * @return
182       *   a new <tt>JulianDate</tt> for the given MJD.
183       *   
184       * @since 2008-09-23
185       */
186      public static JulianDate makeFromMjd(BigDecimal modifiedJulianDate)
187      {
188        if (CLONE_BASE == null)
189          CLONE_BASE = new JulianDate();
190        
191        JulianDate jd = CLONE_BASE.clone();
192        
193        return jd.setFromMjd(modifiedJulianDate);
194      }
195      
196      /**
197       * Returns a new <tt>JulianDate</tt> for the given MJD.
198       * 
199       * @param modifiedJulianDate
200       *   the intial MJD for this Julian Date.
201       *   
202       * @return
203       *   a new <tt>JulianDate</tt> for the given MJD.
204       *   
205       * @since 2008-09-23
206       */
207      public static JulianDate makeFromMjd(String modifiedJulianDate)
208      {
209        if (CLONE_BASE == null)
210          CLONE_BASE = new JulianDate();
211        
212        JulianDate jd = CLONE_BASE.clone();
213        
214        return jd.setFromMjd(modifiedJulianDate);
215      }
216      
217      //============================================================================
218      // SETTING THE DATE
219      //============================================================================
220    
221      /**
222       * Sets this date to the current time.
223       * 
224       * See the {@link JulianDate class comments} for an explanation of
225       * the use of the {@link java.util.Date} class.
226       */ 
227      public JulianDate set()
228      {
229        return set(new Date());
230      }
231      
232      /**
233       * Sets this date to the given time.
234       * 
235       * See the {@link JulianDate class comments} for an explanation of
236       * the use of the {@link java.util.Date} class.
237       * 
238       * @param gregorianCalendarDate the common form of a date.
239       * 
240       * @return this date.
241       */ 
242      public JulianDate set(Date gregorianCalendarDate)
243      {
244        setValues(gregorianCalendarDate);
245        return this;
246      }
247      
248      /**
249       * Sets this date to the given time.
250       * 
251       * @param julianDayNumber an integral representation of a Julian Date.
252       * 
253       * @return this date.
254       */
255      public JulianDate set(int julianDayNumber)
256      {
257        setValues(julianDayNumber);
258        return this;
259      }
260      
261      /**
262       * Sets this date to the given time.
263       * <p>
264       * The actual value used by this object may differ from the parameter value
265       * by as much as 10<sup>-7</sup>.  This is because this class attempts to
266       * mimic the millisecond resolution of java's <tt>Date</tt> class.
267       * See the {@link JulianDate class comments} for a longer explanation.</p>
268       * 
269       * @param julianDate
270       *   a real number representation of a Julian Date.
271       * 
272       * @return
273       *   this date.
274       */
275      public JulianDate set(BigDecimal julianDate)
276      {
277        setValues(julianDate);
278        return this;
279      }
280      
281      /**
282       * Sets this date to the given time.
283       * <p>
284       * The actual value used by this object may differ from the parameter value
285       * by as much as 10<sup>-7</sup>.  This is because this class attempts to
286       * mimic the millisecond resolution of java's <tt>Date</tt> class.
287       * See the {@link JulianDate class comments} for a longer explanation.</p>
288       * 
289       * @param julianDate
290       *   a real number representation, in text form, of a Julian Date.
291       * 
292       * @return
293       *   this date.
294       * 
295       * @since 2008-08-14
296       */
297      public JulianDate set(String julianDate)
298      {
299        setValues(julianDate);
300        return this;
301      }
302      
303      /**
304       * Sets this date to the given time.
305       * <p>
306       * The actual value used by this object may differ from the parameter value
307       * by as much as 10<sup>-7</sup>.  This is because this class attempts to
308       * mimic the millisecond resolution of java's <tt>Date</tt> class.
309       * See the {@link JulianDate class comments} for a longer explanation.</p>
310       * 
311       * @param modifiedJulianDate
312       *   a real number representation of a Modified Julian Date.
313       * 
314       * @return
315       *   this date.
316       * 
317       * @since 2008-09-23
318       */
319      public JulianDate setFromMjd(BigDecimal modifiedJulianDate)
320      {
321        BigDecimal julianDate = modifiedJulianDate.add(JULIAN_DATE_AT_MJD_ZERO);
322        return set(julianDate);
323      }
324    
325      /**
326       * Sets this date to the given time.
327       * <p>
328       * The actual value used by this object may differ from the parameter value
329       * by as much as 10<sup>-7</sup>.  This is because this class attempts to
330       * mimic the millisecond resolution of java's <tt>Date</tt> class.
331       * See the {@link JulianDate class comments} for a longer explanation.</p>
332       * 
333       * @param modifiedJulianDate
334       *   a real number representation, in text form, of a Modified Julian Date.
335       *   
336       * @return
337       *   this date.
338       * 
339       * @since 2008-09-23
340       */
341      public JulianDate setFromMjd(String modifiedJulianDate)
342      {
343        return setFromMjd(new BigDecimal(modifiedJulianDate));
344      }
345      
346      //============================================================================  
347      // ARITHMETIC
348      //============================================================================  
349      
350      /**
351       * Increments this date by the whole and fractional {@code days}.
352       * @param days the number of whole and fractional days to add to this date.
353       * @return this date, after the addition.
354       */
355      public JulianDate add(BigDecimal days)
356      {
357        setValues(jd.add(days));
358        return this;
359      }
360      
361      /**
362       * Increments this date by the given amount of time.
363       * @param duration the amount by which to increment this date.
364       * @return this date, after the addition.
365       */
366      public JulianDate add(TimeDuration duration)
367      {
368        return add(duration.toUnits(TimeUnits.DAY));
369      }
370      
371      /**
372       * Decrements this date by the whole and fractional {@code days}.
373       * @param days the number of whole and fractional days to subtract
374       *             from this date.
375       * @return this date, after the subtraction.
376       */
377      public JulianDate subtract(BigDecimal days)
378      {
379        setValues(jd.subtract(days));
380        return this;
381      }
382      
383      /**
384       * Decrements this date by the given amount of time.
385       * @param duration the amount by which to decrement this date.
386       * @return this date, after the subtraction.
387       */
388      public JulianDate subtract(TimeDuration duration)
389      {
390        return subtract(duration.toUnits(TimeUnits.DAY));
391      }
392      
393      //============================================================================  
394      // QUERIES
395      //============================================================================  
396    
397      /**
398       * Returns a real number representation of this date.
399       * @return a real number representation of this date.
400       */
401      public BigDecimal value()
402      {
403        return jd;
404      }
405      
406      /**
407       * Returns the Julian Day Number for this date.
408       * This is the integer portion of the
409       * {@link #value() real number representation} of this date.
410       * 
411       * @return the Julian Day Number for this date.
412       */
413      public int dayNumber()
414      {
415        return jd.intValue();
416      }
417      
418      /**
419       * Returns the fractional portion of the value of this date.
420       * <p>
421       * For example, if {@link #value()} returns <tt>2,456,789.0123</tt>,
422       * {@link #dayNumber()} will return <tt>2,456,789</tt> and
423       * this method will return <tt>0.0123</tt>.</p>
424       * 
425       * @return
426       *   the fractional portion of the value of this date.
427       */
428      public BigDecimal fraction()
429      {
430        return jd.subtract(new BigDecimal(dayNumber()));
431      }
432      
433      /**
434       * Returns the real number representation of the Modified
435       * Julian Date (MJD) corresponding this date.
436       * The MJD is 2,400,000.5 days less than the JD.
437       * 
438       * @return the modified julian date.
439       */
440      public BigDecimal modifiedValue()
441      {
442        return mjd;
443      }
444      
445      /**
446       * Returns the Modified Julian Day Number for this date.
447       * This is the integer portion of the
448       * {@link #modifiedValue() real number representation} of
449       * the MJD corresponding to this date.
450       * 
451       * @return the Julian Day Number for this date.
452       */
453      public int modifiedDayNumber()
454      {
455        return mjd.intValue();
456      }
457      
458      /**
459       * Returns the fractional portion of the modified value of this date.
460       * <p>
461       * For example, if {@link #modifiedValue()} returns <tt>56,789.0123</tt>,
462       * {@link #modifiedDayNumber()} will return <tt>56,789</tt> and
463       * this method will return <tt>0.0123</tt>.</p>
464       * 
465       * @return
466       *   the fractional portion of the modified value of this date.
467       */
468      public BigDecimal modifiedFraction()
469      {
470        return mjd.subtract(new BigDecimal(modifiedDayNumber()));
471      }
472    
473      /**
474       * Returns <i>true</i> if this date is earlier than {@code other}.
475       * @param other the date to be tested against this one.
476       * @return <i>true</i> if this date is earlier than {@code other}.
477       */
478      public boolean isBefore(JulianDate other)
479      {
480        return this.compareTo(other) < 0;
481      }
482      
483      /**
484       * Returns <i>true</i> if this date is later than {@code other}.
485       * @param other the date to be tested against this one.
486       * @return <i>true</i> if this date is later than {@code other}.
487       */
488      public boolean isAfter(JulianDate other)
489      {
490        return this.compareTo(other) > 0;
491      }
492      
493      //============================================================================  
494      // TO OTHER FORMS
495      //============================================================================  
496      
497      /**
498       * Returns a date in the Gregorian, Julian, or proleptic Julian calendar
499       * corresponding to this date.  The date returned is a copy of the one used
500       * internally by this object, so subsequent changes to it
501       * will <i>not</i> be reflected herein.
502       * <p>
503       * See the {@link JulianDate class comments} for more information on
504       * the use of the Gregorian and Julian calendars made by this class.</p>
505       * 
506       * @return a date in the proleptic Gregorian calendar.
507       */
508      public Date toDate()
509      {
510        return (Date)gd.clone();
511      }
512      
513      private static final BigDecimal JULIAN_DATE_AT_EPOCH_2000 = new BigDecimal("2451545.0");
514      private static final BigDecimal JULIAN_YEAR_IN_DAYS       = new BigDecimal("365.25");
515      private static final BigDecimal TWO_THOUSAND              = new BigDecimal("2000.0");
516    
517      /**
518       * Returns the
519       * <a href="http://scienceworld.wolfram.com/astronomy/JulianEpoch.html">
520       * Julian Epoch</a> for this date.
521       * <p>
522       * For example, the Julian Epoch for JD 2451545.0 is 2000.0.</p>
523       * 
524       * @return
525       *   the Julian Epoch for this date.
526       * 
527       * @since 2008-09-19
528       */
529      public BigDecimal toEpoch()
530      {
531        return value().subtract(JULIAN_DATE_AT_EPOCH_2000)
532                      .divide(JULIAN_YEAR_IN_DAYS, MC_INTERM_CALCS)
533                      .add(TWO_THOUSAND);
534      }
535      
536      /**
537       * Returns the time of day, in the local time zone, for this Julian Date.
538       * @return the time of day, in the local time zone, for this Julian Date.
539       * 
540       * @since 2008-08-14
541       */
542      public TimeOfDay toTimeOfDayLocal()
543      {
544        return makeTimeOfDay(LOCAL_CALENDAR);
545      }
546    
547      /**
548       * Returns the UTC time of day for this Julian Date.
549       * @return the UTC time of day for this Julian Date.
550       * 
551       * @since 2008-08-14
552       */
553      public TimeOfDay toTimeOfDayUtc()
554      {
555        return makeTimeOfDay(GREGORIAN_UTC_CALENDAR);
556      }
557      
558      /**
559       * Returns the time of day, in the given time zone, for this Julian Date.
560       * 
561       * @param timeZone
562       *   the time zone for which the returne time of day is applicable.
563       *   
564       * @return
565       *   the time of day, in the given time zone, for this Julian Date.
566       * 
567       * @since 2008-08-14
568       */
569      public TimeOfDay toTimeOfDay(TimeZone timeZone)
570      {
571        CALENDAR.setTimeZone(timeZone);
572        return makeTimeOfDay(CALENDAR);
573      }
574      
575      private TimeOfDay makeTimeOfDay(Calendar cal)
576      {
577        TimeOfDay tod = new TimeOfDay();
578        
579        cal.setTime(gd);
580        
581        int hour   = cal.get(Calendar.HOUR_OF_DAY);
582        int minute = cal.get(Calendar.MINUTE);
583        int sec    = cal.get(Calendar.SECOND);
584        int milli  = cal.get(Calendar.MILLISECOND);
585        
586        String second = Integer.toString(sec) + "." + Integer.toString(milli);
587        
588        tod.set(hour, minute, second);
589        
590        return tod;
591      }
592      
593      /* (non-Javadoc)
594       * @see java.lang.Object#toString()
595       */
596      @Override
597      public String toString()
598      {
599        return StringUtil.getInstance().formatNoScientificNotation(jd);
600      }
601      
602      /**
603       * Returns a text representation of this Julian Date.
604       * 
605       * @param minFracDigits the minimum number of places after the decimal point.
606       *                      
607       * @param maxFracDigits the maximum number of places after the decimal point.
608       * 
609       * @return a text representation of this Julian Date.
610       */
611      public String toString(int minFracDigits, int maxFracDigits)
612      {
613        return StringUtil.getInstance().formatNoScientificNotation(jd,
614                                                                   minFracDigits,
615                                                                   maxFracDigits);
616      }
617      
618      //============================================================================  
619      // CALCULATIONS
620      //============================================================================  
621      
622      private void setValues(int julianDayNumber)
623      {
624        jd  = new BigDecimal(julianDayNumber);
625        mjd = jd.subtract(JULIAN_DATE_AT_MJD_ZERO);
626        gd  = calcGregorianDate(jd);
627      }
628      
629      private void setValues(BigDecimal julianDate)
630      {
631        //The Date class has only millisecond resolution.  We will attempt
632        //to keep the resolution of this class about the same.  The important
633        //thing is if we create JD1 w/ a Date, then create JD2 from JD1.value,
634        //then ask JD2 for its date, we get the original date.  That is why
635        //we go through a couple calcs here instead of just one.
636        gd  = calcGregorianDate(julianDate);
637        
638        jd  = calcJulianDate(gd);
639        mjd = jd.subtract(JULIAN_DATE_AT_MJD_ZERO);
640      }
641      
642      private void setValues(Date gregorianCalendarDate)
643      {
644        gd  = gregorianCalendarDate;
645        jd  = calcJulianDate(gd);
646        mjd = jd.subtract(JULIAN_DATE_AT_MJD_ZERO);
647      }
648    
649      private void setValues(String julianDate)
650      {
651        setValues(new BigDecimal(julianDate));
652      }
653      
654      private static final BigDecimal MS_PER_DAY =
655        TimeUnits.DAY.toUnits(TimeUnits.MILLISECOND);
656      
657      private static final BigDecimal DAYS_PER_MS =
658        TimeUnits.MILLISECOND.toUnits(TimeUnits.DAY);
659      
660      //1ms ~ 10^-8 days, thus the "8" below
661      private static final int DAY_SCALE = 8;
662      
663      /**
664       * Calculates a Julian Date for a Gregorian Date using java's
665       * calendar logic and a point in time where both the
666       * JD and UTC times are known.
667       * 
668       * @param gregDate a date in the proleptic Gregorian calendar.
669       * 
670       * @return the Julian Date for {@code gregDate}.
671       */
672      private BigDecimal calcJulianDate(Date gregDate)
673      {
674        //Let the GregorianCalendar class handle leap years and such.
675        //Milliseconds elapsed since (or before) our zero point.
676        BigDecimal deltaMs =
677          new BigDecimal(gregDate.getTime() - MILLISECONDS_AT_MJD_ZERO);
678        
679        //Days elapsed since (or before) our zero point.
680        //(Do we need to deal with leap seconds?)
681        BigDecimal deltaDays = DAYS_PER_MS.multiply(deltaMs);
682        
683        //Convert to roughly millisecond resolution
684        deltaDays = deltaDays.setScale(DAY_SCALE, RoundingMode.HALF_UP);
685        
686        return JULIAN_DATE_AT_MJD_ZERO.add(deltaDays);
687      }
688      
689      /**
690       * Calculates a Gregorian Date for a Julian Date using java's
691       * calendar logic and a point in time where both the
692       * JD and UTC times are known.
693       * 
694       * @param julianDate a Julian Date
695       * 
696       * @return a date in the proleptic Gregorian calendar for {@code gregDate}.
697       */
698      private Date calcGregorianDate(BigDecimal julianDate)
699      {
700        //Days elapsed since (or before) our zero point.
701        BigDecimal deltaDays = julianDate.subtract(JULIAN_DATE_AT_MJD_ZERO);
702        
703        //Milliseconds elapsed since (or before) our zero point.
704        BigDecimal deltaMs = MS_PER_DAY.multiply(deltaDays);
705    
706        long ms = MILLISECONDS_AT_MJD_ZERO + Math.round(deltaMs.doubleValue());
707        
708        GREGORIAN_UTC_CALENDAR.setTimeInMillis(ms);
709        
710        return GREGORIAN_UTC_CALENDAR.getTime();
711      }
712    
713      //============================================================================  
714      // 
715      //============================================================================
716    
717      /**
718       * Returns a copy of this date.
719       * <p>
720       * If anything goes wrong during the cloning procedure,
721       * a {@code RuntimeException} will be thrown.</p>
722       */
723      @Override
724      public JulianDate clone()
725      {
726        JulianDate clone = null;
727    
728        try
729        {
730          //This line takes care of the primitive fields properly
731          clone = (JulianDate)super.clone();
732        }
733        catch (Exception ex)
734        {
735          throw new RuntimeException(ex);
736        }
737        
738        return clone;
739      }
740    
741      /** Returns <i>true</i> if {@code o} is equal to this Julian date. */
742      @Override
743      public boolean equals(Object o)
744      {
745        //Quick exit if o is this
746        if (o == this)
747          return true;
748        
749        //Quick exit if o is null
750        if (o == null)
751          return false;
752        
753        //Quick exit if classes are different
754        if (!o.getClass().equals(this.getClass()))
755          return false;
756        
757        JulianDate other = (JulianDate)o;
758        
759        return compareTo(other) == 0;
760      }
761    
762      /** Returns a hash code value for this Julian date. */
763      @Override
764      public int hashCode()
765      {
766        //Taken from the Effective Java book by Joshua Bloch.
767        //The constants 17 & 37 are arbitrary & carry no meaning.
768        int result = 17;
769        
770        result = 37 * result + jd.hashCode();
771        
772        return result;
773      }
774    
775      /* (non-Javadoc)
776       * @see java.lang.Comparable#compareTo(java.lang.Object)
777       */
778      public int compareTo(JulianDate otherDate)
779      {
780        return this.jd.compareTo(otherDate.jd);
781      }
782    
783      //============================================================================  
784      // 
785      //============================================================================
786      
787      //Here for quick testing
788      /*
789      public static void main(String[] args) throws Exception
790      {
791        JulianDate mjd = JulianDate.makeFromMjd("50000");
792        System.out.println("mjd 50,000          = " + mjd.toDate());
793        
794        Date       date = new Date();
795        JulianDate jd   = new JulianDate(date);
796        
797        System.out.println("java date           = " + date);
798        System.out.println("julian date         = " + jd);
799        System.out.println("jd.toTimeOfDayLocal = " + jd.toTimeOfDayLocal());
800        System.out.println("jd.toTimeOfDayUtc   = " + jd.toTimeOfDayUtc());
801      }
802      */
803      /*
804      public static void main(String[] args) throws Exception
805      {
806        JulianDate jd = new JulianDate("2456789.0123");
807        System.out.println(jd);
808        System.out.println(jd.value());
809        System.out.println(jd.dayNumber());
810        System.out.println(jd.fraction());
811        System.out.println(jd.toEpoch().doubleValue());
812    
813        System.out.println();
814        
815        jd = new JulianDate("2456789.5123");
816        System.out.println(jd);
817        System.out.println(jd.modifiedValue());
818        System.out.println(jd.modifiedDayNumber());
819        System.out.println(jd.modifiedFraction());
820      }
821      */
822      /*
823      public static void main(String[] args) throws Exception
824      {
825        boolean inputIsDate = false;
826        
827        SimpleDateFormat df = new SimpleDateFormat();
828        df.setTimeZone(TimeZone.getTimeZone("GMT"));
829        df.applyPattern("G yyyy MMMM dd HH:mm:ss.S");
830        
831        for (String arg : args)
832        {
833          if (!arg.startsWith("-"))
834          {
835            if (inputIsDate)
836              processDate(arg, df);
837            else
838              processReal(arg, df);
839          }
840          else
841            inputIsDate = arg.equalsIgnoreCase("-utc");
842        }
843      }
844      
845      private static void processDate(String dateText, SimpleDateFormat df)
846        throws Exception
847      {
848        System.out.print("The Julian Date for UTC date/time ");
849        System.out.print(dateText + " is ");
850       
851        Date date = df.parse(dateText);
852        
853        System.out.println(new JulianDate(date));
854      }
855      
856      private static void processReal(String numText, SimpleDateFormat df)
857      {
858        System.out.print("The UTC date/time for JD ");
859        System.out.print(numText + " is ");
860        
861        double jd = Double.parseDouble(numText);
862        
863        System.out.println(df.format(new JulianDate(jd).toDate()));
864      }
865      */
866      /*
867      //Show consequence of millisecond resolution
868      public static void main(String... args) throws Exception
869      {
870        Calendar cal = Calendar.getInstance();
871        BigDecimal currVal=null, priorVal=null;
872        JulianDate jd = new JulianDate();
873        for (int i=1; i <= 20; i++)
874        {
875          jd.set(cal.getTime());
876    
877          currVal = jd.value();
878          System.out.print(currVal.setScale(10, RoundingMode.HALF_UP));
879          System.out.print(", " + cal.getTimeInMillis());
880          
881          System.out.println();
882          
883          if (priorVal != null)
884          {
885            BigDecimal midVal = currVal.add(priorVal).divide(new BigDecimal("2"));
886            jd.set(midVal);
887            System.out.print(jd.value().setScale(10, RoundingMode.HALF_UP));
888            System.out.print(", " + midVal.setScale(10, RoundingMode.HALF_UP));
889            System.out.println();
890          }
891    
892          priorVal = currVal;
893          
894          cal.add(Calendar.MILLISECOND, 1);
895        }
896      }
897      */
898    }