001/*
002 * Shredzone Commons - suncalc
003 *
004 * Copyright (C) 2017 Richard "Shred" Körber
005 *   http://commons.shredzone.org
006 *
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
013 */
014package org.shredzone.commons.suncalc.util;
015
016import static java.lang.Math.*;
017import static org.shredzone.commons.suncalc.util.ExtendedMath.*;
018
019import java.util.Calendar;
020import java.util.Date;
021
022import org.shredzone.commons.suncalc.param.TimeResultParameter.Unit;
023
024/**
025 * This class contains a Julian Date representation of a date.
026 * <p>
027 * Objects are immutable and threadsafe.
028 */
029public class JulianDate {
030
031    private final Calendar cal;
032    private final double mjd;
033
034    /**
035     * Creates a new {@link JulianDate}.
036     *
037     * @param cal
038     *            {@link Calendar} to use. Do not modify this object after invocation.
039     */
040    public JulianDate(Calendar cal) {
041        this.cal = cal;
042        mjd = cal.getTimeInMillis() / 86400000.0 + 40587.0;
043    }
044
045    /**
046     * Returns a {@link JulianDate} of the current date and the given hour.
047     *
048     * @param hour
049     *            Hour of this date. This is a floating point value. Fractions are used
050     *            for minutes and seconds.
051     * @return {@link JulianDate} instance.
052     */
053    public JulianDate atHour(double hour) {
054        Calendar clone = getCalendar();
055        clone.add(Calendar.SECOND, (int) round(hour * 60.0 * 60.0));
056        return new JulianDate(clone);
057    }
058
059    /**
060     * Returns a {@link JulianDate} of the given modified Julian date.
061     *
062     * @param mjd
063     *            Modified Julian Date
064     * @return {@link JulianDate} instance.
065     * @since 2.3
066     */
067    public JulianDate atModifiedJulianDate(double mjd) {
068        Calendar clone = getCalendar();
069        clone.setTimeInMillis(Math.round((mjd - 40587.0) * 86400000.0));
070        clone.clear(Calendar.MILLISECOND);
071        return new JulianDate(clone);
072    }
073
074    /**
075     * Returns a {@link JulianDate} of the given Julian century.
076     *
077     * @param jc
078     *            Julian Century
079     * @return {@link JulianDate} instance.
080     * @since 2.3
081     */
082    public JulianDate atJulianCentury(double jc) {
083        return atModifiedJulianDate(jc * 36525.0 + 51544.5);
084    }
085
086    /**
087     * Returns this {@link JulianDate} as {@link Date} object.
088     *
089     * @return {@link Date} of this {@link JulianDate}.
090     */
091    public Date getDate() {
092        return cal.getTime();
093    }
094
095    /**
096     * Returns this {@link JulianDate} as truncated {@link Date} object.
097     *
098     * @param unit
099     *            {@link Unit} to truncate to
100     * @return Rounded {@link Date} of this {@link JulianDate}.
101     * @since 2.3
102     */
103    public Date getDateTruncated(Unit unit) {
104        if (unit == null) { //NOSONAR: safety null check
105            throw new NullPointerException();
106        }
107
108        Calendar clone = getCalendar();
109        clone.set(Calendar.MILLISECOND, 0);
110
111        if (unit == Unit.MINUTES || unit == Unit.HOURS || unit == Unit.DAYS) {
112            clone.add(Calendar.SECOND, 30);
113            clone.set(Calendar.SECOND, 0);
114        }
115
116        if (unit == Unit.HOURS || unit == Unit.DAYS) {
117            clone.add(Calendar.MINUTE, 30);
118            clone.set(Calendar.MINUTE, 0);
119        }
120
121        if (unit == Unit.DAYS) {
122            clone.set(Calendar.HOUR_OF_DAY, 0);
123        }
124
125        return clone.getTime();
126    }
127
128    /**
129     * Returns this {@link JulianDate} as {@link Calendar} object.
130     *
131     * @return New {@link Calendar} instance of this {@link JulianDate}.
132     */
133    public Calendar getCalendar() {
134        return (Calendar) cal.clone();
135    }
136
137    /**
138     * Returns the Modified Julian Date.
139     *
140     * @return Modified Julian Date, UTC.
141     */
142    public double getModifiedJulianDate() {
143        return mjd;
144    }
145
146    /**
147     * Returns the Julian Centuries.
148     *
149     * @return Julian Centuries, based on J2000 epoch, UTC.
150     */
151    public double getJulianCentury() {
152        return (mjd - 51544.5) / 36525.0;
153    }
154
155    /**
156     * Returns the Greenwich Mean Sidereal Time of this Julian Date.
157     *
158     * @return GMST
159     */
160    public double getGreenwichMeanSiderealTime() {
161        final double secs = 86400.0;
162
163        double mjd0 = floor(mjd);
164        double ut = (mjd - mjd0) * secs;
165        double t0 = (mjd0 - 51544.5) / 36525.0;
166        double t = (mjd - 51544.5) / 36525.0;
167
168        double gmst = 24110.54841
169                + 8640184.812866 * t0
170                + 1.0027379093 * ut
171                + (0.093104 - 6.2e-6 * t) * t * t;
172
173        return (PI2 / secs) * (gmst % secs);
174    }
175
176    /**
177     * Returns the earth's true anomaly of the current date.
178     * <p>
179     * A simple approximation is used here.
180     *
181     * @return True anomaly, in radians
182     */
183    public double getTrueAnomaly() {
184        int dayOfYear = cal.get(Calendar.DAY_OF_YEAR) - 1;
185        return PI2 * frac((dayOfYear - 4.0) / 365.256363);
186    }
187
188    @Override
189    public String toString() {
190        return String.format("%dd %02dh %02dm %02ds",
191                (long) mjd,
192                (long) (mjd * 24 % 24),
193                (long) (mjd * 24 * 60 % 60),
194                (long) (mjd * 24 * 60 * 60 % 60));
195    }
196
197}