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