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.floor;
017import static java.lang.Math.round;
018import static org.shredzone.commons.suncalc.util.ExtendedMath.PI2;
019import static org.shredzone.commons.suncalc.util.ExtendedMath.frac;
020
021import java.time.Instant;
022import java.time.ZonedDateTime;
023import java.util.Objects;
024
025/**
026 * This class contains a Julian Date representation of a date.
027 * <p>
028 * Objects are immutable and threadsafe.
029 */
030public class JulianDate {
031
032    private final ZonedDateTime dateTime;
033    private final double mjd;
034
035    /**
036     * Creates a new {@link JulianDate}.
037     *
038     * @param time
039     *            {@link ZonedDateTime} to use for the date.
040     */
041    public JulianDate(ZonedDateTime time) {
042        dateTime = Objects.requireNonNull(time, "time");
043        mjd = dateTime.toInstant().toEpochMilli() / 86400000.0 + 40587.0;
044    }
045
046    /**
047     * Returns a {@link JulianDate} of the current date and the given hour.
048     *
049     * @param hour
050     *            Hour of this date. This is a floating point value. Fractions are used
051     *            for minutes and seconds.
052     * @return {@link JulianDate} instance.
053     */
054    public JulianDate atHour(double hour) {
055        return new JulianDate(dateTime.plusSeconds(round(hour * 60.0 * 60.0)));
056    }
057
058    /**
059     * Returns a {@link JulianDate} of the given modified Julian date.
060     *
061     * @param mjd
062     *            Modified Julian Date
063     * @return {@link JulianDate} instance.
064     */
065    public JulianDate atModifiedJulianDate(double mjd) {
066        Instant mjdi = Instant.ofEpochMilli(Math.round((mjd - 40587.0) * 86400000.0));
067        return new JulianDate(ZonedDateTime.ofInstant(mjdi, dateTime.getZone()));
068    }
069
070    /**
071     * Returns a {@link JulianDate} of the given Julian century.
072     *
073     * @param jc
074     *            Julian Century
075     * @return {@link JulianDate} instance.
076     */
077    public JulianDate atJulianCentury(double jc) {
078        return atModifiedJulianDate(jc * 36525.0 + 51544.5);
079    }
080
081    /**
082     * Returns this {@link JulianDate} as {@link ZonedDateTime} object.
083     *
084     * @return {@link ZonedDateTime} of this {@link JulianDate}.
085     */
086    public ZonedDateTime getDateTime() {
087        return dateTime;
088    }
089
090    /**
091     * Returns the Modified Julian Date.
092     *
093     * @return Modified Julian Date, UTC.
094     */
095    public double getModifiedJulianDate() {
096        return mjd;
097    }
098
099    /**
100     * Returns the Julian Centuries.
101     *
102     * @return Julian Centuries, based on J2000 epoch, UTC.
103     */
104    public double getJulianCentury() {
105        return (mjd - 51544.5) / 36525.0;
106    }
107
108    /**
109     * Returns the Greenwich Mean Sidereal Time of this Julian Date.
110     *
111     * @return GMST
112     */
113    public double getGreenwichMeanSiderealTime() {
114        final double secs = 86400.0;
115
116        double mjd0 = floor(mjd);
117        double ut = (mjd - mjd0) * secs;
118        double t0 = (mjd0 - 51544.5) / 36525.0;
119        double t = (mjd - 51544.5) / 36525.0;
120
121        double gmst = 24110.54841
122                + 8640184.812866 * t0
123                + 1.0027379093 * ut
124                + (0.093104 - 6.2e-6 * t) * t * t;
125
126        return (PI2 / secs) * (gmst % secs);
127    }
128
129    /**
130     * Returns the earth's true anomaly of the current date.
131     * <p>
132     * A simple approximation is used here.
133     *
134     * @return True anomaly, in radians
135     */
136    public double getTrueAnomaly() {
137        return PI2 * frac((dateTime.getDayOfYear() - 5.0) / 365.256363);
138    }
139
140    @Override
141    public String toString() {
142        return String.format("%dd %02dh %02dm %02ds",
143                (long) mjd,
144                (long) (mjd * 24 % 24),
145                (long) (mjd * 24 * 60 % 60),
146                (long) (mjd * 24 * 60 * 60 % 60));
147    }
148
149}