001/*
002 * Zmanim Java API
003 * Copyright (C) 2004-2026 Eliyahu Hershfeld
004 *
005 * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General
006 * Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option)
007 * any later version.
008 *
009 * This library is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied
010 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
011 * details.
012 * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to
013 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA,
014 * or connect to: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
015 */
016package com.kosherjava.zmanim;
017
018import java.math.BigDecimal;
019import java.time.Duration;
020import java.time.Instant;
021import java.time.LocalDate;
022import java.time.LocalDateTime;
023import java.time.LocalTime;
024import java.time.ZoneOffset;
025import java.time.ZonedDateTime;
026import java.util.Objects;
027
028import com.kosherjava.zmanim.util.AstronomicalCalculator;
029import com.kosherjava.zmanim.util.GeoLocation;
030import com.kosherjava.zmanim.util.ZmanimFormatter;
031
032/**
033 * A Java calendar that calculates astronomical times such as {@link getSunrise() sunrise}, {@link
034 * getSunset() sunset} and twilight times. This class contains a {@link getLocalDate() LocalDate} and can therefore
035 * use the standard Calendar functionality to change dates etc. The calculation engine used to calculate the astronomical times can
036 * be changed to a different implementation by implementing the abstract {@link AstronomicalCalculator} and setting it withthe {@link
037 * setAstronomicalCalculator(AstronomicalCalculator)}. A number of different calculation engine implementations are included in the
038 * util package.
039 * <b>Note:</b> There are times when the algorithms can't calculate proper values for sunrise, sunset and twilight. This is usually
040 * caused by trying to calculate times for areas either very far North or South, where sunrise / sunset never happen on that date.
041 * This is common when calculating twilight with a deep dip below the horizon for locations as far south of the North Pole as London,
042 * in the northern hemisphere. The sun never reaches this dip at certain times of the year. When the calculations encounter this
043 * condition a <code>null</code> will be returned when a <code>{@link java.time.Instant}</code> is expected and {@link Long#MIN_VALUE}
044 * when a <code>long</code> is expected. The reason that <code>Exception</code>s are not thrown in these cases is because the lack of
045 * a rise/set or twilight is not an exception, but an expected condition in many parts of the world.
046 * <p>
047 * Here is a simple example of how to use the API to calculate sunrise.
048 * First create the Calendar for the location you would like to calculate sunrise or sunset times for:
049 * 
050 * <pre>
051 * String locationName = &quot;Lakewood, NJ&quot;;
052 * double latitude = 40.0828; // Lakewood, NJ
053 * double longitude = -74.2094; // Lakewood, NJ
054 * double elevation = 20; // optional elevation correction in Meters
055 * // the String parameter in getZoneId() has to be a valid ZoneId listed in {@link java.time.ZoneId#getAvailableZoneIds()}
056 * ZoneId zoneId = ZoneId.of(&quot;America/New_York&quot;);
057 * GeoLocation location = new GeoLocation(locationName, latitude, longitude, elevation, zoneId);
058 * AstronomicalCalendar ac = new AstronomicalCalendar(location);
059 * </pre>
060 * 
061 * To get the time of sunrise, first set the date you want (if not set, the date will default to today):
062 * 
063 * <pre>
064 * LocalDate localDate = LocalDate.of(1969, Month.FEBRUARY, 8);
065 * ac.setLocalDate(localDate);
066 * Instant sunrise = ac.getSunrise();
067 * </pre>
068 * 
069 * @author &copy; Eliyahu Hershfeld 2004 - 2026
070 */
071public class AstronomicalCalendar implements Cloneable {
072
073        /**
074         * 90&deg; below the vertical. Used as a basis for most calculations since the location of the sun is 90&deg; below the horizon
075         * at sunrise and sunset.
076         * <b>Note </b>: it is important to note that for sunrise and sunset the {@link AstronomicalCalculator#adjustZenith(double,
077         * double) adjusted zenith} is required to account for the radius of the sun and refraction. The adjusted zenith should not be
078         * used for calculations above or below 90&deg; since they are usually calculated as an offset to 90&deg;.
079         */
080        public static final double GEOMETRIC_ZENITH = 90;
081
082        /** Sun's zenith at civil twilight (96&deg;). */
083        public static final double CIVIL_ZENITH = 96;
084
085        /** Sun's zenith at nautical twilight (102&deg;). */
086        public static final double NAUTICAL_ZENITH = 102;
087
088        /** Sun's zenith at astronomical twilight (108&deg;). */
089        public static final double ASTRONOMICAL_ZENITH = 108;
090
091        /** constant for milliseconds in a minute (60,000) */
092        public static final long MINUTE_MILLIS = 60 * 1000;
093
094        /** constant for milliseconds in an hour (3,600,000) */
095        public static final long HOUR_MILLIS = MINUTE_MILLIS * 60;
096        
097        /**
098         * The <code>LocalDate</code> encapsulated by this class to track the current date used by the class
099         */
100        private LocalDate localDate;
101
102        /**
103         * the {@link GeoLocation} used for calculations.
104         */
105        private GeoLocation geoLocation;
106
107        /**
108         * the internal {@link AstronomicalCalculator} used for calculating solar based times.
109         */
110        private AstronomicalCalculator astronomicalCalculator;
111
112        /**
113         * The getSunrise method returns a <code>Instant</code> representing the {@link AstronomicalCalculator
114         * #getElevationAdjustment(double) elevation adjusted} sunrise time. The zenith used for the calculation uses {@link
115         * GEOMETRIC_ZENITH geometric zenith} of 90&deg; plus {@link AstronomicalCalculator#getElevationAdjustment(double)}. This is
116         * adjusted by the {@link AstronomicalCalculator} to add approximately 50/60 of a degree to account for 34 archminutes of
117         * refraction and 16 archminutes for the sun's radius for a total of {@link AstronomicalCalculator#adjustZenith 90.83333&deg;}.
118         * See documentation for the specific implementation of the {@link AstronomicalCalculator} that you are using.
119         * 
120         * @return the <code>Instant</code> representing the exact sunrise time. If the calculation can't be computed such as in the
121         *         Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does not set, a
122         *         <code>null</code> will be returned. See detailed explanation on top of the page.
123         * @see AstronomicalCalculator#adjustZenith(double, double)
124         * @see getSeaLevelSunrise()
125         * @see getUTCSunrise(double)
126         */
127        public Instant getSunrise() {
128                double sunrise = getUTCSunrise(GEOMETRIC_ZENITH);
129                if (Double.isNaN(sunrise)) {
130                        return null;
131                } else {
132                        return getInstantFromTime(sunrise, SolarEvent.SUNRISE);
133                }
134        }
135
136        /**
137         * A method that returns the sunrise without {@link AstronomicalCalculator#getElevationAdjustment(double) elevation
138         * adjustment}. Non-sunrise and sunset calculations such as dawn and dusk, depend on the amount of visible light,
139         * something that is not affected by elevation. This method returns sunrise calculated at sea level. This forms the
140         * base for dawn calculations that are calculated as a dip below the horizon before sunrise.
141         * 
142         * @return the <code>Instant</code> representing the exact sea-level sunrise time. If the calculation can't be computed
143         *         such as in the Arctic Circle where there is at least one day a year where the sun does not rise, and one
144         *         where it does not set, a <code>null</code> will be returned. See detailed explanation on top of the page.
145         * @see getSunrise()
146         * @see getUTCSeaLevelSunrise(double)
147         * @see getSeaLevelSunset()
148         */
149        public Instant getSeaLevelSunrise() {
150                double sunrise = getUTCSeaLevelSunrise(GEOMETRIC_ZENITH);
151                if (Double.isNaN(sunrise)) {
152                        return null;
153                } else {
154                        return getInstantFromTime(sunrise, SolarEvent.SUNRISE);
155                }
156        }
157
158        /**
159         * A method that returns the beginning of <a href="https://en.wikipedia.org/wiki/Twilight#Civil_twilight">civil twilight</a>
160         * (dawn) using a zenith of {@link CIVIL_ZENITH 96&deg;}.
161         * 
162         * @return The <code>Instant</code> of the beginning of civil twilight using a zenith of 96&deg;. If the calculation
163         *         can't be computed, <code>null</code> will be returned. See detailed explanation on top of the page.
164         */
165        public Instant getBeginCivilTwilight() {
166                return getSunriseOffsetByDegrees(CIVIL_ZENITH);
167        }
168
169        /**
170         * A method that returns the beginning of <a href="https://en.wikipedia.org/wiki/Twilight#Nautical_twilight">nautical twilight</a>
171         * using a zenith of {@link NAUTICAL_ZENITH 102&deg;}.
172         * 
173         * @return The <code>Instant</code> of the beginning of nautical twilight using a zenith of 102&deg;. If the calculation
174         *         can't be computed <code>null</code> will be returned. See detailed explanation on top of the page.
175         */
176        public Instant getBeginNauticalTwilight() {
177                return getSunriseOffsetByDegrees(NAUTICAL_ZENITH);
178        }
179
180        /**
181         * A method that returns the beginning of <a href="https://en.wikipedia.org/wiki/Twilight#Astronomical_twilight">astronomical
182         * twilight</a> using a zenith of {@link ASTRONOMICAL_ZENITH 108&deg;}.
183         * 
184         * @return The <code>Instant</code> of the beginning of astronomical twilight using a zenith of 108&deg;. If the calculation
185         *         can't be computed, <code>null</code> will be returned. See detailed explanation on top of the page.
186         */
187        public Instant getBeginAstronomicalTwilight() {
188                return getSunriseOffsetByDegrees(ASTRONOMICAL_ZENITH);
189        }
190
191    /**
192     * The getSunset method returns an <code>Instant</code> representing the
193     * {@link AstronomicalCalculator#getElevationAdjustment(double) elevation adjusted} sunset time. The zenith used for the
194     * calculation uses {@link GEOMETRIC_ZENITH geometric zenith} of 90&deg; plus {@link AstronomicalCalculator
195     * #getElevationAdjustment(double)}. This is adjusted by the {@link AstronomicalCalculator} to add approximately 50/60 of a
196     * degree to account for 34 archminutes of refraction and 16 archminutes for the sun's radius for a total of {@link
197     * AstronomicalCalculator#adjustZenith(double, double) 90.83333&deg;}. See documentation for the specific implementation of the
198     * {@link AstronomicalCalculator} that you are using.
199     * Note: In certain cases the calculates sunset will occur before sunrise. This will typically happen when a time zone other than
200     * the local timezone is used (calculating Los Angeles sunset using a GMT time zone for example). In this case the sunset date
201     * will be incremented to the following date.
202     *
203     * @return the <code>Instant</code> representing the exact sunset time. If the calculation can't be computed such as in the Arctic
204     *         Circle where there is at least one day a year where the sun does not rise, and one where it does not set, a
205     *         <code>null</code> will be returned. See detailed explanation on top of the page.
206     * @see AstronomicalCalculator#adjustZenith(double, double)
207     * @see getSeaLevelSunset()
208     * @see getUTCSunset(double)
209     */
210    public Instant getSunset() {
211        double sunset = getUTCSunset(GEOMETRIC_ZENITH);
212        if (Double.isNaN(sunset)) {
213            return null;
214        } else {
215            return getInstantFromTime(sunset, SolarEvent.SUNSET);
216        }
217    }
218    
219        /**
220         * A method that returns the sunset without {@link AstronomicalCalculator#getElevationAdjustment(double) elevation adjustment}.
221         * Non-sunrise and sunset calculations such as dawn and dusk, depend on the amount of visible light, something that is not
222         * affected by elevation. This method returns sunset calculated at sea level. This forms the base for dusk calculations that are
223         * calculated as a dip below the horizon after sunset.
224         * 
225         * @return the <code>Instant</code> representing the exact sea-level sunset time. If the calculation can't be computed
226         *         such as in the Arctic Circle where there is at least one day a year where the sun does not rise, and one
227         *         where it does not set, a <code>null</code> will be returned. See detailed explanation on top of the page.
228         * @see getSunset()
229         * @see getUTCSeaLevelSunset(double)
230         */
231        public Instant getSeaLevelSunset() {
232                double sunset = getUTCSeaLevelSunset(GEOMETRIC_ZENITH);
233                if (Double.isNaN(sunset)) {
234                        return null;
235                } else {
236                        return getInstantFromTime(sunset, SolarEvent.SUNSET);
237                }
238        }
239
240        /**
241         * A method that returns the end of <a href="https://en.wikipedia.org/wiki/Twilight#Civil_twilight">civil twilight</a>
242         * using a zenith of {@link CIVIL_ZENITH 96&deg;}.
243         * 
244         * @return The <code>Instant</code> of the end of civil twilight using a zenith of {@link CIVIL_ZENITH 96&deg;}. If the
245         *         calculation can't be computed, <code>null</code> will be returned. See detailed explanation on top of the page.
246         */
247        public Instant getEndCivilTwilight() {
248                return getSunsetOffsetByDegrees(CIVIL_ZENITH);
249        }
250
251        /**
252         * A method that returns the end of nautical twilight using a zenith of {@link NAUTICAL_ZENITH 102&deg;}.
253         * 
254         * @return The <code>Instant</code> of the end of nautical twilight using a zenith of {@link NAUTICAL_ZENITH 102&deg;}. If
255         *         the calculation can't be computed, <code>null</code> will be returned. See detailed explanation on top of the
256         *         page.
257         */
258        public Instant getEndNauticalTwilight() {
259                return getSunsetOffsetByDegrees(NAUTICAL_ZENITH);
260        }
261
262        /**
263         * A method that returns the end of astronomical twilight using a zenith of {@link ASTRONOMICAL_ZENITH 108&deg;}.
264         * 
265         * @return the <code>Instant</code> of the end of astronomical twilight using a zenith of {@link ASTRONOMICAL_ZENITH
266         *         108&deg;}. If the calculation can't be computed, <code>null</code> will be returned. See detailed
267         *         explanation on top of the page.
268         */
269        public Instant getEndAstronomicalTwilight() {
270                return getSunsetOffsetByDegrees(ASTRONOMICAL_ZENITH);
271        }
272
273        /**
274         * A utility method that returns a date offset by the offset time passed in as a parameter. This method casts the
275         * offset as a <code>long</code> and calls {@link getTimeOffset(Instant, long)}.
276         * 
277         * @param time
278         *            the start time
279         * @param offset
280         *            the offset in milliseconds to add to the time
281         * @return the {@link java.time.Instant} with the offset added to it
282         */
283        public static Instant getTimeOffset(Instant time, double offset) {
284                return getTimeOffset(time, (long) offset);
285        }
286        
287        /**
288         * A utility method that returns an <code>Instant</code> offset by the offset time passed in. Please note that the level of light
289         * during twilight is not affected by elevation, so if this is being used to calculate an offset before sunrise or after sunset
290         * with the intent of getting a rough "level of light" calculation, the sunrise or sunset time passed to this method should be
291         * sea level sunrise and sunset.
292         * 
293         * @param time
294         *            the start time
295         * @param offsetMillis
296         *            the offset in milliseconds to add to the time.
297         * @return the {@link java.time.Instant} with the offset in milliseconds added to it
298         */
299        public static Instant getTimeOffset(Instant time, long offsetMillis) {
300            if (time == null || offsetMillis == Long.MIN_VALUE) {
301                return null;
302            }
303            return time.plusMillis(offsetMillis);
304        }
305        
306        /**
307         * A utility method that returns the time of an offset by degrees below or above the horizon of {@link getSunrise()
308         * sunrise}. Note that the degree offset is from the vertical, so for a calculation of 14&deg; before sunrise, an offset of 14
309         * + {@link GEOMETRIC_ZENITH} = 104 would have to be passed as a parameter.
310         * 
311         * @param offsetZenith
312         *            the degrees before {@link getSunrise()} to use in the calculation. For time after sunrise use negative
313         *            numbers. Note that the degree offset is from the vertical, so for a calculation of 14&deg; before sunrise, an offset
314         *            of 14 + {@link GEOMETRIC_ZENITH} = 104 would have to be passed as a parameter.
315         * @return The {@link java.time.Instant} of the offset after (or before) {@link getSunrise()}. If the calculation
316         *         can't be computed such as in the Arctic Circle where there is at least one day a year where the sun does
317         *         not rise, and one where it does not set, a <code>null</code> will be returned. See detailed explanation
318         *         on top of the page.
319         */
320        public Instant getSunriseOffsetByDegrees(double offsetZenith) {
321            double dawn = getUTCSunrise(offsetZenith);
322            return Double.isNaN(dawn) ? null
323                    : getInstantFromTime(dawn, SolarEvent.SUNRISE);
324        }
325
326        /**
327         * A utility method that returns the time of an offset by degrees below or above the horizon of {@link getSunset()
328         * sunset}. Note that the degree offset is from the vertical, so for a calculation of 14&deg; after sunset, an offset of 14 +
329         * {@link GEOMETRIC_ZENITH} = 104 would have to be passed as a parameter.
330         * 
331         * @param offsetZenith
332         *            the degrees after {@link getSunset()} to use in the calculation. For time before sunset use negative
333         *            numbers. Note that the degree offset is from the vertical, so for a calculation of 14&deg; after sunset, an offset
334         *            of 14 + {@link GEOMETRIC_ZENITH} = 104 would have to be passed as a parameter.
335         * @return The {@link java.time.Instant} of the offset after (or before) {@link getSunset()}. If the calculation
336         *         can't be computed such as in the Arctic Circle where there is at least one day a year where the sun does not rise, and
337         *         and one where it does not set, a <code>null</code> will be returned. See detailed explanation on top of the page.
338         */
339        public Instant getSunsetOffsetByDegrees(double offsetZenith) {
340            double sunset = getUTCSunset(offsetZenith);
341            return Double.isNaN(sunset) ? null
342                    : getInstantFromTime(sunset, SolarEvent.SUNSET);
343        }
344
345        /**
346         * Default constructor will set a default {@link GeoLocation#GeoLocation()}, a default {@link AstronomicalCalculator#getDefault()
347         * AstronomicalCalculator} and default the <code>LocalDate</code> to the current date.
348         */
349        public AstronomicalCalendar() {
350                this(new GeoLocation());
351        }
352
353        /**
354         * A constructor that takes in <a href="https://en.wikipedia.org/wiki/Geolocation">geolocation</a> information as a parameter.
355         * The default {@link AstronomicalCalculator#getDefault() AstronomicalCalculator} used for solar calculations is the more
356         * accurate {@link com.kosherjava.zmanim.util.NOAACalculator}.
357         *
358         * @param geoLocation
359         *            The location information used for calculating astronomical sun times.
360         *
361         * @see setAstronomicalCalculator(AstronomicalCalculator) for changing the calculator class.
362         */
363        public AstronomicalCalendar(GeoLocation geoLocation) {
364                setLocalDate(LocalDate.now(geoLocation.getZoneId()));
365                setGeoLocation(geoLocation);// duplicate call
366                setAstronomicalCalculator(AstronomicalCalculator.getDefault());
367        }
368
369        /**
370         * A method that returns the sunrise in UTC time without correction for time zone offset from GMT and without using daylight
371         * savings time.
372         * 
373         * @param zenith
374         *            the degrees below the horizon. For time after sunrise use negative numbers.
375         * @return The time in the format: 18.75 for 18:45:00 UTC/GMT. If the calculation can't be computed such as in the
376         *         Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does
377         *         not set, {@link Double#NaN} will be returned. See detailed explanation on top of the page.
378         */
379        public double getUTCSunrise(double zenith) {
380                return getAstronomicalCalculator().getUTCSunrise(getAdjustedLocalDate(), getGeoLocation(), zenith, true);
381        }
382
383        /**
384         * A method that returns the sunrise in UTC time without correction for time zone offset from GMT and without using
385         * daylight savings time. Non-sunrise and sunset calculations such as dawn and dusk, depend on the amount of visible
386         * light, something that is not affected by elevation. This method returns UTC sunrise calculated at sea level. This
387         * forms the base for dawn calculations that are calculated as a dip below the horizon before sunrise.
388         * 
389         * @param zenith
390         *            the degrees below the horizon. For time after sunrise use negative numbers.
391         * @return The time in the format: 18.75 for 18:45:00 UTC/GMT. If the calculation can't be computed such as in the
392         *         Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does
393         *         not set, {@link Double#NaN} will be returned. See detailed explanation on top of the page.
394         * @see getUTCSunrise(double)
395         * @see getUTCSeaLevelSunset(double)
396         */
397        public double getUTCSeaLevelSunrise(double zenith) {
398                return getAstronomicalCalculator().getUTCSunrise(getAdjustedLocalDate(), getGeoLocation(), zenith, false);
399        }
400
401        /**
402         * A method that returns the sunset in UTC time without correction for time zone offset from GMT and without using
403         * daylight savings time.
404         * 
405         * @param zenith
406         *            the degrees below the horizon. For time after sunset use negative numbers.
407         * @return The time in the format: 18.75 for 18:45:00 UTC/GMT. If the calculation can't be computed such as in the
408         *         Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does
409         *         not set, {@link Double#NaN} will be returned. See detailed explanation on top of the page.
410         * @see getUTCSeaLevelSunset(double)
411         */
412        public double getUTCSunset(double zenith) {
413                return getAstronomicalCalculator().getUTCSunset(getAdjustedLocalDate(), getGeoLocation(), zenith, true);
414        }
415
416        /**
417         * A method that returns the sunset in UTC time without correction for elevation, time zone offset from GMT and without using
418         * daylight savings time. Non-sunrise and sunset calculations such as dawn and dusk, depend on the amount of visible light,
419         * something that is not affected by elevation. This method returns UTC sunset calculated at sea level. This forms the base for
420         * dusk calculations that are calculated as a dip below the horizon after sunset.
421         * 
422         * @param zenith
423         *            the degrees below the horizon. For time before sunset use negative numbers.
424         * @return The time in the format: 18.75 for 18:45:00 UTC/GMT. If the calculation can't be computed such as in the
425         *         Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does
426         *         not set, {@link Double#NaN} will be returned. See detailed explanation on top of the page.
427         * @see getUTCSunset(double)
428         * @see getUTCSeaLevelSunrise(double)
429         */
430        public double getUTCSeaLevelSunset(double zenith) {
431                return getAstronomicalCalculator().getUTCSunset(getAdjustedLocalDate(), getGeoLocation(), zenith, false);
432        }
433
434        /**
435         * A method that returns a sea-level based temporal (solar) hour. The day from {@link getSeaLevelSunrise() sea-level sunrise} to
436         * {@link getSeaLevelSunset() sea-level sunset} is split into 12 equal parts with each one being a temporal hour.
437         * 
438         * @see getSeaLevelSunrise()
439         * @see getSeaLevelSunset()
440         * @see getTemporalHour(Instant, Instant)
441         * 
442         * @return the <code>long</code> millisecond length of a temporal hour. If the calculation can't be computed,
443         *         {@link Long#MIN_VALUE} will be returned. See detailed explanation on top of the page.
444         * 
445         */
446        public long getTemporalHour() {
447                return getTemporalHour(getSeaLevelSunrise(), getSeaLevelSunset());
448        }
449
450        /**
451         * A utility method that will allow the calculation of a temporal (solar) hour based on the sunrise and sunset passed as
452         * parameters to this method. An example of the use of this method would be the calculation of a elevation adjusted temporal
453         * hour by passing in {@link getSunrise() sunrise} and {@link getSunset() sunset} as parameters.
454         * 
455         * @param startOfDay
456         *            The start of the day.
457         * @param endOfDay
458         *            The end of the day.
459         * 
460         * @return the <code>long</code> millisecond length of the temporal hour. If the calculation can't be computed a
461         *         {@link Long#MIN_VALUE} will be returned. See detailed explanation on top of the page.
462         * 
463         * @see getTemporalHour()
464         */
465        public long getTemporalHour(Instant startOfDay, Instant endOfDay) {
466            if (startOfDay == null || endOfDay == null) {
467                return Long.MIN_VALUE;
468            }
469
470            return Duration.between(startOfDay, endOfDay).toMillis() / 12;
471        }
472
473        /**
474         * A method that returns sundial or solar noon. It occurs when the Sun is <a href=
475         * "https://en.wikipedia.org/wiki/Transit_%28astronomy%29">transiting</a> the <a
476         * href="https://en.wikipedia.org/wiki/Meridian_%28astronomy%29">celestial meridian</a>. The calculations used by this class
477         * depend on the {@link AstronomicalCalculator} used. If this calendar instance is {@link setAstronomicalCalculator(
478         * AstronomicalCalculator) set} to use the {@link com.kosherjava.zmanim.util.NOAACalculator} (the default) it will calculate
479         * astronomical noon. If the calendar instance is  to use the {@link com.kosherjava.zmanim.util.SunTimesCalculator}, that does
480         * not have code to calculate astronomical noon, the sun transit is calculated as halfway between sea level sunrise and sea level
481         * sunset, which can be slightly off the real transit time due to changes in declination (the lengthening or shortening day). See
482         * <a href="https://kosherjava.com/2020/07/02/definition-of-chatzos/">The Definition of Chatzos</a> for details on the proper
483         * definition of solar noon / midday.
484         * 
485         * @return the <code>Instant</code> representing Sun's transit. If the calculation can't be computed such as when using the {@link
486         *         com.kosherjava.zmanim.util.SunTimesCalculator USNO calculator} that does not support getting solar noon for the Arctic
487         *         Circle (where there is at least one day a year where the sun does not rise, and one where it does not set), a
488         *         <code>null</code> will be returned. See detailed explanation on top of the page.
489         * @see getSunTransit(Instant, Instant)
490         * @see getTemporalHour()
491         * @see com.kosherjava.zmanim.util.NOAACalculator#getUTCNoon(Calendar, GeoLocation)
492         * @see com.kosherjava.zmanim.util.SunTimesCalculator#getUTCNoon(Calendar, GeoLocation)
493         */
494        public Instant getSunTransit() {
495                double noon = getAstronomicalCalculator().getUTCNoon(getAdjustedLocalDate(), getGeoLocation());
496                return getInstantFromTime(noon, SolarEvent.NOON);
497        }
498        
499        /**
500         * A method that returns solar midnight as the <b>end of the day</b> (that may actually be after midnight of the  day it is
501         * being calculated for). For example calculating solar midnight for February 8, will calculate it for midnight between February
502         * 8 and February 9. It occurs when the Sun is <a href="https://en.wikipedia.org/wiki/Transit_%28astronomy%29">transiting</a> the
503         * lower <a href="https://en.wikipedia.org/wiki/Meridian_%28astronomy%29">celestial meridian</a>, or when the sun is at it's
504         * <a href="https://en.wikipedia.org/wiki/Nadir">nadir</a>. The calculations used by this class depend on the {@link
505         * AstronomicalCalculator} used. If this calendar instance is {@link setAstronomicalCalculator(AstronomicalCalculator) set} to use
506         * the {@link com.kosherjava.zmanim.util.NOAACalculator} (the default) it will calculate astronomical midnight. If the calendar
507         * instance is to use the {@link com.kosherjava.zmanim.util.SunTimesCalculator USNO Calculator}, that does not have code to
508         * calculate astronomical noon, midnight is calculated as 12 hours after halfway between sea level sunrise and sea level sunset
509         * of that day. This can be slightly off the real transit time due to changes in declination (the lengthening or shortening day).
510         * See <a href="https://kosherjava.com/2020/07/02/definition-of-chatzos/">The Definition of Chatzos</a> for details on the proper
511         * definition of solar noon / midday.
512         * 
513         * @return the <code>Instant</code> representing Sun's lower transit at the <b>end of the current day</b>. If the calculation
514         *         can't be computed such as when using the {@link com.kosherjava.zmanim.util.SunTimesCalculator USNO calculator} that does
515         *         not support getting solar noon or midnight for the Arctic Circle (where there is at least one day a year where the sun
516         *         does not rise, and one where it does not set), a <code>null</code> will be returned. This is not relevant when using the
517         *         {@link com.kosherjava.zmanim.util.NOAACalculator NOAA Calculator} that is never expected to return <code>null</code>.
518         *         See the detailed explanation on top of the page.
519         * 
520         * @see getSunTransit()
521         * @see com.kosherjava.zmanim.util.NOAACalculator#getUTCNoon(Calendar, GeoLocation)
522         * @see com.kosherjava.zmanim.util.SunTimesCalculator#getUTCNoon(Calendar, GeoLocation)
523         */
524        public Instant getSolarMidnight() {
525                double noon = getAstronomicalCalculator().getUTCMidnight(getAdjustedLocalDate(), getGeoLocation());
526                return getInstantFromTime(noon, SolarEvent.MIDNIGHT);
527        }
528
529        /**
530         * A method that returns sundial or solar noon (or midnight) calculated as halfway between the times passed in. It is close to,
531         * but not exactly  occurs when the Sun is <a href="https://en.wikipedia.org/wiki/Transit_%28astronomy%29">transiting</a> the
532         * <a href="https://en.wikipedia.org/wiki/Meridian_%28astronomy%29">celestial meridian</a>. It will not exactly match the
533         * astronomical transit, due to changes in declination (the lengthening or shortening day).
534         * 
535         * @param startOfDay
536         *            the start of day for calculating the sun's transit. This can be sea level sunrise, visual sunrise (or any arbitrary
537         *            start of day) passed to this method.
538         * @param endOfDay
539         *            the end of day for calculating the sun's transit. This can be sea level sunset, visual sunset (or any arbitrary end
540         *            of day) passed to this method.
541         * 
542         * @return the <code>Instant</code> representing Sun's transit. If the calculation can't be computed such as in the
543         *         Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does
544         *         not set, <code>null</code> will be returned. See detailed explanation on top of the page.
545         */
546        public Instant getSunTransit(Instant startOfDay, Instant endOfDay) {
547                long temporalHour = getTemporalHour(startOfDay, endOfDay);
548                if (temporalHour == Long.MIN_VALUE) {
549                        return null;
550                }
551                return getTimeOffset(startOfDay, temporalHour * 6);
552        }
553
554        /**
555         * An enum to indicate what type of solar event is being calculated.
556         */
557        protected enum SolarEvent {
558                /**SUNRISE A solar event related to sunrise*/SUNRISE, /**SUNSET A solar event related to sunset*/SUNSET,
559                /**NOON A solar event related to noon*/NOON, /**MIDNIGHT A solar event related to midnight*/MIDNIGHT,
560                /**NONE solar event representing azimuth or elevation calculations that that can be any time of the day*/ NONE;
561        }
562        
563        /**
564         * Return the time at a given azimuth. This often will not occur and a null will be returned.
565         * @param azimuth the azimuth that you want to get the time of day for.
566         * @return the time that the azimuth will be reached. There are cases where this azimuth will never be reached for the date
567         *            and location, and a null will be returned in that case.
568         * @see com.kosherjava.zmanim.util.AstronomicalCalculator#getTimeAtAzimuth(LocalDate, GeoLocation, double)
569         */
570        public Instant getTimeAtAzimuth(double azimuth) {
571                double rawAzimuth = getAstronomicalCalculator().getTimeAtAzimuth(getAdjustedLocalDate(), getGeoLocation(), azimuth);
572                return getInstantFromTime(rawAzimuth, SolarEvent.NONE);
573        }
574        
575        /**
576         * A method that returns an <code>Instant</code> from the time passed in as a parameter.
577         * 
578         * @param time
579         *            The time to be set as the time for the <code>Instant</code>. The time expected is in the format: 18.75
580         *            for 6:45:00 PM.time is sunrise and false if it is sunset
581         * @param solarEvent the type of {@link SolarEvent}
582         * @return The Instant object representation of the time double
583         */
584        protected Instant getInstantFromTime(double time, SolarEvent solarEvent) {
585            if (Double.isNaN(time)) {
586                return null;
587            }
588
589        LocalDate date = getAdjustedLocalDate();
590
591            double localTimeHours = (getGeoLocation().getLongitude() / 15) + time;
592
593            if (solarEvent == SolarEvent.SUNRISE && localTimeHours > 18) {
594                date = date.minusDays(1);
595            } else if (solarEvent == SolarEvent.SUNSET && localTimeHours < 6) {
596                date = date.plusDays(1);
597            } else if (solarEvent == SolarEvent.MIDNIGHT && localTimeHours < 12) {
598                date = date.plusDays(1);
599            } else if (solarEvent == SolarEvent.NOON) {
600                if (localTimeHours < 0) {
601                    date = date.plusDays(1);
602                } else if (localTimeHours > 24) {
603                    date = date.minusDays(1);
604                }
605            }
606            
607        // Math.round(time * HOUR_MILLIS) * 1_000_000L could be used below, but this exactly matches the pre-3.0 Date-based code.
608        LocalDateTime dateTime = date.atStartOfDay().plusNanos((long)(time * HOUR_MILLIS) * 1_000_000L);  
609
610            // The computed time is in UTC fractional hours; anchor in UTC before converting.
611            return ZonedDateTime.of(dateTime, ZoneOffset.UTC).toInstant();
612        }
613
614        /**
615         * Returns the sun's elevation (number of degrees) below the horizon before sunrise that matches the offset minutes
616         * on passed in as a parameter. For example passing in 72 minutes for a calendar set to the equinox in Jerusalem
617         * returns a value close to 16.1&deg;.
618         * 
619         * @param minutes
620         *            minutes before sunrise
621         * @return the degrees below the horizon before sunrise that match the offset in minutes passed it as a parameter. If
622         *            the calculation can't be computed (no sunrise occurs on this day) a {@link Double#NaN} will be returned.
623         * @deprecated This method is slow and inefficient and should NEVER be used in a loop. This method should be replaced
624         *            by calls to {@link AstronomicalCalculator#getSolarElevation(Calendar, GeoLocation)}. That method will
625         *            efficiently return the the solar elevation (the sun's position in degrees below (or above) the horizon)
626         *            at the given time even in the arctic when there is no sunrise.
627         * @see AstronomicalCalculator#getSolarElevation(Calendar, GeoLocation)
628         * @see getSunsetSolarDipFromOffset(double)
629         */
630        @Deprecated(forRemoval=false)
631        public double getSunriseSolarDipFromOffset(double minutes) {
632                Instant offsetByDegrees = getSeaLevelSunrise();
633                if(offsetByDegrees == null) {
634                        return Double.NaN;
635                }
636                Instant offsetByTime = getTimeOffset(getSeaLevelSunrise(), -(minutes * MINUTE_MILLIS));
637
638                BigDecimal degrees = new BigDecimal(0);
639                BigDecimal incrementor = new BigDecimal("0.0001");
640
641                while (offsetByDegrees == null || ((minutes < 0.0 && offsetByDegrees.toEpochMilli() < offsetByTime.toEpochMilli()) ||
642                                (minutes > 0.0 && offsetByDegrees.toEpochMilli() > offsetByTime.toEpochMilli()))) {
643                        if (minutes > 0.0) {
644                                degrees = degrees.add(incrementor);
645                        } else {
646                                degrees = degrees.subtract(incrementor);
647                        }
648                        offsetByDegrees = getSunriseOffsetByDegrees(GEOMETRIC_ZENITH + degrees.doubleValue());
649                }
650                return degrees.doubleValue();
651        }
652
653        /**
654         * Returns the sun's elevation (number of degrees) below the horizon after sunset that matches the offset minutes
655         * passed in as a parameter. For example passing in 72 minutes for a calendar set to the equinox in Jerusalem
656         * returns a value close to 16.1&deg;.
657         * 
658         * @param minutes
659         *            minutes after sunset
660         * @return the degrees below the horizon after sunset that match the offset in minutes passed it as a parameter. If
661         *            the calculation can't be computed (no sunset occurs on this day) a {@link Double#NaN} will be returned.
662         * @deprecated This method is slow and inefficient and should NEVER be used in a loop. This method should be replaced
663         *            by calls to {@link AstronomicalCalculator#getSolarElevation(ZonedDateTime, GeoLocation)}. That method will
664         *            efficiently return the the solar elevation (the sun's position in degrees below (or above) the horizon)
665         *            at the given time even in the arctic when there is no sunrise.
666         * @see AstronomicalCalculator#getSolarElevation(ZonedDateTime, GeoLocation)
667         * @see getSunriseSolarDipFromOffset(double)
668         */
669        @Deprecated(forRemoval=false)
670        public double getSunsetSolarDipFromOffset(double minutes) {
671                Instant offsetByDegrees = getSeaLevelSunset();
672                if(offsetByDegrees == null) {
673                        return Double.NaN;
674                }
675                Instant offsetByTime = getTimeOffset(getSeaLevelSunset(), minutes * MINUTE_MILLIS);
676                BigDecimal degrees = new BigDecimal(0);
677                BigDecimal incrementor = new BigDecimal("0.001");
678                while (offsetByDegrees == null || ((minutes > 0.0 && offsetByDegrees.toEpochMilli() < offsetByTime.toEpochMilli()) ||
679                                (minutes < 0.0 && offsetByDegrees.toEpochMilli() > offsetByTime.toEpochMilli()))) {
680                        if (minutes > 0.0) {
681                                degrees = degrees.add(incrementor);
682                        } else {
683                                degrees = degrees.subtract(incrementor);
684                        }
685                        offsetByDegrees = getSunsetOffsetByDegrees(GEOMETRIC_ZENITH + degrees.doubleValue());
686                }
687                return degrees.doubleValue();
688        }
689        
690        /**
691         * A method that returns <a href="https://en.wikipedia.org/wiki/Local_mean_time">local mean time (LMT)</a> time converted to
692         * regular clock time for the local wall-clock time passed to this method. This time is adjusted from standard time to account for
693         * the local latitude. The 360&deg; of the globe divided by 24 calculates to 15&deg; per hour with 4 minutes per degree, so at a
694         * longitude of 0 , 15, 30 etc... noon is at exactly 12:00pm. Lakewood, N.J., with a longitude of -74.222, is 0.7906 away from the
695         * closest multiple of 15 at -75&deg;. This is multiplied by 4 clock minutes (per degree) to yield 3 minutes and 7 seconds for a
696         * noon time of 11:56:53am. This method is not tied to the theoretical 15&deg; time zones, but will adjust to the actual time zone
697         * and <a href="https://en.wikipedia.org/wiki/Daylight_saving_time">Daylight saving time</a> to return LMT.
698         * 
699         * @param localTime
700         *                      the local wall-clock time (such as 12:00 for noon and 00:00 for midnight) to calculate as LMT.
701         * @return the <code>Instant</code> representing the local mean time (LMT) for the time passed in. In Lakewood,
702         *         NJ, passing noon will return 11:56:50am.
703         * @see GeoLocation#getLocalMeanTimeOffset(Instant)
704         */
705        public Instant getLocalMeanTime(LocalTime localTime) {
706                Instant localMeanTime = LocalDateTime.of(getAdjustedLocalDate(), localTime).toInstant(ZoneOffset.UTC);
707            long longitudeOffsetMillis = (long) (getGeoLocation().getLongitude() * 4 * MINUTE_MILLIS);
708            return getTimeOffset(localMeanTime, -longitudeOffsetMillis);
709        }
710
711        /**
712         * Adjusts the <code>LocalDate</code> to deal with edge cases where the location crosses the antimeridian.
713         * 
714         * @see GeoLocation#getAntimeridianAdjustment(Instant)
715         * @return the adjusted Calendar
716         */
717        protected LocalDate getAdjustedLocalDate(){
718        int offset = getGeoLocation().getAntimeridianAdjustment(getMidnightLastNight().toInstant());
719        return offset == 0 ? getLocalDate() : getLocalDate().plusDays(offset);
720        }
721    
722    /**
723     * Used by Molad based <em>zmanim</em> to determine if <em>zmanim</em> occur during the current day. This is also used as the
724     * anchor for current timezone-offset calculations.
725     * @return midnight at the start of the current local date in the configured timezone
726     */
727    protected ZonedDateTime getMidnightLastNight() {
728        return ZonedDateTime.of(getLocalDate(),LocalTime.MIDNIGHT,getGeoLocation().getZoneId());
729    }
730
731    /**
732     * Used by Molad based <em>zmanim</em> to determine if <em>zmanim</em> occur during the current day.
733     * @return following midnight
734     */
735    protected ZonedDateTime getMidnightTonight() {
736        return ZonedDateTime.of(getLocalDate().plusDays(1),LocalTime.MIDNIGHT,getGeoLocation().getZoneId());
737    }
738
739        /**
740         * Returns an XML formatted representation of the class using the default output of the
741         *         {@link com.kosherjava.zmanim.util.ZmanimFormatter#toXML(AstronomicalCalendar) toXML} method.
742         * @return an XML formatted representation of the class. It returns the default output of the
743         *         {@link com.kosherjava.zmanim.util.ZmanimFormatter#toXML(AstronomicalCalendar) toXML} method.
744         * @see com.kosherjava.zmanim.util.ZmanimFormatter#toXML(AstronomicalCalendar)
745         * @see java.lang.Object#toString()
746         */
747        public String toString() {
748                return ZmanimFormatter.toXML(this);
749        }
750        
751        /**
752         * Returns a JSON formatted representation of the class using the default output of the
753         *         {@link com.kosherjava.zmanim.util.ZmanimFormatter#toJSON(AstronomicalCalendar) toJSON} method.
754         * @return a JSON formatted representation of the class. It returns the default output of the
755         *         {@link com.kosherjava.zmanim.util.ZmanimFormatter#toJSON(AstronomicalCalendar) toJSON} method.
756         * @see com.kosherjava.zmanim.util.ZmanimFormatter#toJSON(AstronomicalCalendar)
757         * @see java.lang.Object#toString()
758         */
759        public String toJSON() {
760                return ZmanimFormatter.toJSON(this);
761        }
762
763        /**
764         * @see java.lang.Object#equals(Object)
765         */
766        public boolean equals(Object object) {
767                if (this == object) {
768                        return true;
769                }
770                if (object == null || getClass() != object.getClass()) {
771                        return false;
772                }
773                AstronomicalCalendar aCal = (AstronomicalCalendar) object;
774                return Objects.equals(getLocalDate(), aCal.getLocalDate())
775                                && Objects.equals(getGeoLocation(), aCal.getGeoLocation())
776                                && Objects.equals(getAstronomicalCalculator(), aCal.getAstronomicalCalculator());
777        }
778
779        /**
780         * @see java.lang.Object#hashCode()
781         */
782        public int hashCode() {
783                int result = 17;
784                result = 37 * result + getClass().hashCode(); // needed or this and subclasses will return identical hash
785                result += 37 * result + Objects.hashCode(getLocalDate());
786                result += 37 * result + Objects.hashCode(getGeoLocation());
787                result += 37 * result + Objects.hashCode(getAstronomicalCalculator());
788                return result;
789        }
790
791        /**
792         * A method that returns the currently set {@link GeoLocation} which contains location information used for the
793         * astronomical calculations.
794         * 
795         * @return Returns the geoLocation.
796         */
797        public GeoLocation getGeoLocation() {
798                return this.geoLocation;
799        }
800
801        /**
802         * Sets the {@link GeoLocation} <code>Object</code> to be used for astronomical calculations.
803         * 
804         * @param geoLocation
805         *            The geoLocation to set.
806         * @todo Possibly adjust for horizon elevation. It may be smart to just have the calculator check the GeoLocation
807         *       though it doesn't really belong there.
808         */
809        public void setGeoLocation(GeoLocation geoLocation) {
810                this.geoLocation = geoLocation;
811        }
812
813        /**
814         * A method that returns the currently set AstronomicalCalculator.
815         * 
816         * @return Returns the astronomicalCalculator.
817         * @see setAstronomicalCalculator(AstronomicalCalculator)
818         */
819        public AstronomicalCalculator getAstronomicalCalculator() {
820                return this.astronomicalCalculator;
821        }
822
823        /**
824         * A method to set the {@link AstronomicalCalculator} used for astronomical calculations. The Zmanim package ships
825         * with a number of different implementations of the <code>abstract</code> {@link AstronomicalCalculator} based on
826         * different algorithms, including the default {@link com.kosherjava.zmanim.util.NOAACalculator} based on <a href=
827         * "https://noaa.gov">NOAA's</a> implementation of Jean Meeus's algorithms as well as {@link
828         * com.kosherjava.zmanim.util.SunTimesCalculator} based on the <a href = "https://www.cnmoc.usff.navy.mil/usno/">US
829         * Naval Observatory's</a> algorithm. This allows easy runtime switching and comparison of different algorithms.
830         * 
831         * @param astronomicalCalculator
832         *            The astronomicalCalculator to set.
833         */
834        public void setAstronomicalCalculator(AstronomicalCalculator astronomicalCalculator) {
835                this.astronomicalCalculator = astronomicalCalculator;
836        }
837        
838        /**
839         * returns the <code>LocalDate</code> object encapsulated in this class.
840         * 
841         * @return Returns the <code>LocalDate</code>.
842         */
843        public LocalDate getLocalDate() {
844                return this.localDate;
845        }
846        
847        /**
848         * Sets the <code>LocalDate</code>  object for us in this class.
849         * @param localDate
850         *            The <code>LocalDate</code> to set.
851         */
852        public void setLocalDate(LocalDate localDate) {
853                this.localDate = localDate;
854        }
855
856        /**
857         * A method that creates a <a href="https://en.wikipedia.org/wiki/Object_copy#Deep_copy">deep copy</a> of the object.
858         * 
859         * @see java.lang.Object#clone()
860         */
861        public Object clone() {
862                AstronomicalCalendar clone = null;
863                try {
864                        clone = (AstronomicalCalendar) super.clone();
865                } catch (CloneNotSupportedException cnse) {
866                        // Required by the compiler. Should never be reached since we implement clone()
867                }
868        if (clone != null) {
869                        clone.setGeoLocation((GeoLocation) getGeoLocation().clone()); // consider converting the GeoLocation class to be immutable to avoid the deep copy
870                        clone.setAstronomicalCalculator((AstronomicalCalculator) getAstronomicalCalculator().clone()); // likely not needed
871                }
872                return clone;
873        }
874}