/**
 * Copyright (c) 2002, Raben Systems, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Raben Systems, Inc. nor the names of its contributors
 * may be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
//package com.raben.util;
package usda.weru.mcrew;

import java.util.*;
import java.text.*;
import org.apache.log4j.Logger;

/**
 * Routines for calculating and setting Julian day number based on algorithms from
 * Jean Meeus, "Astronomical Algorithms", 2nd Edition, Willmann-Bell, Inc., 1998.
 * @author Vern Raben (mailto:vern@raben.com)
 * @version $Revision: 1.4 $ $Date: 2007-06-05 21:36:35 $
 */
public final class JulianDay implements java.io.Serializable, Cloneable {

    private static final long serialVersionUID = 1L;

    private static final Logger LOGGER = Logger.getLogger(JulianDay.class);
    /**
     * The Julian date default value. Used while creating a new object.
     */
    public final static int JD = 100;
    /**
     * The modified Julian Date default value
     */
    public final static int MJD = 101;
    /**
     * The year to be set for the Julian day of the calendar object being reffered to.
     */
    public final static int YEAR = Calendar.YEAR;
    /**
     * The month to be set for the Julian day of the calendar object being reffered to.
     */
    public final static int MONTH = Calendar.MONTH;
    /**
     * The Date to be set for the Julian day of the calendar object being reffered to.
     */
    public final static int DATE = Calendar.DATE;
    /**
     * The hour to be set for the Julian day of the calendar object being reffered to.
     */
    public final static int HOUR = Calendar.HOUR;
    /**
     * Tells us which hour of the day we are into at that point for the Julian Day.
     */
    public final static int HOUR_OF_DAY = Calendar.HOUR_OF_DAY;
    /**
     * The minute to be set for the Julian day of the calendar object being reffered to.
     */
    public final static int MINUTE = Calendar.MINUTE;
    /**
     * The second to be set for the Julian day of the calendar object being reffered to.
     */
    public final static int SECOND = Calendar.SECOND;
    /**
     * Tells us which day of the year we are into at that point for the Julian Day.
     */
    public final static int DAY_OF_YEAR = Calendar.DAY_OF_YEAR;
    /**
     * Tells us which day of the week we are into at that point for the Julian Day.
     */
    public final static int DAY_OF_WEEK = Calendar.DAY_OF_WEEK;
    /**
     * Tells us which day of the month we are into at that point for the Julian Day.
     */
    public final static int DAY_OF_MONTH = Calendar.DAY_OF_MONTH;
    /**
     * Value of the MONTH field indicating the first month of the year
     */
    public final static int JANUARY = Calendar.JANUARY;
    /**
     * Value of the MONTH field indicating the second month of the year
     */
    public final static int FEBRUARY = Calendar.FEBRUARY;
    /**
     * Value of the MONTH field indicating the third month of the year
     */
    public final static int MARCH = Calendar.MARCH;
    /**
     * Value of the MONTH field indicating the fourth month of the year
     */
    public final static int APRIL = Calendar.APRIL;
    /**
     * Value of the MONTH field indicating the fifth month of the year
     */
    public final static int MAY = Calendar.MAY;
    /**
     * Value of the MONTH field indicating the sixth month of the year
     */
    public final static int JUNE = Calendar.JUNE;
    /**
     * Value of the MONTH field indicating the seventh month of the year
     */
    public final static int JULY = Calendar.JULY;
    /**
     * Value of the MONTH field indicating the eighth month of the year
     */
    public final static int AUGUST = Calendar.AUGUST;
    /**
     * Value of the MONTH field indicating the ninth month of the year
     */
    public final static int SEPTEMBER = Calendar.SEPTEMBER;
    /**
     * Value of the MONTH field indicating the tenth month of the year
     */
    public final static int OCTOBER = Calendar.OCTOBER;
    /**
     * Value of the MONTH field indicating the eleventh month of the year
     */
    public final static int NOVEMBER = Calendar.NOVEMBER;
    /**
     * Value of the MONTH field indicating the twelth month of the year
     */
    public final static int DECEMBER = Calendar.DECEMBER;

    /**
     * String that stores the atime unit of the calendar day
     */
    private final static String[] TIME_UNIT = {"unk", "yr", "mo", "unk", "unk", "day", "unk", "unk",
        "unk", "unk", "unk", "hr", "min", "sec"};
    /**
     * Double value of the year 1970 taken for the Gregorian calendar, the 
     * default for a field is the same as that of the start of the epoch:
     */
    public final static double EPOCH_1970 = 2440587.5;
    /**
     * String that stores the date in a format that could be used in an SQl statement.
     */
    public final static String SQL_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
    private DateFormat dateFormat = null;
    private Integer year = 0;
    private Integer month = 0;
    private Integer date = 0;
    private Integer hour = 0;
    private Integer minute = 0;
    private Integer second = 0;
    private Double jd;
    private Integer dayOfWeek;
    private Integer dayOfYear;
    private final static DecimalFormat fmt4Dig = new DecimalFormat("0000");
    private final static DecimalFormat fmt2Dig = new DecimalFormat("00");
    private final static TimeZone tz = TimeZone.getTimeZone("UTC");

    /**
     * JulianCalendar constructor - sets JD for current time
     */
    public JulianDay() {
        Calendar cal = new GregorianCalendar(tz);
        setTime(cal.getTime());
    }

    /**
     * JulianCalendar constructor - sets JD passed as double
     * @param jd double The Julian date
     */
    public JulianDay(double jd) {
        set(JulianDay.JD, jd);
        calcCalDate();
    }

    /**
     * Constructor to create Julian day given year, month, and decimal day
     * @param yr int The year to be set.
     * @param mo int The month to be set.
     * @param da double The decimal version of day to be set that has the time 
     * related details upto seconds.
     */
    public JulianDay(int yr, int mo, double da) {
        int day = (int) da;
        int hr = 0;
        int min = 0;
        int sec = 0;
        double dhr = (da - day) * 24.0;
        hr = (int) dhr;
        double dmin = (dhr - hr) * 60.0;
        min = (int) (dmin);
        sec = (int) ((dmin - min) * 60.0);
        set(yr, mo, day, hr, min, sec);
        calcJD();
    }

    /**
     * Constructor that constructs the JulianDay given the year, month, and date
     * @param yr int The year to be set while constructing the JulianDay object.
     * @param mo int The month to be set while constructing the JulianDay object.
     * @param da int The day to be set while constructing the JulianDay object.
     */
    public JulianDay(int yr, int mo, int da) {
        int hr = 0;
        int min = 0;
        int sec = 0;

        if (da < 1) {
            da = 1;
        }

        if (mo < 0) {
            mo = 0;
        }

        if (hr < 0) {
            hr = 0;
        }

        if (min < 0) {
            min = 0;
        }

        if (sec < 0) {
            sec = 0;
        }

        set(yr, mo, da, hr, min, sec);
        calcJD();
    }

    /**
     * Constructor that constructs the JulianDay given the year, month, date
     * hour and minutes.
     * @param yr int The year to be set while constructing the JulianDay object.
     * @param mo int The month to be set while constructing the JulianDay object.
     * @param da int The day to be set while constructing the JulianDay object.
     * @param hr int The hour to be set while constructing the JulianDay object.
     * @param min int The minute to be set while constructing the JulianDay object.
     */
    public JulianDay(int yr, int mo, int da, int hr, int min) {

        int sec = 0;

        if (da < 1) {
            da = 1;
        }

        if (mo < 0) {
            mo = 0;
        }

        if (hr < 0) {
            hr = 0;
        }

        if (min < 0) {
            min = 0;
        }

        if (sec < 0) {
            sec = 0;
        }

        set(yr, mo, da, hr, min, sec);
        calcJD();
    }

    /**
     * Constructor that constructs the JulianDay given the year, month, date
     * hour, minutes and seconds
     * @param yr int The year to be set while constructing the JulianDay object.
     * @param mo int The month to be set while constructing the JulianDay object.
     * @param da int The day to be set while constructing the JulianDay object.
     * @param hr int The hour to be set while constructing the JulianDay object.
     * @param min int The minute to be set while constructing the JulianDay object.
     * @param sec int The seconds to be set while constructing the JulianDay object.
     */
    public JulianDay(int yr, int mo, int da, int hr, int min, int sec) {

        if (da < 1) {
            da = 1;
        }

        if (mo < 0) {
            mo = 0;
        }

        if (hr < 0) {
            hr = 0;
        }

        if (min < 0) {
            min = 0;
        }

        if (sec < 0) {
            sec = 0;
        }

        set(yr, mo, da, hr, min, sec);
        calcJD();
    }

    /**
     * Construct JulianDay from system time in milli-seconds since Jan 1, 1970
     * @param timeInMilliSec The time for which the JulianDay object is being calculated.
     */
    public JulianDay(long timeInMilliSec) {
        setDateTime("1970-01-01 0:00");
        add(JulianDay.DATE, ((double) timeInMilliSec / 86400000.0));
    }

    /**
     * Copy constructor for JulianDate
     * @param cal com.raben.util.JulianDate Calendar object whose date and time 
     * will be used for the creation of a new object.
     */
    public JulianDay(JulianDay cal) {
        if (cal != null) {
            set(Calendar.YEAR, cal.get(Calendar.YEAR));
            set(Calendar.MONTH, cal.get(Calendar.MONTH));
            set(Calendar.DATE, cal.get(Calendar.DATE));
            set(Calendar.HOUR_OF_DAY, cal.get(Calendar.HOUR_OF_DAY));
            set(Calendar.MINUTE, cal.get(Calendar.MINUTE));
            set(Calendar.SECOND, cal.get(Calendar.SECOND));
            calcJD();
        } else {
            Calendar calendar = new GregorianCalendar(tz);
            setTime(calendar.getTime());
        }
    }

    /**
     * Set JulianDay from sql database compatible date/time string (yyyy-mm-dd hh:mm:ss)
     * @param str java.lang.String The date string to be used for setting values
     * on creation of a newer object.
     */
    public JulianDay(String str) {
        setDateTime(str);
        calcJD();
    }

    /**
     * Construct JulianDate given Calendar as a parameter to the second's precision.
     * @param cal java.util.Calendar The object that holds the details of the date
     * to the second's precision.
     */
    public JulianDay(Calendar cal) {
        set(YEAR, cal.get(YEAR));
        set(MONTH, cal.get(MONTH));
        set(DATE, cal.get(DATE));
        set(HOUR_OF_DAY, cal.get(HOUR_OF_DAY));
        set(MINUTE, cal.get(MINUTE));
        set(SECOND, cal.get(SECOND));
        calcJD();
        calcCalDate();
    }

    /**
     * Add specified value in specified time unit to current Julian Date
     * increments next higher field
     * ISSUE - meaning of incrementing YEAR and MONTH by fractional value is not clear since
     *         period of a month and year varies, that is ignored. Year is assumed to be 365 days and
     *         month is assumed to be 30 days for computing the fractional increment.
     * ISSUE - not thoroughly tested, typically 1-2 second errors may occur
     *         due to round-off. Will be refactored
     *         "real soon  now" :) to utilize BigDecimal internal representation
     *         of Julian Day.
     * @param unit int Time unit
     * @param val int Time increment
     */
    public void add(int unit, double val) {
        double da;

        switch (unit) {
            case YEAR:
                // issue - what this means if its not whole year
                int yr = year + (int) val;
                set(YEAR, yr);
                da = (val - (int) val) * 365.0;
                set(DATE, da);
                break;
            case MONTH:
                int mo = month + (int) val;
                set(MONTH, mo);
                da = (val - (int) val) * 30.0;
                set(DATE, da);
                break;

            case DATE:
                set(JD, getJDN() + val);
                break;
            case HOUR:
            case HOUR_OF_DAY:
                set(JD, getJDN() + (val / 24.0));
                break;
            case MINUTE:
                set(JD, getJDN() + (val / 1440.0));
                break;
            case SECOND:
                set(JD, getJDN() + (val / 86400.0));
                break;
            default:
                //System.out.println("Error: JulianDate.add: The 'unit' parameter is not recognized="+unit);
                set(JD, getJDN() + val);
                break;
        }

        calcJD();

    }

    /**
     * Add specified value in specified time unit to current Julian Date
     * increments next higher field
     *
     * ISSUE - meaning of incrementing YEAR and MONTH by fractional value is not clear since
     *         period of a month and year varies, that is ignored. Year is assumed to be 365 days and
     *         month is assumed to be 30 days for computing the fractional increment.
     * ISSUE - not thoroughly tested, typically 1-2 second errors may occur
     *         due to round-off. Will be refactored
     *         "real soon  now" :) to utilize BigDecimal internal representation
     *         of Julian Day.
     * @param unit int Time unit
     * @param val int Time increment
     */
    public void add(int unit, int val) {
        int yr;
        int mo;
        switch (unit) {
            case YEAR:
                yr = year + val;
                set(YEAR, yr);
                break;
            case MONTH:
                mo = month + val;

                while (mo >= 12) {
                    mo -= 12;
                    yr = year + 1;
                    set(YEAR, yr);
                }

                while (mo < 0) {
                    mo += 12;
                    yr = year - 1;
                    set(YEAR, yr);
                }

                set(MONTH, mo);
                break;

            case DATE:
                set(JD, getJDN() + val);
                break;
            case HOUR:
            case HOUR_OF_DAY:
                set(JD, getJDN() + val * 0.041667);
                break;

            case MINUTE:
                set(JD, getJDN() + (double) val / 1440.0);
                break;

            case SECOND:
                set(JD, getJDN() + (double) val / 86400.0);
                break;
            default:
                //System.out.println("Error: JulianDate.add: The 'unit' parameter is not recognized="+unit);
                set(JD, getJDN() + val); // default to adding days
                break;
        }

        calcJD();

    }

    /**
     * Calculate calendar date for Julian date field this.jd
     */
    private void calcCalDate() {

        Double jd2 = jd + 0.5;
        long I = jd2.longValue();
        double F = jd2 - (double) I;
        long A = 0;
        long B = 0;

        if (I > 2299160) {
            Double a1 = ((double) I - 1867216.25) / 36524.25;
            A = a1.longValue();
            Double a3 = (double) A / 4.0;
            B = I + 1 + A - a3.longValue();
        } else {
            B = I;
        }

        double C = (double) B + 1524;
        Double d1 = (C - 122.1) / 365.25;
        long D = d1.longValue();
        Double e1 = 365.25 * (double) D;
        long E = e1.longValue();
        Double g1 = (C - E) / 30.6001;
        long G = g1.longValue();
        Double h = (double) G * 30.6001;
        long da = (long) C - E - h.longValue();
        date = (int) da;

        if (G < 14L) {
            month = (int) (G - 2L);
        } else {
            month = (int) (G - 14L);
        }

        if (month > 1) {
            year = (int) (D - 4716L);
        } else {
            year = (int) (D - 4715L);
        }

        // Calculate fractional part as hours, minutes, and seconds
        Double dhr = 24.0 * F;
        hour = dhr.intValue();
        Double dmin = (dhr - (double) dhr.longValue()) * 60.0;
        minute = dmin.intValue();
        Double dsec = (dmin - (double) dmin.longValue()) * 60.0;
        second = dsec.intValue();

    }

    /**
     * Calculate day of week class attribute for class attribute jd
     */
    private void calcDayOfWeek() {
        JulianDay nJd = new JulianDay(getJDN());
        nJd.setStartOfDay();
        double nJdn = nJd.getJDN() + 1.5;
        int dow = (int) (nJdn % 7);
        dayOfWeek = dow;
    }

    /**
     * Calculate day of year for jd (jd is a class attribute)
     */
    private void calcDayOfYear() {
        JulianDay julCal = new JulianDay();
        julCal.set(year, 0, 1);
        double doy = jd - julCal.getJDN();
        int idoy = (int) doy;
        dayOfYear = idoy;
    }

    /**
     * Calculate Julian Date class attribute for class attributes year, month,
     * date, hour, minute, and second
     */
    private void calcJD() {
        int mo = month + 1;
        int da = date;
        int yr = year;
        int A = 0;
        int B = 0;
        int C = 0;
        int D = 0;

        if (mo <= 2) {
            yr--;
            mo += 12;
        } else {
            mo = month + 1;
        }

        if ((year > 1582) || ((year == 1582) && (month >= 10) && (date >= 15))) {
            Double a1 = (double) yr / 100.0;
            A = a1.intValue();
            Double b1 = (double) A / 4.0;
            B = 2 - A + b1.intValue();
        } else {
            B = 0;
        }

        Double c1 = 365.25 * (double) yr;
        if (yr < 0) {
            c1 = 365.25 * (double) yr - 0.75;
        }

        C = c1.intValue();
        Double d1 = 30.6001 * (mo + 1);
        D = d1.intValue();

        double jdd = B + C + D + da + (hour.doubleValue() / 24.0)
                + (minute.doubleValue() / 1440.0) + (second.doubleValue() / 86400.0)
                + 1720994.5;
        jd = jdd;

    }

    /**
     * Returns time difference in days between date specified and the JulianDay of this object
     * (parameter date-this date)
     * @param date com.raben.util.JulianDate The object between whom the difference 
     * is being computed and returned
     * @return double The time difference in days
     */
    public double diff(JulianDay date) {
        return date != null ? date.getJDN() - getJDN() : Double.NaN;
    }

    /**
     *
     * @param obj
     * @return
     */
    @Override
    public boolean equals(Object obj) {
        LOGGER.trace("equals() called on " + obj != null ? obj.toString() : "null");
        if (obj == null) {
            return false;
        }
        return super.equals(obj);
    }

    /**
     *
     * @return
     */
    @Override
    public int hashCode() {
        return super.hashCode();
    }

    /**
     * Returns the specified field
     * @param field int The specified field
     * @return int The field value
     */
    public final int get(int field) {

        switch (field) {
            case YEAR:
                return year;
            case MONTH:
                return month;
            case DAY_OF_MONTH:
                return date;
            case HOUR:
                int hr = hour;
                hr = hr > 12 ? hr -= 12 : hr;
                return hr;
            case HOUR_OF_DAY:
                return hour;
            case MINUTE:
                return minute;
            case SECOND:
                return second;
            case DAY_OF_WEEK:
                calcDayOfWeek();
                return dayOfWeek;
            case DAY_OF_YEAR:
                calcDayOfYear();
                return dayOfYear;
            default:
                return -1; // ISSUE - should throw exception? - what does Calendar do?
        }

    }

    /**
     * Get the UTC date/time string in the format yyyy-mm-dd hh:mm:ss
     * If the dateFormat is set, the date must be more recent than Jan 1, 1970
     * otherwise the empty string "" will be returned.)
     * @return java.lang.String
     */
    public String getDateTimeStr() {
        String retStr = "";

        if ((dateFormat != null) && (getJDN() >= EPOCH_1970)) {
            dateFormat.setTimeZone(tz);
            retStr = dateFormat.format(getTime());
        } else {
            StringBuilder strBuf = new StringBuilder(fmt4Dig.format(get(JulianDay.YEAR)));
            strBuf.append("-");
            strBuf.append(fmt2Dig.format(get(JulianDay.MONTH) + 1));
            strBuf.append("-");
            strBuf.append(fmt2Dig.format(get(JulianDay.DATE)));
            strBuf.append(" ");
            strBuf.append(fmt2Dig.format(get(JulianDay.HOUR_OF_DAY)));
            strBuf.append(":");
            strBuf.append(fmt2Dig.format(get(JulianDay.MINUTE)));
            strBuf.append(":");
            strBuf.append(fmt2Dig.format(get(JulianDay.SECOND)));
            retStr = strBuf.toString();
        }
        return retStr;
    }

    /**
     * Returns the Julian Date Number as a double
     * @return double Current object's Julian day number.
     */
    public final double getJDN() {
        if (jd == null) {
            calcJD();
        }

        calcJD();

        return jd;
    }

    /**
     * Returns milli-seconds since Jan 1, 1970
     * @return long
     */
    public long getMilliSeconds() {
        //JulianDay jd1970=new JulianDay("1970-01-01 0:00");
        //double diff=getJDN()-jd1970.getJDN();
        double diff = getJDN() - EPOCH_1970;
        return (long) (diff * 86400000.0);
    }

    /**
     * Return the modified Julian date
     * @return double The modified dae.
     */
    public final double getMJD() {

        return (getJDN() - 2400000.5);
    }

    /**
     * Return date as YYYYMMDDHHMMSS string with the least unit to be returned specified
     * For example to to return YYYYMMDD specify least unit as JulianDay.DATE
     * @param leastUnit int least unit to be returned
     * @return The string upto the least unit of the JulianDay string.
     */
    public String getYMD(int leastUnit) {

        StringBuilder retBuf = new StringBuilder();
        int yr = get(JulianDay.YEAR);
        int mo = get(JulianDay.MONTH) + 1;
        int da = get(JulianDay.DATE);
        int hr = get(JulianDay.HOUR_OF_DAY);
        int min = get(JulianDay.MINUTE);
        int sec = get(JulianDay.SECOND);

        String yrStr = fmt4Dig.format(yr);

        String moStr = fmt2Dig.format(mo);
        String daStr = fmt2Dig.format(da);
        String hrStr = fmt2Dig.format(hr);
        String minStr = fmt2Dig.format(min);
        String secStr = fmt2Dig.format(sec);

        switch (leastUnit) {
            case JulianDay.YEAR:
                retBuf.append(yrStr);
                break;

            case JulianDay.MONTH:
                retBuf.append(yrStr);
                retBuf.append(moStr);
                break;

            case JulianDay.DATE:
                retBuf.append(yrStr);
                retBuf.append(moStr);
                retBuf.append(daStr);
                break;

            case JulianDay.HOUR_OF_DAY:
            case JulianDay.HOUR:
                retBuf.append(yrStr);
                retBuf.append(moStr);
                retBuf.append(daStr);
                retBuf.append(hrStr);
                break;

            case JulianDay.MINUTE:
                retBuf.append(yrStr);
                retBuf.append(moStr);
                retBuf.append(daStr);
                retBuf.append(hrStr);
                retBuf.append(minStr);
                break;

            case JulianDay.SECOND:
                retBuf.append(yrStr);
                retBuf.append(moStr);
                retBuf.append(daStr);
                retBuf.append(hrStr);
                retBuf.append(minStr);
                retBuf.append(secStr);
                break;
        }

        return retBuf.toString();

    }

    /**
     * This method sets Julian day or modified Julian day
     * @param field int Field to be changed
     * @param value double The value the field is set to
     * ISSUE - double values are truncated when setting
     * YEAR, MONTH<DATE, HOUR,MINUTE, and SECOND - this is not
     * what should happen. (Should be able to set date to 1.5 to be
     * the 1st day of month plus 12 hours).
     */
    public void set(int field, double value) {
        int ivalue = (int) value;

        switch (field) {

            case JD:
                jd = value;
                calcCalDate();
                break;

            case MJD:
                jd = value + 2400000.5;
                calcCalDate();
                break;

            case YEAR:
                year = ivalue;
                calcJD();
                break;

            case MONTH:
                if (ivalue > 11) {
                    set(YEAR, ivalue);
                    ivalue -= 11;
                }
                month = ivalue;
                calcJD();
                break;

            case DATE:
                date = ivalue;
                calcJD();
                break;

            case HOUR_OF_DAY:
            case HOUR:
                hour = ivalue;
                while (hour >= 24) {
                    add(DATE, 1);
                    hour = hour - 24;
                }
                calcJD();
                break;

            case MINUTE:
                minute = ivalue;
                while (minute >= 60) {
                    add(HOUR, 1);
                    minute = minute - 60;
                }
                calcJD();
                break;

            case SECOND:
                second = ivalue;
                while (second >= 60) {
                    add(MINUTE, 1);
                    second = second - 60;
                }
                calcJD();
                break;

        }

    }

    /**
     * Set various JulianCalendar fields
     * Example:
     *    JulianDay jd=new JulianDay();
     *    jd.set(Calendar.YEAR,1999);
     * @param field int The field to be set
     * @param value int The field value
     */
    public final void set(int field, int value) {

        switch (field) {
            case YEAR:
                year = value;
                break;

            case MONTH:
                month = value;
                break;

            case DATE:
                date = value;
                break;

            case HOUR_OF_DAY:
            case HOUR:
                hour = value;
                break;

            case MINUTE:
                minute = value;
                break;

            case SECOND:
                second = value;
                break;
        }
        calcJD();

    }

    /**
     * Set year, month, and day
     * @param year int
     * @param month int Note - January is 0, December is 11
     * @param date int
     */
    public final void set(int year, int month, int date) {
        this.year = year;
        this.month = month;
        this.date = date;
        this.hour = 0;
        this.minute = 0;
        this.second = 0;
        calcJD();
    }

    /**
     * Set year, month,day, hour and minute
     * @param year int
     * @param month int January is 0, Dec is 11
     * @param date int
     * @param hour int
     * @param minute int
     */
    public final void set(int year, int month, int date, int hour, int minute) {
        this.year = year;
        this.month = month;
        this.date = date;
        this.hour = hour;
        this.minute = minute;
        this.second = 0;
        calcJD();
    }

    /**
     * Set year month, day, hour, minute and second
     * @param year int
     * @param month int January is 0, December is 11
     * @param date int
     * @param hour int
     * @param minute int
     * @param second int
     */
    public final void set(int year, int month, int date, int hour, int minute, int second) {
        this.year = year;
        this.month = month;
        this.date = date;
        this.hour = hour;
        this.minute = minute;
        this.second = second;
        calcJD();
    }

    /**
     * Set date/time from string
     * @param str java.lang.String
     */
    public void setDateTime(String str) {
        try {
            int vals[] = {0, 0, 0, 0, 0, 0};
            str = str.replace('T', ' ');
            StringTokenizer tok = new StringTokenizer(str, "/:- ");

            if (tok.countTokens() > 0) {

                // Check if its not a database time format yyyy-mm-dd
                int j = str.indexOf("-");

                if ((j == -1) && (tok.countTokens() == 1)) {
                    setYMD(str);
                } else {
                    int i = 0;

                    while (tok.hasMoreTokens()) {
                        vals[i++] = Integer.parseInt(tok.nextToken());
                    }

                    set(vals[0], vals[1] - 1, vals[2], vals[3], vals[4], vals[5]);

                }

            }

        } catch (NumberFormatException e) {
            throw new Error(e.toString());
        }

        calcJD();

    }

    /**
     * set hour to 23, minute and second to 59
     */
    public void setEndOfDay() {
        int yr = get(YEAR);
        int mo = get(MONTH);
        int da = get(DATE);
        set(yr, mo, da, 23, 59, 59);
    }

    /**
     * Set hour,minute and second to 0
     */
    public void setStartOfDay() {
        int yr = get(YEAR);
        int mo = get(MONTH);
        int da = get(DATE);
        set(yr, mo, da, 0, 0, 0);
    }

    /**
     * Set date from Java Date
     * @param dat The time to be set for the current JulianDay object
     */
    public final void setTime(Date dat) {
        Calendar cal = new GregorianCalendar(tz);
        cal.setTime(dat);
        year = cal.get(Calendar.YEAR);
        month = cal.get(Calendar.MONTH);
        date = cal.get(Calendar.DATE);
        hour = cal.get(Calendar.HOUR_OF_DAY);
        minute = cal.get(Calendar.MINUTE);
        second = cal.get(Calendar.SECOND);
        ////System.out.println("JulianCalendar.setTime: year="+year+" month="+month+" date="+date+" hour="+hour+" minute="+minute+" second="+second);
        calcJD();
        ////System.out.println("jd="+jd);
    }

    /**
     * Set date from sting in the form YYYYMMDDhhmmss (YYYY=year MM=month DD=day hh=hr mm=min ss=sec)
     * @param str java.lang.String
     */
    public void setYMD(String str) {

        int vals[] = {0, 0, 0, 0, 0, 0};

        if (str.length() >= 4) {
            vals[0] = Integer.parseInt(str.substring(0, 4));
        }
        if (str.length() >= 6) {
            vals[1] = Integer.parseInt(str.substring(4, 6));
        }

        if (str.length() >= 8) {
            vals[2] = Integer.parseInt(str.substring(6, 8));
        }

        if (str.length() >= 10) {
            vals[3] = Integer.parseInt(str.substring(8, 10));
        }
        if (str.length() >= 12) {
            vals[4] = Integer.parseInt(str.substring(10, 12));
        }

        if (str.length() >= 14) {
            vals[5] = Integer.parseInt(str.substring(12, 14));
        }

        set(YEAR, vals[0]);
        set(MONTH, vals[1] - 1);
        set(DATE, vals[2]);
        set(HOUR_OF_DAY, vals[3]);
        set(MINUTE, vals[4]);
        set(SECOND, vals[5]);
    }

    /**
     * Creates a string that has the details of the Julian Day of the calendar from year
     * to seconds.
     * @return the string that is constructed from this append operation that has day-of-week,
     * day-of-year and the date with time specified to second's accuracy.
     */
    @Override
    public final String toString() {

        StringBuilder buf = new StringBuilder("JulianDay[jdn=");
        buf.append(getJDN());
        buf.append(",yr=");
        buf.append(get(Calendar.YEAR));
        buf.append(",mo=");
        buf.append(get(Calendar.MONTH));
        buf.append(",da=");
        buf.append(get(Calendar.DATE));
        buf.append(",hr=");
        buf.append(get(Calendar.HOUR_OF_DAY));
        buf.append(",min=");
        buf.append(get(Calendar.MINUTE));
        buf.append(",sec=");
        buf.append(get(Calendar.SECOND));
        buf.append(",dayOfWeek=");
        buf.append(get(DAY_OF_WEEK));
        buf.append(",dayOfYear=");
        buf.append(get(DAY_OF_YEAR));
        buf.append("]");

        return buf.toString();
    }

    /**
     * Return the clone of JulianDay object
     * @return Object;
     */
    @Override
    public Object clone() {
        JulianDay clone = null;
        try {
            clone = (JulianDay) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }

    /**
     * Set SimpleDateFormat string ISSUE - only valid after Jan 1, 1970
     * @param formatStr The string to be formatted according to the standard date format.
     */
    public void setDateFormat(java.lang.String formatStr) {
        if ((formatStr != null) && (formatStr.length() > 0)) {
            dateFormat = new SimpleDateFormat(formatStr);
        }
    }

    /**
     * Set SimpleDateFormat for displaying date/time string
     * @param dateFormat SimpleDateFormat
     */
    public void setDateFormat(SimpleDateFormat dateFormat) {
        this.dateFormat = dateFormat;
    }

    /**
     * Return Java Date
     * @return The Date object
     */
    public Date getTime() {
        return new Date(getMilliSeconds());
    }

    /**
     * Update JulianDay to current time
     */
    public void update() {
        Calendar cal = new GregorianCalendar(tz);
        setTime(cal.getTime());
    }

    /**
     * Get increment in days given time unit and increment
     * @return double Increment in days
     * @param unit Time unit (DATE,HOUR,HOUR_OF_DAY,MINUTE, or SECOND
     * @param incr Time increment in unit specified
     */
    public static double getIncrement(int unit, int incr) {
        double retVal = 0.0;

        switch (unit) {
            case DATE:
                retVal = incr;
                break;
            case HOUR:
            case HOUR_OF_DAY:
                retVal = incr / 24.0;
                break;
            case MINUTE:
                retVal = incr / 1440.0;
                break;
            case SECOND:
                retVal = incr / 86400.0;
                break;
            default:
                StringBuilder errMsg = new StringBuilder("JulianDay.getIncrement unit=");
                errMsg.append(unit);

                if ((unit > 0) && (unit < TIME_UNIT.length)) {
                    errMsg.append(" (");
                    errMsg.append(TIME_UNIT[unit]);
                    errMsg.append(" )");
                }

                throw new IllegalArgumentException(errMsg.toString());

        }

        return retVal;
    }

    /**
     * Get java Calendar equivalent of Julian Day
     * @return Calendar
     */
    public java.util.Calendar getCalendar() {
        Calendar cal = GregorianCalendar.getInstance(tz);
        cal.set(get(YEAR), get(MONTH), get(DATE), get(HOUR_OF_DAY),
                get(MINUTE), get(SECOND));
        return cal;
    }

}
