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.TimeZone;
008    
009    import static edu.nrao.sss.math.MathUtil.MC_FINAL_CALC;
010    import static edu.nrao.sss.math.MathUtil.MC_INTERM_CALCS;
011    
012    /**
013     * Local sidereal time (LST).
014     * See <a href="http://en.wikipedia.org/wiki/Sidereal_time">Wikipedia</a>
015     * or other places on the web for more information.
016     * <p>
017     * Note that this LST functions as a date/time, like the
018     * {@link java.util.Date} class, not as a dateless time, like the
019     * {@link TimeOfDay} class.  This means, for example, that two
020     * LSTs of 12:34:56.789 on different days are <i>not</i> equal.
021     * Clients who wish to work only with the LST time-of-day may
022     * get a <tt>TimeOfDay</tt> instance from the
023     * {@link #toTimeOfDay()} method.</p>
024     * <p>
025     * Currently this class represents local <i>mean</i> sidereal time;
026     * it could be upgraded to allow for the calculation of local
027     * <i>apparent</i> sidereal time in the future, if clients find
028     * that useful.</p>
029     * <p>
030     * The LST may be constructed for any location, but by default
031     * is set up to use the VLA's longitude (-107d 37' 03.819").</p>
032     * <p>
033     * <b>Version Info:</b>
034     * <table style="margin-left:2em">
035     *   <tr><td>$Revision: 1669 $</td></tr>
036     *   <tr><td>$Date: 2008-11-05 11:08:04 -0700 (Wed, 05 Nov 2008) $</td></tr>
037     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
038     * </table></p>
039     * 
040     * @author David M. Harland
041     * @since 2007-08-02
042     */
043    public class LocalSiderealTime
044      implements Cloneable, Comparable<LocalSiderealTime>
045    {
046      //These variables are used internally in VLA location calculations
047      private static final Longitude VLA_LOCATION =
048        Longitude.parse("-107d 37' 03.819\"");
049      
050      private static final TimeZone VLA_TIME_ZONE =
051        TimeZone.getTimeZone("US/Mountain");
052    
053      /**
054       * The number of mean sidereal units in one solar unit.
055       * For example, if the unit is "day", there are about
056       * 1.0027 sidereal days in one solar day. 
057       */
058      static final BigDecimal MEAN_SIDEREAL_PER_SOLAR = new BigDecimal("1.00273790935");
059      
060      //============================================================================
061      // INSTANCE VARIABLES & CONSTRUCTORS
062      //============================================================================
063    
064      private Longitude location;
065      private TimeZone  timeZone;
066    
067      private JulianDate julianDate;
068      private TimeOfDay  gmst;  //24-hr day, but sec/min/hr are sidereal, not solar
069      private TimeOfDay  lmst;  //same as above
070      
071      private BigDecimal gmstDay;       //helps calc of lmstDay#; not publicly available
072      private int        lmstDayNumber; //a VLA-specific concept
073      
074      private Calendar calendar; //helps set(int,int,...,int) method
075      
076      /**
077       * Creates a new local sidereal time based on the current solar time and the
078       * longitude and time zone of the VLA.
079       */
080      public LocalSiderealTime()
081      {
082        initialize(null, null, VLA_LOCATION, VLA_TIME_ZONE);
083      }
084      
085      /**
086       * Creates a new local sidereal time based on the given solar time and the
087       * longitude and time zone of the VLA.
088       * 
089       * @param gregorianCalendarDate
090       *   a solar time based on the Gregorian calendar.
091       *   A value of <i>null</i> will be intrepeted as the current system time.
092       */
093      public LocalSiderealTime(Date gregorianCalendarDate)
094      {
095        initialize(gregorianCalendarDate, null, VLA_LOCATION, VLA_TIME_ZONE);
096      }
097      
098      /**
099       * Creates a new local sidereal time based on the given solar time and the
100       * longitude and time zone of the VLA.
101       * 
102       * @param julianDate
103       *   a solar time in Julian Date form.
104       *   A value of <i>null</i> will be intrepeted as the current system time.
105       */
106      public LocalSiderealTime(JulianDate julianDate)
107      {
108        initialize(null, julianDate, VLA_LOCATION, VLA_TIME_ZONE);
109      }
110      
111      /**
112       * @param localSiderealDay
113       * @param localSiderealTime
114       */
115      public LocalSiderealTime(int localSiderealDay, TimeOfDay localSiderealTime)
116      {
117        initialize(null, null, VLA_LOCATION, VLA_TIME_ZONE);
118    
119        setSiderealTime(localSiderealDay, localSiderealTime);
120      }
121      
122      /**
123       * Creates a new local sidereal time based on the given parameters
124       * 
125       * @param gregorianCalendarDate
126       *   a solar time based on the Gregorian calendar.
127       *   A value of <i>null</i> will be intrepeted as the current system time.
128       *   
129       * @param longitude
130       *   the longitude for which this LST applies.
131       *   A value of <i>null</i> will result in a <tt>NullPointerException</tt>.
132       * 
133       * @param timeZone
134       *   the time zone at the location for which this LST applies.
135       *   A value of <i>null</i> will result in a <tt>NullPointerException</tt>.
136       */
137      public LocalSiderealTime(Date      gregorianCalendarDate,
138                               Longitude longitude,
139                               TimeZone  timeZone)
140      {
141        initialize(gregorianCalendarDate, null, longitude, timeZone);
142      }
143      
144      /** Creates and initializes instance variables for constructors. */
145      private void initialize(Date initGD, JulianDate initJD,
146                              Longitude loc, TimeZone tz)
147      {
148        timeZone = (TimeZone)tz.clone();
149        calendar = Calendar.getInstance(timeZone);
150        location = loc.clone();
151        
152        if (initJD != null)
153        {
154          julianDate = initJD.clone();
155        }
156        else
157        {
158          Date gd = (initGD == null) ? new Date() : (Date)initGD.clone();
159          julianDate = new JulianDate(gd);
160        }
161        
162        gmst = new TimeOfDay();
163        lmst = new TimeOfDay();
164        
165        //Calculates GMST and LMST based on above values
166        updateGmst();
167      }
168    
169      //============================================================================
170      // CHANGING THE LOCATION & TIME
171      //============================================================================
172    
173      /**
174       * Changes the location and time zone for which this LST is valid.
175       * 
176       * @param newLongitude
177       *   the new location on which this LST is based.
178       *   A value of <i>null</i> will result in a <tt>NullPointerException</tt>.
179       * 
180       * @param newTimeZone
181       *   the time zone corresponding to the new location.
182       *   A value of <i>null</i> will result in a <tt>NullPointerException</tt>.
183       * 
184       * @return this LST.
185       */
186      public LocalSiderealTime setLocationAndTimeZone(Longitude newLongitude,
187                                                      TimeZone  newTimeZone)
188      {
189        timeZone = (TimeZone)newTimeZone.clone();
190        
191        calendar.setTimeZone(timeZone);
192        
193        updateLocation(newLongitude);
194        
195        return this;
196      }
197    
198      /**
199       * Sets the solar time on which this LST is based to
200       * the current system time.
201       * 
202       * @return this LST.
203       */
204      public LocalSiderealTime setSolarTime()
205      {
206        return setSolarTime(new Date());
207      }
208      
209      /**
210       * Sets the solar time on which this LST is based to
211       * the given time.
212       * 
213       * @param gregorianCalendarDate a solar time based on the Gregorian calendar.
214       * 
215       * @return this LST.
216       */
217      public LocalSiderealTime setSolarTime(Date gregorianCalendarDate)
218      {
219        updateSolarDate(gregorianCalendarDate);
220        
221        return this;
222      }
223      
224      /**
225       * Sets the solar time on which this LST is based to
226       * the given time.
227       * 
228       * @param julianDate a solar time in Julian Date form.
229       * 
230       * @return this LST.
231       */
232      public LocalSiderealTime setSolarTime(JulianDate julianDate)
233      {
234        updateSolarDate(julianDate);
235        
236        return this;
237      }
238      
239      /**
240       * Sets this LST to the given day and time of day.
241       * 
242       * @param localSiderealDay
243       *   the sidereal day to which this LST should be set.  Remember that
244       *   this class is similar to java's <tt>Date</tt> class in that it
245       *   is real a date/timestamp class.  This value is based on the VLA
246       *   {@link #getDayNumber() LST-day} concept, but is not specific to
247       *   that longitude.  The UTC corresponding to local sidereal day zero
248       *   and local sidereal time zero varies with longitude, but is on
249       *   December 6 or 7 of 1840, arrived at empirically.  Day / time
250       *   zero is just an arbitrary epoch for beginning a count of sidereal
251       *   time. 
252       * 
253       * @param localSiderealTime
254       *   the local sidereal time of day.
255       * 
256       * @return this LST.
257       * 
258       * @since 2008-09-25
259       */
260      public final LocalSiderealTime setSiderealTime(int localSiderealDay,
261                                                     TimeOfDay localSiderealTime)
262      {
263        //STRATEGY: Rather than rework the algorithms to let us go "backward" from
264        //          LST to UTC / JD, we use our current LST-DAY and LST values, 
265        //          comparing them to the parameters.  We can easily figure out
266        //          how far in the sidereal future or past the parameter values
267        //          are.  We then add this duration to our current values.
268        
269        //The next two values below are in number of days since epoch
270        BigDecimal now = new BigDecimal(lmstDayNumber).add(lmst.toFractionOfDay());
271        
272        BigDecimal other =
273          new BigDecimal(localSiderealDay).add(localSiderealTime.toFractionOfDay());
274        
275        BigDecimal deltaSiderealDays = other.subtract(now);
276        
277        //TimeDuration has this dopey restriction on units -- DAY is illegal
278        BigDecimal deltaSiderealHours =
279          deltaSiderealDays.multiply(TimeUnits.DAY.toUnits(TimeUnits.HOUR));
280        
281        TimeDuration siderealDuration = new TimeDuration(deltaSiderealHours.abs());
282        
283        switch (deltaSiderealHours.signum())
284        {
285          case -1:  return subtractSidereal(siderealDuration);
286          case +1:  return addSidereal(siderealDuration);
287          default:  return this;
288        }
289      }
290      
291      /**
292       * Sets this LST to the given day and time of day.
293       * The {@link #getDayNumber() local sidereal day} will not be changed.
294       * 
295       * @param lst
296       *   the local sidereal time of day.
297       * 
298       * @return this LST.
299       * 
300       * @since 2008-09-25
301       */
302      public final LocalSiderealTime setSiderealTime(TimeOfDay lst)
303      {
304        return setSiderealTime(lmstDayNumber, lst);
305      }
306      
307      /**
308       * Sets the local solar calendar date and the local sidereal time.
309       * <p>
310       * <b><u>Date Parameters</u></b><br/>
311       * The <tt>year</tt>, <tt>month</tt>, and <tt>day</tt> parameters
312       * all refer to our standard notion of a calendar date,
313       * such as February 22, 2008.  This is a solar, not sidereal, date
314       * and is understood to refer to a date for the current time zone
315       * of this instance.
316       * (See {@link #setLocationAndTimeZone(Longitude, TimeZone)}.)
317       * The parameters follow the conventions set forth in
318       * {@link Calendar#set(int, int, int)}.  This method will not
319       * check the values of these parameters, but will instead delegate
320       * that job to the <tt>Calendar.set</tt> method.</p>
321       * <p>
322       * <b><u>Time Parameters</u></b><br/>
323       * The <tt>lstHour</tt>, <tt>lstMinute</tt>, and <tt>lstSecond</tt>
324       * parameters are all local sidereal time values.  This method will
325       * use {@link TimeOfDay#set(int, int, BigDecimal)} to validate these
326       * values and will rethrow any exceptions encountered.</p>
327       * <p>
328       * <b><u>Example</u></b><br/>
329       * Let's say you want to configure an instance of this class so that it is
330       * set to LST 12:34:56.789 on July 3, 2008 for the VLA.  The default location
331       * and time zone for instances of this class are those of the VLA, so you need
332       * do nothing regarding longitude and time zone.  The code set the date and
333       * time given above is:
334       * <pre>
335       *   LocalSiderealTime schedTime = new LocalSiderealTime();
336       *   schedTime.set(2008, 6, 3, 12, 34, 56.789);</pre>
337       * Note that July is <tt>6</tt>, not <tt>7</tt>, due to the conventions
338       * of the <tt>Calendar</tt> class.  If you were then to write this code:
339       * <pre>
340       *   System.out.println(schedTime.toDate());</pre>
341       * You should see that if the date is expressed as a Mountain Daylight Time
342       * value, the date is July 3, 2008 and that the time of day is some value
343       * that in all likelihood is not 12:34:56.789.</p>
344       * <p>
345       * <b><u>Days That Contain Two of the Given LST Values</u></b><br/>
346       * Since a sidereal day is shorter than a solar day by about four minutes,
347       * there are about four minutes of an LST day that show up twice per
348       * solar day.  This method will always return the earlier of these
349       * two times.</p>
350       * 
351       * @param year
352       *   the calendar year.
353       * @param month
354       *   the calendar month.  This is a zero-based value.
355       * @param day
356       *   the day of the calendar month.
357       * @param lstHour
358       *   the local sidereal hour.
359       * @param lstMinute
360       *   the local sidereal minute.
361       * @param lstSecond
362       *   the local sidereal second.
363       *   
364       * @return this LST after the values have been set.
365       * 
366       * @throws IllegalArgumentException
367       *   if there are problems with the parameter values.
368       */
369      public LocalSiderealTime set(int year,    int month,     int        day,
370                                   int lstHour, int lstMinute, BigDecimal lstSecond)
371      {
372        //We do this first in case an exception is thrown in order to leave
373        //our state unchanged.
374        TimeOfDay lstDesired = new TimeOfDay();
375        lstDesired.set(lstHour, lstMinute, lstSecond);
376    
377        //Change the solar time to midnight of the given local time zone date
378        calendar.clear();
379        calendar.set(year, month, day);
380        
381        this.setSolarTime(calendar.getTime());
382    
383        //See what LST we have at solar midnight
384        TimeOfDay lstAtMidnight = this.toTimeOfDay();
385    
386        //Find out how far into the future the desired LST is
387        TimeDuration lstDelta = lstAtMidnight.timeUntil(lstDesired);
388    
389        //Move this clock by the calculated amount
390        this.addSidereal(lstDelta);
391    
392        return this;
393      }
394      
395      /**
396       * Sets the local solar calendar date and the local sidereal time.
397       * See {@link #set(int, int, int, int, int, BigDecimal)} for
398       * more details.
399       *   
400       * @return this LST after the values have been set.
401       */
402      public LocalSiderealTime set(int year,    int month,     int    day,
403                                   int lstHour, int lstMinute, String lstSecond)
404      {
405        return set(year, month, day, lstHour, lstMinute, new BigDecimal(lstSecond));
406      }
407      
408      //TODO? method for setting mean vs apparent?
409      
410      //============================================================================  
411      // QUERIES
412      //============================================================================  
413      
414      /**
415       * Returns a copy of the location on which this LST is based.
416       * @return a copy of the location on which this LST is based.
417       */
418      public Longitude getLocation()
419      {
420        return location.clone();
421      }
422      
423      /**
424       * Returns a copy of the time zone on which this LST is based.
425       * @return a copy of the time zone on which this LST is based.
426       */
427      public TimeZone getTimeZone()
428      {
429        return (TimeZone)timeZone.clone();
430      }
431      
432      /**
433       * Returns the VLA-scheduling day number for this LST.
434       * This concept is not universal, but is specific to the VLA.
435       * We probably will not need it once we completely replace the VLA
436       * with the EVLA.
437       * 
438       * @return the VLA scheduler's idea of an LST day number.
439       */
440      public int getDayNumber()
441      {
442        return lmstDayNumber;
443      }
444      
445      /**
446       * Returns <i>true</i> if this LST is earlier than {@code other}.
447       * (Remember that this class treats LST as a date/time, not
448       * a dateless time.)
449       * 
450       * @param other the LST to be tested against this one.
451       * @return <i>true</i> if this LST is earlier than {@code other}.
452       */
453      public boolean isBefore(LocalSiderealTime other)
454      {
455        return this.compareTo(other) < 0;
456      }
457      
458      /**
459       * Returns <i>true</i> if this LST is later than {@code other}.
460       * (Remember that this class treats LST as a date/time, not
461       * a dateless time.)
462       * 
463       * @param other the LST to be tested against this one.
464       * @return <i>true</i> if this LST is later than {@code other}.
465       */
466      public boolean isAfter(LocalSiderealTime other)
467      {
468        return this.compareTo(other) > 0;
469      }
470    
471      /**
472       * Returns the hour angle for the given right ascension at this LST.
473       * 
474       * @param rightAscension
475       *   the right ascension for which an hour angle is desired.
476       *   
477       * @return
478       *   an hour angle that is equal to this LST minus
479       *   {@code rightAscension}.
480       */
481      public Longitude getHourAngle(Longitude rightAscension)
482      {
483        //HA = LST - RA
484        return Longitude.parse(lmst.toString()).subtract(rightAscension);
485      }
486      
487      /**
488       * Returns the right ascension for the given hour angle at this LST.
489       * 
490       * @param hourAngle
491       *   the hour angle for which a right ascension is desired.
492       *   
493       * @return
494       *   a right ascension that is equal to this LST minus
495       *   {@code hourAngle}.
496       */
497      public Longitude getRightAscension(Longitude hourAngle)
498      {
499        //RA = LST - HA
500        return Longitude.parse(lmst.toString()).subtract(hourAngle);
501      }
502      
503      //============================================================================  
504      // TO OTHER FORMS
505      //============================================================================  
506      
507      /**
508       * Returns a copy of the Julian Date corresponding to this LST.
509       * @return a copy of the Julian Date corresponding to this LST.
510       */
511      public JulianDate toJulianDate()
512      {
513        return julianDate.clone();
514      }
515      
516      /**
517       * Returns a copy of the solar date and time corresponding to this LST.
518       * @return a copy of the solar date and time corresponding to this LST.
519       */
520      public Date toDate()
521      {
522        return julianDate.toDate();
523      }
524      
525      /**
526       * Returns this LST as a dateless object.
527       * The returned time of day is in sidereal units, with a 24 sidereal hour
528       * length of day.  The returned time of day is not referenced by this
529       * LST, so changes made to it will <i>not</i> be reflected herein.
530       * 
531       * @return the time portion of this LST.
532       */
533      public TimeOfDay toTimeOfDay()
534      {
535        return lmst.clone();
536      }
537      
538      /**
539       * Returns the local solar time of day for this LST.
540       * @return the local solar time of day for this LST.
541       * 
542       * @since 2008-08-14
543       */
544      public TimeOfDay toTimeOfDaySolar()
545      {
546        return julianDate.toTimeOfDayLocal();
547      }
548      
549      /**
550       * Returns the UTC solar time of day for this LST.
551       * @return the UTC solar time of day for this LST.
552       * 
553       * @since 2008-08-14
554       */
555      public TimeOfDay toTimeOfDayUtc()
556      {
557        return julianDate.toTimeOfDayUtc();
558      }
559      
560      @Override
561      public String toString()
562      {
563        return lmst.toString() + " (VLA day #" + lmstDayNumber + ")";
564      }
565    
566      //============================================================================  
567      // ARITHMETIC
568      //============================================================================  
569      
570      /**
571       * Advances this LST to the given time of day.
572       * <p>
573       * Immediately after this call a call to {@link #toTimeOfDay()} should
574       * return the same time of day as {@code lstTimeOfDay}, and a call to
575       * {@link #getDayNumber()} should show that either the day number is the
576       * same as it was prior to calling this method, or it has advanced by
577       * one day.</p>
578       * <p>
579       * Attempting to advance to the current time of day of this LST will
580       * leave this LST unchanged.  For example, the following code would
581       * not change the state of <tt>myLst</tt>:
582       * <pre>
583       *   myLst.advanceTo(myLst.toTimeOfDay());</pre>
584       * 
585       * @param lstTimeOfDay
586       *   the new time of day for this LST date/time object.
587       *   
588       * @return
589       *   this LST, after the advancement.
590       */
591      public LocalSiderealTime advanceTo(TimeOfDay lstTimeOfDay)
592      {
593        //lmst is the current LST time-of-day for this date/time instance
594        return addSidereal(lmst.timeUntil(lstTimeOfDay));
595      }
596      
597      /**
598       * Increments this LST by the given amount of <i>sidereal</i> time.
599       * 
600       * @param siderealDuration the amount of solar time by which to
601       *                         increment this LST.
602       * @return this LST, after the addition.
603       * 
604       * @see #addSolar(TimeDuration)
605       */
606      public LocalSiderealTime addSidereal(TimeDuration siderealDuration)
607      {
608        gmst.add(siderealDuration);
609        lmst.add(siderealDuration);
610        
611        gmstDay = gmstDay.add(siderealDuration.toUnits(TimeUnits.DAY));
612        updateLmstDayNumber();
613    
614        BigDecimal solarDays =
615          siderealToSolar(siderealDuration).toUnits(TimeUnits.DAY);
616        
617        julianDate.add(solarDays);
618        
619        return this;
620      }
621    
622      /**
623       * Decrements this LST by the given amount of <i>sidereal</i> time.
624       * 
625       * @param siderealDuration the amount of solar time by which to
626       *                         decrement this LST.
627       * @return this LST, after the subtraction.
628       * 
629       * @see #subtractSolar(TimeDuration)
630       */
631      public LocalSiderealTime subtractSidereal(TimeDuration siderealDuration)
632      {
633        gmst.subtract(siderealDuration);
634        lmst.subtract(siderealDuration);
635        
636        gmstDay = gmstDay.subtract(siderealDuration.toUnits(TimeUnits.DAY));
637        updateLmstDayNumber();
638        
639        BigDecimal solarDays =
640          siderealToSolar(siderealDuration).toUnits(TimeUnits.DAY);
641    
642        julianDate.subtract(solarDays);
643    
644        return this;
645      }
646      
647      /**
648       * Increments this LST by the given amount of <i>solar</i> time.
649       * @param solarDuration the amount of solar time by which to
650       *                      increment this LST.
651       * @return this LST, after the addition.
652       * 
653       * @see #addSidereal(TimeDuration)
654       */
655      public LocalSiderealTime addSolar(TimeDuration solarDuration)
656      {
657        julianDate.add(solarDuration);
658        
659        updateSolarDate(julianDate);
660        
661        return this;
662      }
663    
664      /**
665       * Decrements this LST by the given amount of <i>solar</i> time.
666       * @param solarDuration the amount of solar time by which to
667       *                      decrement this LST.
668       * @return this LST, after the subtraction.
669       * 
670       * @see #subtractSidereal(TimeDuration)
671       */
672      public LocalSiderealTime subtractSolar(TimeDuration solarDuration)
673      {
674        julianDate.subtract(solarDuration);
675        
676        updateSolarDate(julianDate);
677        
678        return this;
679      }
680      
681      /**
682       * Returns a duration, in <i>sidereal time</i>, from this LST to 
683       * {@code otherSidereal}.  If this LST is after the other time,
684       * the returned duration will have zero length. (Remember that
685       * this LST is really a date/time, not a time-of-day.)
686       * <p>
687       * To get behavior similar to {@link TimeOfDay#timeUntil(TimeOfDay)},
688       * use this pattern:</p><pre>
689       *   TimeDuration lstDur = myLst.toTimeOfDay().timeUntil(yourLst.timeOfDay());</pre>
690       * 
691       * @param otherSidereal
692       *   another point in time, expressed in sidereal time.
693       *   
694       * @return
695       *   the amount of sidereal time from this LST to {@code otherSidereal}.
696       */
697      public TimeDuration siderealTimeUntil(LocalSiderealTime otherSidereal)
698      {
699        return siderealTimeUntil(otherSidereal.julianDate);
700      }
701      
702      /**
703       * Returns a duration, in <i>sidereal time</i>, from this LST to 
704       * {@code otherSolar}.  If this LST is after the other time,
705       * the returned duration will have zero length. (Remember that
706       * this LST is really a date/time, not a time-of-day.)
707       * 
708       * @param otherSolar
709       *   another point in time, expressed in solar time.
710       * 
711       * @return
712       *   the amount of sidereal time from this LST to {@code otherSolar}.
713       */
714      public TimeDuration siderealTimeUntil(Date otherSolar)
715      {
716        return siderealTimeUntil(new JulianDate(otherSolar));
717      }
718      
719      /**
720       * Returns a duration, in <i>sidereal time</i>, from this LST to 
721       * {@code otherSolar}.  If this LST is after the other time,
722       * the returned duration will have zero length. (Remember that
723       * this LST is really a date/time, not a time-of-day.)
724       * 
725       * @param otherSolar
726       *   another point in time, expressed in solar time.
727       * 
728       * @return
729       *   the amount of sidereal time from this LST to {@code otherSolar}.
730       */
731      public TimeDuration siderealTimeUntil(JulianDate otherSolar)
732      {
733        TimeDuration solarDuration = solarTimeUntil(otherSolar);
734        
735        return solarToSidereal(solarDuration);
736      }
737      
738      /**
739       * Returns a duration, in <i>solar time</i>, from this LST to 
740       * {@code otherSidereal}.  If this LST is after the other time,
741       * the returned duration will have zero length. (Remember that
742       * this LST is really a date/time, not a time-of-day.)
743       * 
744       * @param otherSidereal
745       *   another point in time, expressed in sidereal time.
746       *   
747       * @return
748       *   the amount of solar time from this LST to {@code otherSidereal}.
749       */
750      public TimeDuration solarTimeUntil(LocalSiderealTime otherSidereal)
751      {
752        return solarTimeUntil(otherSidereal.julianDate);
753      }
754      
755      /**
756       * Returns a duration, in <i>solar time</i>, from this LST to 
757       * {@code otherSolar}.  If this LST is after the other time,
758       * the returned duration will have zero length. (Remember that
759       * this LST is really a date/time, not a time-of-day.)
760       * 
761       * @param otherSolar
762       *   another point in time, expressed in solar time.
763       *   
764       * @return
765       *   the amount of solar time from this LST to {@code otherSolar}.
766       */
767      public TimeDuration solarTimeUntil(Date otherSolar)
768      {
769        return solarTimeUntil(new JulianDate(otherSolar));
770      }
771      
772      /**
773       * Returns a duration, in <i>solar time</i>, from this LST to 
774       * {@code otherSolar}.  If this LST is after the other time,
775       * the returned duration will have zero length. (Remember that
776       * this LST is really a date/time, not a time-of-day.)
777       * 
778       * @param otherSolar
779       *   another point in time, expressed in solar time.
780       *   
781       * @return
782       *   the amount of solar time from this LST to {@code otherSolar}.
783       */
784      public TimeDuration solarTimeUntil(JulianDate otherSolar)
785      {
786        TimeDuration answer = new TimeDuration();
787    
788        if (otherSolar.isAfter(julianDate))
789        {
790          BigDecimal days = otherSolar.value().subtract(julianDate.value());
791          BigDecimal ms = TimeUnits.DAY.convertTo(TimeUnits.MILLISECOND, days);
792    
793          ms=ms.setScale(0, RoundingMode.HALF_UP);
794    
795          answer.set(ms, TimeUnits.MILLISECOND);
796        }
797        
798        return answer;
799      }
800      
801      //============================================================================  
802      // SOLAR / SIDEREAL CONVERSION
803      //============================================================================  
804      
805      /**
806       * Returns a duration in sidereal time that is equivalent to the given
807       * solar duration.
808       * 
809       * @param solarDuration
810       *   a duration whose units are based on solar time.
811       *   
812       * @return
813       *   a duration whose units are based on sidereal time and whose length
814       *   is equivalent to that of {@code solarDuration}.
815       */
816      public static TimeDuration solarToSidereal(TimeDuration solarDuration)
817      {
818        BigDecimal newValue =
819          solarDuration.getValue().multiply(MEAN_SIDEREAL_PER_SOLAR, MC_INTERM_CALCS);
820        
821        TimeDuration sidereal = solarDuration.clone();
822        sidereal.setValue(newValue);
823        return sidereal;
824      }
825      
826      private static final BigDecimal MEAN_SOLAR_PER_SIDEREAL =
827        BigDecimal.ONE.divide(MEAN_SIDEREAL_PER_SOLAR, MC_INTERM_CALCS);
828      
829      /**
830       * Returns a duration in solar time that is equivalent to the given
831       * sidereal duration.
832       * 
833       * @param siderealDuration
834       *   a duration whose units are based on sidereal time.
835       *   
836       * @return
837       *   a duration whose units are based on solar time and whose length
838       *   is equivalent to that of {@code siderealDuration}.
839       */
840      public static TimeDuration siderealToSolar(TimeDuration siderealDuration)
841      {
842        BigDecimal newValue =
843          siderealDuration.getValue().multiply(MEAN_SOLAR_PER_SIDEREAL, MC_INTERM_CALCS);
844        
845        TimeDuration solar = siderealDuration.clone();
846        solar.setValue(newValue);
847        return solar;
848    
849      }
850      
851      //============================================================================  
852      // CALCULATIONS
853      //============================================================================  
854      
855      /**
856       * Updates this object's location and all internal variables
857       * that depend on it.
858       */
859      private void updateLocation(Longitude newLongitude)
860      {
861        if (location != newLongitude)
862          location.set(newLongitude.getValue(), newLongitude.getUnits());
863        
864        updateLmst();
865      }
866      
867      /**
868       * Updates this object's solar date and all internal variables
869       * that depend on it.
870       */
871      private void updateSolarDate(Date newDate)
872      {
873        julianDate.set(newDate);
874        
875        updateSolarDate(julianDate);
876      }
877      
878      /**
879       * Updates this object's solar date and all internal variables
880       * that depend on it.
881       */
882      private void updateSolarDate(JulianDate newDate)
883      {
884        //No need to set self from self
885        if (julianDate != newDate)
886          julianDate.set(newDate.value());
887        
888        updateGmst();
889      }
890      
891      private static final BigDecimal HR_PER_DAY =
892        TimeUnits.DAY.toUnits(TimeUnits.HOUR);
893    
894      //At this time the LST at Greenwich is 0:00:00. The particular day is Jan 1, 2000 GMT.
895      private static final BigDecimal GMST_DAY_ZERO = new BigDecimal("2451544.2230699");
896      
897      /**
898       * An arbitrary number arrived at empirically in order to match the
899       * VLA's "LST Day" concept.
900       * Steps taken:
901       * 
902       *   1. Since the USNO alg uses 1/1/2000 as its base date, use that
903       *      same date to get an arbitrary "GMST Day Zero".  This will
904       *      be the solar time at which the sidereal time is 0:00:00.
905       *      Its value is in the GMST_DAY_ZERO constant, above.
906       *      
907       *   2. Get ahold of a VLA schedule.  Find these at:
908       *      http://www.vla.nrao.edu/cgi-bin/schedules.cgi
909       *      
910       *   3. Make a program and set up times to match the schedule.
911       *      At this point the LST_DAY_NUMBER_OFFSET should be zero.
912       *      
913       *   4. Compare the LST Day # reported by this class with that of
914       *      the schedule and use the difference from here on out.
915       *      The calculated # was 58,257.
916       */
917      private static final int LST_DAY_NUMBER_OFFSET = 58257;
918    
919      /**
920       * Updates this object's GMST and all internal variables
921       * that depend on it.
922       */
923      private void updateGmst()
924      {
925        //Calc GMST as time of day
926        BigDecimal gmstHours = calcGmstHours(julianDate.value());
927        gmstHours = normalizeHours(gmstHours);
928        
929        BigDecimal gmstSeconds = TimeUnits.HOUR.convertTo(TimeUnits.SECOND, gmstHours);
930        
931        gmst.set(gmstSeconds);
932    
933        //Calc GMST day so that we can support VLA's "LST day number"
934        BigDecimal solarDaysSinceT0 = julianDate.value().subtract(GMST_DAY_ZERO);
935        gmstDay = solarDaysSinceT0.multiply(MEAN_SIDEREAL_PER_SOLAR);
936        
937        //Update local times
938        updateLmst();
939        updateLmstDayNumber();
940      }
941      
942      /**
943       * Updates the Local Mean Sidereal Time based on the current
944       * location and GMST.
945       */
946      private void updateLmst()
947      {
948        lmst.set(gmst.getTimeSinceMidnight().toUnits(TimeUnits.SECOND));
949        
950        //The convert... method puts angle to +/-0.5 circles, instead of [0.0-1.0)
951        BigDecimal secondsFromGreenwich =
952          location.toAngle().convertToMinAbsValueNormal().toUnits(ArcUnits.SECOND); 
953    
954        lmst.add(secondsFromGreenwich, TimeUnits.SECOND);
955      }
956      
957      private static final BigDecimal ONE_SECOND_IN_DAYS =
958        TimeUnits.SECOND.toUnits(TimeUnits.DAY);
959      
960      private static final TimeDuration ONE_SECOND_DURATION =
961        new TimeDuration(BigDecimal.ONE, TimeUnits.SECOND);
962        
963      /**
964       * Updates the LST Day Number from an already-up-to-date gmstDay value.
965       */
966      private void updateLmstDayNumber()
967      {
968        lmstDayNumber = calcLmstDayNumber(gmstDay);
969        
970        //We're having some trouble with rounding issues near LST midnight.
971        //Some test cases have shown that we don't change day numbers until
972        //a few milliseconds after midnight.  I wouldn't be surprised if there
973        //were undiscovered cases where we change day numbers a few ms before
974        //midnight.  This code aims to get the right day # near midnight.
975        
976        //See if we're in 1st second of LST day
977        if (lmst.getTimeSinceMidnight().compareTo(ONE_SECOND_DURATION) < 0)
978        {
979          //Use day number from LST + 1.000s
980          lmstDayNumber = calcLmstDayNumber(gmstDay.add(ONE_SECOND_IN_DAYS));
981        }
982        //See if we're in last second of LST day
983        else if (lmst.getTimeUntilMidnight().compareTo(ONE_SECOND_DURATION) < 0)
984        {
985          //Use day number from LST - 1.000s
986          lmstDayNumber = calcLmstDayNumber(gmstDay.subtract(ONE_SECOND_IN_DAYS));
987        }
988      }
989      
990      private int calcLmstDayNumber(BigDecimal greenwichMeanSiderealDay)
991      {
992        //The convert... method puts angle to +/-0.5 circles, instead of [0.0-1.0)
993        BigDecimal hoursFromGreenwich =
994          location.toAngle().convertToMinAbsValueNormal().toUnits(ArcUnits.HOUR); 
995    
996        BigDecimal lmstDay = hoursFromGreenwich.divide(HR_PER_DAY, RoundingMode.HALF_UP)
997                                               .add(greenwichMeanSiderealDay);
998        
999        return lmstDay.intValue() + LST_DAY_NUMBER_OFFSET;
1000      }
1001    
1002      //Constants used in USNO algorithm in calcGmstHours, below
1003      private static final BigDecimal USNO1 = new BigDecimal("6.697374558");      //meaning?
1004      private static final BigDecimal USNO2 = new BigDecimal("0.06570982441908"); //meaning?
1005      private static final BigDecimal USNO3 = MEAN_SIDEREAL_PER_SOLAR;
1006      private static final BigDecimal USNO4 = new BigDecimal("0.000026");         //meaning?
1007    
1008      private static final BigDecimal DAYS_PER_CENT = new BigDecimal("36525.0");
1009      private static final BigDecimal ONE_HALF      = new BigDecimal(    "0.5");
1010    
1011      /**
1012       * Uses the US Naval Observatory's 
1013       * <a href="http://aa.usno.navy.mil/faq/docs/GAST.html">
1014       * Approximate Sidereal Time
1015       * </a>
1016       * algorithm for calculating Greenwich Mean Sidereal Time for
1017       * the given date.
1018       * 
1019       * @return GMST in hours.  This result will usually need to be normalized
1020       *         to fit in the range [0.0 - 24.0).
1021       */
1022      private BigDecimal calcGmstHours(BigDecimal julianDate)
1023      {
1024        //January 1, 2000 12:00:00.0 UTC
1025        final BigDecimal JULIAN_DATE_TIME_ZERO = new BigDecimal("2451545.0");
1026        
1027        //Called "D" in USNO algorithm
1028        BigDecimal daysSinceTimeZero = julianDate.subtract(JULIAN_DATE_TIME_ZERO);
1029        
1030        BigDecimal wholeDay      = new BigDecimal(julianDate.longValue());
1031        BigDecimal fractionalDay = julianDate.subtract(wholeDay);
1032        
1033        //Called "JD0" (JD-zero)
1034        BigDecimal jdForPreviousMidnight =
1035          fractionalDay.compareTo(ONE_HALF) >= 0 ? wholeDay.add(ONE_HALF)
1036                                                 : wholeDay.subtract(ONE_HALF);
1037        //Called "D0" (D-zero)
1038        BigDecimal daysSinceTimeZeroAtPrevMidnight =
1039          jdForPreviousMidnight.subtract(JULIAN_DATE_TIME_ZERO);
1040    
1041        BigDecimal fracDaysSinceMidnight = julianDate.subtract(jdForPreviousMidnight);
1042        
1043        //Called "H"
1044        BigDecimal hoursSinceMidnight = HR_PER_DAY.multiply(fracDaysSinceMidnight);
1045        
1046        //Called "T"
1047        BigDecimal centuriesSinceTimeZero =
1048          daysSinceTimeZero.divide(DAYS_PER_CENT, RoundingMode.HALF_UP);
1049    
1050        final BigDecimal part2 = USNO2.multiply(daysSinceTimeZeroAtPrevMidnight);
1051        final BigDecimal part3 = USNO3.multiply(hoursSinceMidnight);
1052        final BigDecimal part4 = USNO4.multiply(centuriesSinceTimeZero)
1053                                      .multiply(centuriesSinceTimeZero);
1054    
1055        BigDecimal gmstHours = USNO1.add(part2).add(part3).add(part4);
1056        
1057        return gmstHours;
1058      }
1059      
1060      /** Takes a number of hours and puts it into the range [0 - 24). */
1061      private BigDecimal normalizeHours(BigDecimal hours)
1062      {
1063        if (hours.signum() < 0)
1064        {
1065          //Mimic multiples = Math.ceil(hours / -24.0)
1066          BigDecimal[] divAndRem =
1067            hours.divideAndRemainder(HR_PER_DAY.negate(), MC_INTERM_CALCS);
1068          
1069          BigDecimal multiples = divAndRem[0]; //equiv to floor(x)
1070          if (divAndRem[1].signum() < 0)
1071            multiples = multiples.add(BigDecimal.ONE);
1072    
1073          hours = hours.add(HR_PER_DAY.multiply(multiples, MC_INTERM_CALCS),
1074                            MC_FINAL_CALC);
1075        }
1076        else if (hours.compareTo(HR_PER_DAY) >= 0)
1077        {
1078          BigDecimal multiples =
1079            hours.divideToIntegralValue(HR_PER_DAY, MC_INTERM_CALCS);
1080    
1081          hours = hours.subtract(HR_PER_DAY.multiply(multiples, MC_INTERM_CALCS),
1082                                 MC_FINAL_CALC);
1083        }
1084    
1085        //Exactly hitting top of clock
1086        if (hours.compareTo(HR_PER_DAY) == 0)
1087          hours = BigDecimal.ZERO;
1088    
1089        return hours;
1090      }
1091      
1092      //============================================================================  
1093      // 
1094      //============================================================================  
1095      
1096      /**
1097       * Returns a copy of this LST.
1098       * <p>
1099       * If anything goes wrong during the cloning procedure,
1100       * a {@code RuntimeException} will be thrown.</p>
1101       */
1102      @Override
1103      public LocalSiderealTime clone()
1104      {
1105        LocalSiderealTime clone = null;
1106    
1107        try
1108        {
1109          //This line takes care of the primitive variables
1110          clone = (LocalSiderealTime)super.clone();
1111          
1112          clone.julianDate = this.julianDate.clone();
1113          clone.location   = this.location.clone();
1114          clone.gmst       = this.gmst.clone();
1115          clone.lmst       = this.lmst.clone();
1116        }
1117        catch (Exception ex)
1118        {
1119          throw new RuntimeException(ex);
1120        }
1121        
1122        return clone;
1123      }
1124      
1125      /** Returns <i>true</i> if {@code o} is equal to this LST. */
1126      @Override
1127      public boolean equals(Object o)
1128      {
1129        //Quick exit if o is this
1130        if (o == this)
1131          return true;
1132        
1133        //Quick exit if o is null
1134        if (o == null)
1135          return false;
1136        
1137        //Quick exit if classes are different
1138        if (!o.getClass().equals(this.getClass()))
1139          return false;
1140        
1141        LocalSiderealTime other = (LocalSiderealTime)o;
1142        
1143        //This treats LST not as time, but as date/time
1144        return other.julianDate.equals(this.julianDate);
1145      }
1146      
1147      /** Returns a hash code value for this LST. */
1148      @Override
1149      public int hashCode()
1150      {
1151        //This treats LST not as time, but as date/time
1152        return julianDate.hashCode();
1153      }
1154      
1155      /* (non-Javadoc)
1156       * @see java.lang.Comparable#compareTo(java.lang.Object)
1157       */
1158      public int compareTo(LocalSiderealTime other)
1159      {
1160        //This treats LST not as time, but as date/time
1161        return this.julianDate.compareTo(other.julianDate);
1162      }
1163      
1164      //============================================================================  
1165      // 
1166      //============================================================================
1167      /*
1168      public static void main(String[] args) throws Exception
1169      {
1170        LocalSiderealTime lst = new LocalSiderealTime();
1171        
1172        java.util.Calendar cal = java.util.Calendar.getInstance();
1173        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
1174        cal.clear(java.util.Calendar.MILLISECOND);
1175        cal.set(2000, 0, 1, 0, 0, 0);
1176        
1177        for (int trial=1; trial <= 60; trial++)
1178        {
1179          Date d = cal.getTime();
1180          lst.setSolarTime(d);
1181          System.out.print("LST at VLA at time " + d + " = " + lst.toTimeOfDay());
1182          System.out.print(",  DAY # = " + lst.getDayNumber());
1183          System.out.println();
1184          //cal.roll(java.util.Calendar.SECOND, true);
1185          cal.add(java.util.Calendar.HOUR, 1);
1186        }
1187        
1188        Longitude ra = Longitude.parse("07:00:00.0");
1189        
1190        System.out.println();
1191        System.out.println("lst.set(2000, 0, 2, 5, 38, 16.097)");
1192        lst.set(2000, 0, 2, 5, 38, new BigDecimal("16.097"));
1193        System.out.println("lst.toDate      = " + lst.toDate());
1194        System.out.println("lst.toTimeOfDay = " + lst.toTimeOfDay());
1195        System.out.println("HA for RA of " + ra.toStringHms() +
1196                           " = " + lst.getHourAngle(ra).toStringHms(0,3));
1197        System.out.println("HA for RA of " + ra.toStringHms() +
1198                           " = " + lst.getHourAngle(ra).toAngle().convertToMinAbsValueNormal().toUnits(ArcUnits.HOUR));
1199        
1200        System.out.println();
1201        System.out.println("lst.set(2000, 0, 3, 10, 39, 5.380)");
1202        lst.set(2000, 0, 3, 10, 39, new BigDecimal("5.380"));
1203        System.out.println("lst.toDate      = " + lst.toDate());
1204        System.out.println("lst.toTimeOfDay = " + lst.toTimeOfDay());
1205        System.out.println("HA for RA of " + ra.toStringHms() +
1206                           " = " + lst.getHourAngle(ra).toStringHms(0,3));
1207        System.out.println("HA for RA of " + ra.toStringHms() +
1208                           " = " + lst.getHourAngle(ra).toAngle().convertToMinAbsValueNormal().toUnits(ArcUnits.HOUR));
1209        
1210        System.out.println();
1211        lst = new LocalSiderealTime();
1212        System.out.print("LST at VLA now = " + lst.toTimeOfDay());
1213        System.out.print(",  DAY # = " + lst.getDayNumber());
1214        System.out.print(",  solar = " + lst.toDate());
1215        System.out.print(",  JD = " + lst.toJulianDate());
1216        System.out.println();
1217      }
1218      */
1219      /*
1220      //Data chosen to match output on page http://zeitladen.de/time.html
1221      public static void main(String... args) throws Exception
1222      {
1223        LocalSiderealTime lst = new LocalSiderealTime();
1224        lst.setLocationAndTimeZone(new Longitude("0.0"), TimeZone.getTimeZone("GMT"));
1225        
1226        java.util.Calendar cal = java.util.Calendar.getInstance();
1227        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
1228        cal.clear(java.util.Calendar.MILLISECOND);
1229        
1230        for (int day=1; day <= 30; day++)
1231        {
1232          cal.set(2008, 5, day, 0, 0, 0);  //5 = June
1233          
1234          System.out.print(day);
1235          
1236          lst.setSolarTime(cal.getTime());
1237          System.out.print(", " + lst.toTimeOfDay());
1238    
1239          cal.set(2008, 5, day, 18, 0, 0);  //5 = June
1240          lst.setSolarTime(cal.getTime());
1241          System.out.print(", " + lst.toTimeOfDay());
1242          
1243          System.out.println();
1244        }
1245      }
1246      */
1247      /*
1248      //Monthly LSTs
1249      public static void main(String... args) throws Exception
1250      {
1251        LocalSiderealTime lst = new LocalSiderealTime();
1252        
1253        java.util.Calendar cal = java.util.Calendar.getInstance();
1254        cal.clear(java.util.Calendar.MILLISECOND);
1255        
1256        for (int month=0; month < 12; month++)
1257        {
1258          cal.set(2007, month, 1, 0, 0, 0);
1259          
1260          Date date = cal.getTime();
1261          
1262          System.out.print(date + ", ");
1263          
1264          lst.setSolarTime(date);
1265          System.out.print(lst.toTimeOfDay()+", ");
1266    
1267          System.out.print(lst.getDayNumber()+", ");
1268    
1269          System.out.println();
1270        }
1271      }
1272      */
1273      /*
1274      //Daily LSTs
1275      public static void main(String... args) throws Exception
1276      {
1277        LocalSiderealTime lst = new LocalSiderealTime();
1278        
1279        java.util.Calendar cal = java.util.Calendar.getInstance();
1280        cal.clear(java.util.Calendar.MILLISECOND);
1281        
1282        for (int day=1; day <= 31; day++)
1283        {
1284          cal.set(2008, 5, day, 0, 0, 0);
1285          
1286          Date date = cal.getTime();
1287          
1288          System.out.print(date + ", ");
1289          
1290          lst.setSolarTime(date);
1291          System.out.print(lst.toTimeOfDay()+", ");
1292    
1293          System.out.print(lst.getDayNumber()+", ");
1294    
1295          System.out.println();
1296        }
1297      }
1298      */
1299      /*
1300      //Determines an arbitrary zero-point for "GMST day", which
1301      //is used in this class to get VLA's "LST day number".
1302      public static void main(String... args) throws Exception
1303      {
1304        //Set location to Greenwich
1305        LocalSiderealTime lst = new LocalSiderealTime();
1306        lst.setLocationAndTimeZone(new Longitude("0.0"), TimeZone.getTimeZone("GMT"));
1307        
1308        java.util.Calendar cal = java.util.Calendar.getInstance();
1309        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
1310        cal.clear(java.util.Calendar.MILLISECOND);
1311    
1312        //January 1, 2000 GMT (UTC)
1313        cal.set(2000, 0, 1, 0, 0, 0);
1314        lst.setSolarTime(cal.getTime());
1315        
1316        System.out.println("LST = " + lst.toTimeOfDay().toFractionOfDay());
1317        System.out.println("JD  = " + lst.toJulianDate());
1318        
1319        BigDecimal siderealDayFrac = lst.toTimeOfDay().toFractionOfDay();
1320        BigDecimal solarDayFrac = siderealDayFrac.divide(MEAN_SIDEREAL_PER_SOLAR, RoundingMode.HALF_UP);
1321        JulianDate timeZero = lst.toJulianDate().subtract(solarDayFrac);
1322        System.out.println("T0  = " + timeZero);
1323        
1324        lst.setSolarTime(timeZero);
1325        System.out.println("\nLST[t0] = " + lst.toTimeOfDay());
1326      }
1327      */
1328      /*
1329      public static void main(String... args) throws Exception
1330      {
1331        System.out.println(LocalSiderealTime.MEAN_SIDEREAL_PER_SOLAR);
1332        System.out.println(LocalSiderealTime.MEAN_SOLAR_PER_SIDEREAL);
1333        System.out.println();
1334    
1335        TimeDuration solar = new TimeDuration("1000.0", TimeUnits.SECOND);
1336        TimeDuration sidereal = LocalSiderealTime.solarToSidereal(solar);
1337        System.out.println("solar = "+solar.getValue()+"s, sidereal = "+sidereal.getValue()+"s");
1338        System.out.println("solar = "+solar.toStringHms()+", sidereal = "+sidereal.toStringHms());
1339        
1340        solar = LocalSiderealTime.siderealToSolar(sidereal);
1341        System.out.println("\nback to solar = "+solar.getValue()+"s");
1342        System.out.println("back to solar = "+solar.toStringHms());
1343        
1344        System.out.println();
1345        LocalSiderealTime lst = new LocalSiderealTime();
1346        lst.subtractSolar(new TimeDuration("30", TimeUnits.HOUR));
1347        JulianDate jd = new JulianDate();
1348        lst.siderealTimeUntil(jd);
1349      }
1350      */
1351      /*
1352      public static void main(String... args) throws Exception
1353      {
1354        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
1355        cal.set(2008, 10, 10, 3, 51, 44);
1356        cal.set(Calendar.MILLISECOND, 742);
1357        Date javaDate = cal.getTime(); //new Date();
1358        JulianDate jd = new JulianDate(javaDate);
1359        LocalSiderealTime lst1 = new LocalSiderealTime(javaDate);
1360        LocalSiderealTime lst2 = new LocalSiderealTime(jd);
1361        
1362        System.out.println("java date   = " + javaDate);
1363        System.out.println("julian date = " + jd);
1364        System.out.println("jd.toDate   = " + jd.toDate());
1365        
1366        System.out.println();
1367        
1368        System.out.println("The following are all from lst1, created from javaDate");
1369        System.out.println("lst                    = " + lst1);
1370        System.out.println("lst.toDate()           = " + lst1.toDate());
1371        System.out.println("lst.toJulianDate()     = " + lst1.toJulianDate());
1372        System.out.println("lst.toTimeOfDay()      = " + lst1.toTimeOfDay());
1373        System.out.println("lst.toTimeOfDaySolar() = " + lst1.toTimeOfDaySolar());
1374        System.out.println("lst.toTimeOfDayUtc()   = " + lst1.toTimeOfDayUtc());
1375        
1376        System.out.println();
1377        
1378        System.out.println("The following are all from lst2, created from jd");
1379        System.out.println("lst                    = " + lst2);
1380        System.out.println("lst.toDate()           = " + lst2.toDate());
1381        System.out.println("lst.toJulianDate()     = " + lst2.toJulianDate());
1382        System.out.println("lst.toTimeOfDay()      = " + lst2.toTimeOfDay());
1383        System.out.println("lst.toTimeOfDaySolar() = " + lst2.toTimeOfDaySolar());
1384        System.out.println("lst.toTimeOfDayUtc()   = " + lst2.toTimeOfDayUtc());
1385      }
1386      */
1387    }