001/*
002 * $Id: Time.java,v 1.2 2011/03/24 16:06:33 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2011
007 * Space Science and Engineering Center (SSEC)
008 * University of Wisconsin - Madison
009 * 1225 W. Dayton Street, Madison, WI 53706, USA
010 * https://www.ssec.wisc.edu/mcidas
011 * 
012 * All Rights Reserved
013 * 
014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015 * some McIDAS-V source code is based on IDV and VisAD source code.  
016 * 
017 * McIDAS-V is free software; you can redistribute it and/or modify
018 * it under the terms of the GNU Lesser Public License as published by
019 * the Free Software Foundation; either version 3 of the License, or
020 * (at your option) any later version.
021 * 
022 * McIDAS-V is distributed in the hope that it will be useful,
023 * but WITHOUT ANY WARRANTY; without even the implied warranty of
024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025 * GNU Lesser Public License for more details.
026 * 
027 * You should have received a copy of the GNU Lesser Public License
028 * along with this program.  If not, see http://www.gnu.org/licenses.
029 */
030
031package edu.wisc.ssec.mcidasv.data.adde.sgp4;
032
033import java.text.DateFormat;
034import java.text.DecimalFormat;
035import java.text.SimpleDateFormat;
036import java.util.Calendar;
037import java.util.GregorianCalendar;
038import java.util.TimeZone;
039
040/**
041 * Used to keep track of a time and convert to multiple formats.
042 * @author Shawn E. Gano
043 */
044public class Time implements java.io.Serializable
045{     
046    // time units
047    /**
048     * year type
049     */
050    public final static int YEAR=Calendar.YEAR;
051    /**
052     * month type
053     */
054    public final static int MONTH=Calendar.MONTH;
055    /**
056     * date type
057     */
058    public final static int DATE=Calendar.DATE;
059    /**
060     * hour type
061     */
062    public final static int HOUR=Calendar.HOUR;
063    /**
064     * Hour type
065     */
066    public final static int HOUR_OF_DAY=Calendar.HOUR_OF_DAY;
067    /**
068     * Minute type
069     */
070    public final static int MINUTE=Calendar.MINUTE;
071    /**
072     * Second type
073     */
074    public final static int SECOND=Calendar.SECOND;
075    /**
076     * Millisecond type
077     */
078    public final static int MILLISECOND=Calendar.MILLISECOND;
079    
080    // main calendar
081    private GregorianCalendar currentTime;
082    // other date formats
083    private double mjd; // Modified Julian date
084    private double mjde; // Modified Julian Day Ephemeris time 
085    
086    // decimal formats
087    private final static DecimalFormat fmt4Dig = new DecimalFormat("0000");
088    private final static DecimalFormat fmt2Dig = new DecimalFormat("00");
089    
090    // output format
091    private DateFormat dateFormat=null;
092    
093    // time zone
094    private final static TimeZone tz=TimeZone.getTimeZone("UTC");  // default internal timezone
095    private TimeZone tzStringFormat = TimeZone.getTimeZone("UTC"); // SEG removed static April 16 2009
096    
097    
098    public static void main(String args[])
099    {
100        Time t = new Time(2007,9,1,11,59,0.0);
101        System.out.println("Jul Date:" + t.getJulianDate() + ", string:" + t.getDateTimeStr());
102//        System.out.println("MJD  :" + t.getMJD());
103//        System.out.println("MJDE :" + t.getMJDE());
104//        System.out.println("DT: " + t.deltaT( t.getMJD() )*60*60*24 );
105        // 2454344.5
106        t.addSeconds(120.0); // add 60 sec
107        System.out.println("Jul Date + 120 sec:" + t.getJulianDate()+ ", string:" + t.getDateTimeStr());
108        
109        t.add(Time.HOUR,12);
110        System.out.println("Jul Date + 12 hour:" + t.getJulianDate()+ ", string:" + t.getDateTimeStr());
111        
112    }
113    
114    
115    /**
116     * Default Constructor
117     */
118    public Time()
119    {
120        // create new calendar with default timezone
121        currentTime = new GregorianCalendar(tz);
122        
123        // update other time formates
124        updateTimeMeasures();
125    }
126    
127  
128    /**
129     * Constructor with given calendar date (UT)
130     *
131     * @param year year
132     * @param month month (1-12)
133     * @param day day of month
134     * @param hour hour 0-24
135     * @param min minute
136     * @param sec second and fraction of second (accurate up to 1 millisecond)
137     */
138    public Time(int year, int month, int day, int hour, int min, double sec)
139    {
140        int secInt = new Double( Math.floor(sec) ).intValue();
141        int millisec = new Double( Math.round((sec - Math.floor(sec))*1000.0) ).intValue();
142        
143        currentTime = new GregorianCalendar(tz); // set default timezone
144        
145        currentTime.set(Calendar.YEAR,year);
146        currentTime.set(Calendar.MONTH,month-1);
147        currentTime.set(Calendar.DATE,day);
148        currentTime.set(Calendar.HOUR_OF_DAY,hour);
149        currentTime.set(Calendar.MINUTE,min);
150        currentTime.set(Calendar.SECOND,secInt);
151        currentTime.set(Calendar.MILLISECOND,millisec);
152        
153        // update other time formats
154        updateTimeMeasures();
155    }
156    
157    
158/**
159 * Updates the time to current system time
160 */
161    public void update2CurrentTime()
162    {
163        // update to current time (which is faster?)
164        //currentTime =new GregorianCalendar(tz);
165        currentTime.setTimeInMillis( System.currentTimeMillis() );
166        //currentTime.setTime( new Date() );
167        
168        // update other time formats
169        updateTimeMeasures();
170    } // update2CurrentTime
171
172    /**
173     * Set the current time (UT) to the number of milliseconds
174     * @param milliseconds number of millisconds as in from the function Calendar.getTimeInMillis()
175     */
176    public void set(long milliseconds)
177    {
178        currentTime.setTimeInMillis(milliseconds);
179        
180        // update other time formats
181        updateTimeMeasures();
182    } // set
183    
184     /**
185     * Add specified value in specified time unit to current time
186     *
187     * @param unit int Time unit
188     * @param val int Time increment
189     */
190    public void add(int unit, int val) 
191    {
192        currentTime.add(unit, val);
193        
194        // update other time formats
195        updateTimeMeasures();
196    }
197    
198    /**
199     * Add specified seconds to current time
200     *
201     * @param seconds number of seconds to add to current time (can be fractional)
202     */
203    public void addSeconds(double seconds) 
204    {
205        // multiply input by 1000 then round off and add this number of milliseconds to date
206        int millis2Add = new Double(Math.round( seconds*1000 )).intValue();
207        
208        currentTime.add(Calendar.MILLISECOND, millis2Add);
209        
210        // update other time formats
211        updateTimeMeasures();
212    }
213    
214    /**
215     * Updates the Julian and Julian Epehermis Dates using Current GregorianCalendar
216     */
217    private void updateTimeMeasures()
218    {
219        mjd = calcMjd(currentTime);
220        mjde = mjd + deltaT(mjd);
221    }
222    
223    /**
224     * Gets the Julian Date (UT)
225     * @return Returns the Julian Date
226     */
227    public double getJulianDate()
228    {
229        return mjd + 2400000.5;
230    }
231    
232    /**
233     * Gets the Modified Julian Date (Julian date minus 2400000.5) (UT)
234     * @return Returns the Modified Julian Date
235     */
236    public double getMJD()
237    {
238        return mjd ;
239    }
240    
241    /**
242     * Gets the Modified Julian Ephemeris Date (Julian date minus 2400000.5) (TT)
243     * @return Returns the Modified Julian Ephemeris Date
244     */
245    public double getMJDE()
246    {
247        return mjde;
248    }
249    
250    /**
251     * Sets timezone for the output string to use via the function getDateTimeStr()
252     * @param aTzStringFormat time zone to format output strings with
253     */
254    public void setTzStringFormat(TimeZone aTzStringFormat)
255    {
256        tzStringFormat = aTzStringFormat;
257    }
258    
259    
260/**
261 * Set SimpleDateFormat for displaying date/time string
262 * @param dateFormat SimpleDateFormat
263 */
264public void setDateFormat(SimpleDateFormat dateFormat) 
265{
266    this.dateFormat=dateFormat;
267}
268
269/**
270 * Set SimpleDateFormat string
271 * ISSUE - only valid after Jan 1, 1970
272 *@param formatStr String format for simple date format to use for creating strings of the date
273 */
274public void setDateFormat(java.lang.String formatStr)
275{
276    if ((formatStr!=null)&&(formatStr.length()>0))
277    {
278        dateFormat=new SimpleDateFormat(formatStr);
279    }
280}
281
282/**
283 * Gets the date format
284 * @return date format
285 */
286public DateFormat getDateFormat()
287{
288    return dateFormat;
289}
290
291/**
292 * Returns the specified field
293 * @param field int The specified field
294 * @return int The field value
295 */
296public final int get(int field) 
297{
298    return currentTime.get(field);
299}
300
301/*
302 * Get the UTC date/time string in the format  of yyyy-mm-dd hh:mm:ss
303 * If the dateFormat is not set or if the date is before Jan 1, 1970
304 * otherwise the empty string "" will be returned.)
305 * @return java.lang.String
306 */
307public String getDateTimeStr()
308{
309    String retStr="";
310    
311    if ((dateFormat!=null) &&( getJulianDate() >= 2440587.5))
312    {
313        dateFormat.setTimeZone(tzStringFormat);
314        retStr=dateFormat.format( currentTime.getTime() );
315    }
316    else
317    {
318        StringBuffer strBuf = new StringBuffer(fmt4Dig.format(get(Time.YEAR)));
319        strBuf.append("-");
320        strBuf.append(fmt2Dig.format(get(Time.MONTH)+1));
321        strBuf.append("-");
322        strBuf.append(fmt2Dig.format(get(Time.DATE)));
323        strBuf.append(" ");
324        strBuf.append(fmt2Dig.format(get(Time.HOUR_OF_DAY)));
325        strBuf.append(":");
326        strBuf.append(fmt2Dig.format(get(Time.MINUTE)));
327        strBuf.append(":");
328        strBuf.append(fmt2Dig.format(get(Time.SECOND)));
329        strBuf.append(" ");
330        strBuf.append( tz.getID() );
331        retStr=strBuf.toString();
332    }
333    return retStr;
334}
335    
336    // ============================== STATIC Functions ====================================
337    
338  /** 
339   *  Calculate Modified Julian Date from calendar object
340   *
341   * @param cal  Calendar object
342   * @return Modified Julian Date (UT)
343   */
344    public static double calcMjd(Calendar cal)
345    {
346        double sec = cal.get(Calendar.SECOND) + cal.get(Calendar.MILLISECOND)/1000.0;
347        return calcMjd(cal.get(Calendar.YEAR),cal.get(Calendar.MONTH)+1,cal.get(Calendar.DATE),cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), sec);
348    }
349        
350  /** 
351   *  Calculate Modified Julian Date from calendar date and time elements
352   *
353   * @param year  calendar year
354   * @param month calendar month
355   * @param day   calendar day
356   * @param hour  calendar hour (0-24)
357   * @param min   calendar min
358   * @param sec   calendar sec
359   * @return Modified Julian Date (UT)
360   */
361        public static double calcMjd(int year, int month, int day, int hour, int min, double sec)
362        {
363            // Variables
364            long    MjdMidnight;
365            double  FracOfDay;
366            int     b;
367            
368            if (month<=2)
369            { month+=12; --year;}
370            
371            if ( (10000L*year+100L*month+day) <= 15821004L )
372            {
373                b = -2 + ((year+4716)/4) - 1179;     // Julian calendar
374            }
375            else
376            {
377                b = (year/400)-(year/100)+(year/4);  // Gregorian calendar
378            }
379            
380            MjdMidnight = 365L*year - 679004L + b + (int) (30.6001*(month+1)) + day;
381            FracOfDay   = (hour+min/60.0+sec/3600.0) / 24.0;
382            
383            return MjdMidnight + FracOfDay;
384        } //calcMjd
385        
386        
387    /**
388     * Return TT minus UT.
389     *
390     * <p>Up to 1983 Ephemeris Time (ET) was used in place of TT, between
391     * 1984 and 2000 Temps Dynamique Terrestrial (TDT) was used in place of TT.
392     * The three time scales, while defined differently, form a continuous time
393     * scale for most purposes.  TT has a fixed offset from TAI (Temps Atomique
394     * International).
395     *
396     * <p>This method returns the difference TT - UT in days.  Usually this
397     * would be looked up in a table published after the fact.  Here we use
398     * polynomial fits for the distant past, for the future and also for the
399     * time where the table exists.  Except for 1987 to 2015, the expressions
400     * are taken from
401     * Jean Meeus, 1991, <I>Astronomical Algorithms</I>, Willmann-Bell, Richmond VA, p.73f.
402     * For the present (1987 to 2015 we use our own graphical linear fit to the
403     * data 1987 to 2001 from
404     * USNO/RAL, 2001, <I>Astronomical Almanach 2003</I>, U.S. Government Printing Office, Washington DC, Her Majesty's Stationery Office, London, p.K9:
405     *
406     * <p>t = Ep - 2002
407     * <p>DeltaT/s = 9.2 * t / 15 + 65
408     *
409     * <p>Close to the present (1900 to 1987) we use Schmadl and Zech:
410     *
411     * <p>t = (Ep - 1900) / 100
412     * <p>DeltaT/d = -0.000020      + 0.000297 * t
413     *    + 0.025184 * t<sup>2</sup> - 0.181133 * t<sup>3</sup><BR>
414     *    + 0.553040 * t<sup>4</sup> - 0.861938 * t<sup>5</sup>
415     *    + 0.677066 * t<sup>6</sup> - 0.212591 * t<sup>7</sup>
416     *
417     * <p>This work dates from 1988 and the equation is supposed to be valid only
418     * to 1987, but we extend its use into the near future.  For the 19th
419     * century we use Schmadl and Zech:
420     *
421     * <p>t = (Ep - 1900) / 100
422     * <p>DeltaT/d = -0.000009      +  0.003844 * t
423     *     +  0.083563 * t<sup>2</sup> +  0.865736 * t<sup>3</sup><BR>
424     *     +  4.867575 * t<sup>4</sup> + 15.845535 * t<sup>5</sup>
425     *     + 31.332267 * t<sup>6</sup> + 38.291999 * t<sup>7</sup><BR>
426     *     + 28.316289 * t<sup>8</sup> + 11.636204 * t<sup>9</sup>
427     *     +  2.043794 * t<sup>10</sup>
428     *
429     * <p>Stephenson and Houlden are credited with the equations for times before
430     * 1600.  First for the period 948 to 1600:
431     *
432     * <p>t = (Ep - 1850) / 100
433     * <p>DeltaT/s = 22.5 * t<sup>2</sup>
434     *
435     * <p>and before 948:
436     *
437     * <p>t = (Ep - 948) / 100
438     * <p>DeltaT/s = 1830 - 405 * t + 46.5 * t<sup>2</sup>
439     *
440     * <p>This leaves no equation for times between 1600 and 1800 and beyond
441     * 2015.  For such times we use the equation of Morrison and Stephenson:
442     *
443     * <p>t = Ep - 1810
444     * <p>DeltaT/s = -15 + 0.00325 * t<sup>2</sup> 
445     *
446     *  @param givenMJD Modified Julian Date (UT)
447     *  @return TT minus UT in days
448     */
449    
450    public static double deltaT(double givenMJD)
451    {
452        double theEpoch; /* Julian Epoch */
453        double t; /* Time parameter used in the equations. */
454        double D; /* The return value. */
455        
456        givenMJD -= 50000;
457        
458        theEpoch = 2000. + (givenMJD - 1545.) / 365.25;
459        
460    /* For 1987 to 2015 we use a graphical linear fit to the annual tabulation
461     * from USNO/RAL, 2001, Astronomical Almanach 2003, p.K9.  We use this up
462     * to 2015 about as far into the future as it is based on data in the past.
463     * The result is slightly higher than the predictions from that source. */
464        
465        if (1987 <= theEpoch && 2015 >= theEpoch)
466        {
467            t = (theEpoch - 2002.);
468            D = 9.2 * t / 15. + 65.;
469            D /= 86400.;
470        }
471        
472    /* For 1900 to 1987 we use the equation from Schmadl and Zech as quoted in
473     * Meeus, 1991, Astronomical Algorithms, p.74.  This is precise within
474     * 1.0 second. */
475        
476        else if (1900 <= theEpoch && 1987 > theEpoch)
477        {
478            t  = (theEpoch - 1900.) / 100.;
479            D = -0.212591 * t * t * t * t * t * t * t
480                    + 0.677066 * t * t * t * t * t * t
481                    - 0.861938 * t * t * t * t * t
482                    + 0.553040 * t * t * t * t
483                    - 0.181133 * t * t * t
484                    + 0.025184 * t * t
485                    + 0.000297 * t
486                    - 0.000020;
487        }
488        
489    /* For 1800 to 1900 we use the equation from Schmadl and Zech as quoted in
490     * Meeus, 1991, Astronomical Algorithms, p.74.  This is precise within 1.0
491     * second. */
492        
493        else if (1800 <= theEpoch && 1900 > theEpoch)
494        {
495            t  = (theEpoch - 1900.) / 100.;
496            D =  2.043794 * t * t * t * t * t * t * t * t * t * t
497                    + 11.636204 * t * t * t * t * t * t * t * t * t
498                    + 28.316289 * t * t * t * t * t * t * t * t
499                    + 38.291999 * t * t * t * t * t * t * t
500                    + 31.332267 * t * t * t * t * t * t
501                    + 15.845535 * t * t * t * t * t
502                    +  4.867575 * t * t * t * t
503                    +  0.865736 * t * t * t
504                    +  0.083563 * t * t
505                    +  0.003844 * t
506                    -  0.000009;
507        }
508        
509    /* For 948 to 1600 we use the equation from Stephenson and Houlden as
510     * quoted in Meeus, 1991, Astronomical Algorithms, p.73. */
511        
512        else if (948 <= theEpoch && 1600 >= theEpoch)
513        {
514            t  = (theEpoch - 1850.) / 100.;
515            D  = 22.5 * t * t;
516            D /= 86400.;
517        }
518        
519    /* Before 948 we use the equation from Stephenson and Houlden as quoted
520     * in Meeus, 1991, Astronomical Algorithms, p.73. */
521        
522        else if (948 > theEpoch)
523        {
524            t  = (theEpoch - 948.) / 100.;
525            D  = 46.5 * t * t - 405. * t + 1830.;
526            D /= 86400.;
527        }
528        
529    /* Else (between 1600 and 1800 and after 2010) we use the equation from
530     * Morrison and Stephenson, quoted as eqation 9.1 in Meeus, 1991,
531     * Astronomical Algorithms, p.73. */
532        
533        else
534        {
535            t  = theEpoch - 1810.;
536            D  = 0.00325 * t * t - 15.;
537            D /= 86400.;
538        }
539        
540        return D; // in days
541    } // deltaT
542
543    public GregorianCalendar getCurrentGregorianCalendar()
544    {
545        return currentTime;
546    }
547    
548    // function to take a given Julian date and parse it to a Gerorian Calendar
549    public static GregorianCalendar convertJD2Calendar(double jd)
550    {
551         /**
552         * Calculate calendar date for Julian date field this.jd
553         */
554        Double jd2 = new Double(jd + 0.5);
555        long I = jd2.longValue();
556        double F = jd2.doubleValue() - (double) I;
557        long A = 0;
558        long B = 0;
559
560        if (I > 2299160)
561        {
562            Double a1 = new Double(((double) I - 1867216.25) / 36524.25);
563            A = a1.longValue();
564            Double a3 = new Double((double) A / 4.0);
565            B = I + 1 + A - a3.longValue();
566        }
567        else
568        {
569            B = I;
570        }
571
572        double C = (double) B + 1524;
573        Double d1 = new Double((C - 122.1) / 365.25);
574        long D = d1.longValue();
575        Double e1 = new Double(365.25 * (double) D);
576        long E = e1.longValue();
577        Double g1 = new Double((double) (C - E) / 30.6001);
578        long G = g1.longValue();
579        Double h = new Double((double) G * 30.6001);
580        long da = (long) C - E - h.longValue();
581        
582        Integer date = new Integer((int) da); // DATE
583
584        Integer month;
585        Integer year;
586        
587        if (G < 14L)
588        {
589            month = new Integer((int) (G - 2L));
590        }
591        else
592        {
593            month = new Integer((int) (G - 14L));
594        }
595
596        if (month.intValue() > 1)
597        {
598            year = new Integer((int) (D - 4716L));
599        }
600        else
601        {
602            year = new Integer((int) (D - 4715L));
603        }
604
605        // Calculate fractional part as hours, minutes, and seconds
606        Double dhr = new Double(24.0 * F);
607        Integer hour = new Integer(dhr.intValue());
608        Double dmin = new Double((dhr.doubleValue() - (double) dhr.longValue()) * 60.0);
609        Integer minute = new Integer(dmin.intValue());
610        
611        Double dsec = new Double((dmin.doubleValue() - (double) dmin.longValue()) * 60.0);
612        Integer second=new Integer(dsec.intValue());
613        
614        //int ms = (int)((dsec.doubleValue() - (double) second.longValue()) * 1000.0);
615        // rounding fix - e-mailed to SEG by Hani A. Altwaijry 28 May 2009
616        int ms = (int) Math.round((dsec.doubleValue() - (double) second.longValue()) * 1000.0);
617        
618        // create Calendar object
619        GregorianCalendar newTime = new GregorianCalendar(tz); // set default timezone
620        
621        newTime.set(Calendar.YEAR,year);
622        newTime.set(Calendar.MONTH,month);
623        newTime.set(Calendar.DATE,date);
624        newTime.set(Calendar.HOUR_OF_DAY,hour);
625        newTime.set(Calendar.MINUTE,minute);
626        newTime.set(Calendar.SECOND,second);
627        newTime.set(Calendar.MILLISECOND,ms);
628        
629        return newTime;
630        
631    } // convertJD2Calendar
632    
633    // function used to take a given Julian Date and parse it as a string using the current settings 
634    public String convertJD2String(double jd)
635    {
636        // convert to calendar
637        GregorianCalendar newTime = convertJD2Calendar(jd);
638        
639        // format as String -- ASSUMES dateFromat is not NULL!!
640        dateFormat.setTimeZone(tzStringFormat);
641        String retStr=dateFormat.format( newTime.getTime() );
642        
643        return retStr;
644        
645
646    } // convertJD2String
647    
648
649    
650    
651}