001/*
002 * Zmanim Java API
003 * Copyright (C) 2004-2023 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 * 
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 - 2023
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, true);
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, true);
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, false);
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 2see {@link #getSunset()}
233         */
234        public Date getSeaLevelSunset() {
235                double sunset = getUTCSeaLevelSunset(GEOMETRIC_ZENITH);
236                if (Double.isNaN(sunset)) {
237                        return null;
238                } else {
239                        return getDateFromTime(sunset, false);
240                }
241        }
242
243        /**
244         * A method that returns the end of <a href="https://en.wikipedia.org/wiki/Twilight#Civil_twilight">civil twilight</a>
245         * using a zenith of {@link #CIVIL_ZENITH 96&deg;}.
246         * 
247         * @return The <code>Date</code> of the end of civil twilight using a zenith of {@link #CIVIL_ZENITH 96&deg;}. If the
248         *         calculation can't be computed, <code>null</code> will be returned. See detailed explanation on top of the page.
249         * @see #CIVIL_ZENITH
250         */
251        public Date getEndCivilTwilight() {
252                return getSunsetOffsetByDegrees(CIVIL_ZENITH);
253        }
254
255        /**
256         * A method that returns the end of nautical twilight using a zenith of {@link #NAUTICAL_ZENITH 102&deg;}.
257         * 
258         * @return The <code>Date</code> of the end of nautical twilight using a zenith of {@link #NAUTICAL_ZENITH 102&deg;}. If
259         *         the calculation can't be computed, <code>null</code> will be returned. See detailed explanation on top of the
260         *         page.
261         * @see #NAUTICAL_ZENITH
262         */
263        public Date getEndNauticalTwilight() {
264                return getSunsetOffsetByDegrees(NAUTICAL_ZENITH);
265        }
266
267        /**
268         * A method that returns the end of astronomical twilight using a zenith of {@link #ASTRONOMICAL_ZENITH 108&deg;}.
269         * 
270         * @return the <code>Date</code> of the end of astronomical twilight using a zenith of {@link #ASTRONOMICAL_ZENITH
271         *         108&deg;}. If the calculation can't be computed, <code>null</code> will be returned. See detailed
272         *         explanation on top of the page.
273         * @see #ASTRONOMICAL_ZENITH
274         */
275        public Date getEndAstronomicalTwilight() {
276                return getSunsetOffsetByDegrees(ASTRONOMICAL_ZENITH);
277        }
278
279        /**
280         * A utility method that returns a date offset by the offset time passed in as a parameter. This method casts the
281         * offset as a <code>long</code> and calls {@link #getTimeOffset(Date, long)}.
282         * 
283         * @param time
284         *            the start time
285         * @param offset
286         *            the offset in milliseconds to add to the time
287         * @return the {@link java.util.Date}with the offset added to it
288         */
289        public static Date getTimeOffset(Date time, double offset) {
290                return getTimeOffset(time, (long) offset);
291        }
292
293        /**
294         * A utility method that returns a date offset by the offset time passed in. Please note that the level of light
295         * during twilight is not affected by elevation, so if this is being used to calculate an offset before sunrise or
296         * after sunset with the intent of getting a rough "level of light" calculation, the sunrise or sunset time passed
297         * to this method should be sea level sunrise and sunset.
298         * 
299         * @param time
300         *            the start time
301         * @param offset
302         *            the offset in milliseconds to add to the time.
303         * @return the {@link java.util.Date} with the offset in milliseconds added to it
304         */
305        public static Date getTimeOffset(Date time, long offset) {
306                if (time == null || offset == Long.MIN_VALUE) {
307                        return null;
308                }
309                return new Date(time.getTime() + offset);
310        }
311
312        /**
313         * A utility method that returns the time of an offset by degrees below or above the horizon of
314         * {@link #getSunrise() sunrise}. Note that the degree offset is from the vertical, so for a calculation of 14&deg;
315         * before sunrise, an offset of 14 + {@link #GEOMETRIC_ZENITH} = 104 would have to be passed as a parameter.
316         * 
317         * @param offsetZenith
318         *            the degrees before {@link #getSunrise()} to use in the calculation. For time after sunrise use
319         *            negative numbers. Note that the degree offset is from the vertical, so for a calculation of 14&deg;
320         *            before sunrise, an offset of 14 + {@link #GEOMETRIC_ZENITH} = 104 would have to be passed as a
321         *            parameter.
322         * @return The {@link java.util.Date} of the offset after (or before) {@link #getSunrise()}. If the calculation
323         *         can't be computed such as in the Arctic Circle where there is at least one day a year where the sun does
324         *         not rise, and one where it does not set, a <code>null</code> will be returned. See detailed explanation
325         *         on top of the page.
326         */
327        public Date getSunriseOffsetByDegrees(double offsetZenith) {
328                double dawn = getUTCSunrise(offsetZenith);
329                if (Double.isNaN(dawn)) {
330                        return null;
331                } else {
332                        return getDateFromTime(dawn, true);
333                }
334        }
335
336        /**
337         * A utility method that returns the time of an offset by degrees below or above the horizon of {@link #getSunset()
338         * sunset}. Note that the degree offset is from the vertical, so for a calculation of 14&deg; after sunset, an
339         * offset of 14 + {@link #GEOMETRIC_ZENITH} = 104 would have to be passed as a parameter.
340         * 
341         * @param offsetZenith
342         *            the degrees after {@link #getSunset()} to use in the calculation. For time before sunset use negative
343         *            numbers. Note that the degree offset is from the vertical, so for a calculation of 14&deg; after
344         *            sunset, an offset of 14 + {@link #GEOMETRIC_ZENITH} = 104 would have to be passed as a parameter.
345         * @return The {@link java.util.Date}of the offset after (or before) {@link #getSunset()}. If the calculation can't
346         *         be computed such as in the Arctic Circle where there is at least one day a year where the sun does not
347         *         rise, and one where it does not set, a <code>null</code> will be returned. See detailed explanation on
348         *         top of the page.
349         */
350        public Date getSunsetOffsetByDegrees(double offsetZenith) {
351                double sunset = getUTCSunset(offsetZenith);
352                if (Double.isNaN(sunset)) {
353                        return null;
354                } else {
355                        return getDateFromTime(sunset, false);
356                }
357        }
358
359        /**
360         * Default constructor will set a default {@link GeoLocation#GeoLocation()}, a default
361         * {@link AstronomicalCalculator#getDefault() AstronomicalCalculator} and default the calendar to the current date.
362         */
363        public AstronomicalCalendar() {
364                this(new GeoLocation());
365        }
366
367        /**
368         * A constructor that takes in <a href="https://en.wikipedia.org/wiki/Geolocation">geolocation</a> information as a
369         * parameter. The default {@link AstronomicalCalculator#getDefault() AstronomicalCalculator} used for solar
370         * calculations is the the {@link com.kosherjava.zmanim.util.NOAACalculator}.
371         * 
372         * @param geoLocation
373         *            The location information used for calculating astronomical sun times.
374         *
375         * @see #setAstronomicalCalculator(AstronomicalCalculator) for changing the calculator class.
376         */
377        public AstronomicalCalendar(GeoLocation geoLocation) {
378                setCalendar(Calendar.getInstance(geoLocation.getTimeZone()));
379                setGeoLocation(geoLocation);// duplicate call
380                setAstronomicalCalculator(AstronomicalCalculator.getDefault());
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.
386         * 
387         * @param zenith
388         *            the degrees below the horizon. For time after sunrise use negative numbers.
389         * @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
390         *         Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does
391         *         not set, {@link Double#NaN} will be returned. See detailed explanation on top of the page.
392         */
393        public double getUTCSunrise(double zenith) {
394                return getAstronomicalCalculator().getUTCSunrise(getAdjustedCalendar(), getGeoLocation(), zenith, true);
395        }
396
397        /**
398         * A method that returns the sunrise in UTC time without correction for time zone offset from GMT and without using
399         * daylight savings time. Non-sunrise and sunset calculations such as dawn and dusk, depend on the amount of visible
400         * light, something that is not affected by elevation. This method returns UTC sunrise calculated at sea level. This
401         * forms the base for dawn calculations that are calculated as a dip below the horizon before sunrise.
402         * 
403         * @param zenith
404         *            the degrees below the horizon. For time after sunrise use negative numbers.
405         * @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
406         *         Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does
407         *         not set, {@link Double#NaN} will be returned. See detailed explanation on top of the page.
408         * @see AstronomicalCalendar#getUTCSunrise
409         * @see AstronomicalCalendar#getUTCSeaLevelSunset
410         */
411        public double getUTCSeaLevelSunrise(double zenith) {
412                return getAstronomicalCalculator().getUTCSunrise(getAdjustedCalendar(), getGeoLocation(), zenith, false);
413        }
414
415        /**
416         * A method that returns the sunset in UTC time without correction for time zone offset from GMT and without using
417         * daylight savings time.
418         * 
419         * @param zenith
420         *            the degrees below the horizon. For time after sunset use negative numbers.
421         * @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
422         *         Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does
423         *         not set, {@link Double#NaN} will be returned. See detailed explanation on top of the page.
424         * @see AstronomicalCalendar#getUTCSeaLevelSunset
425         */
426        public double getUTCSunset(double zenith) {
427                return getAstronomicalCalculator().getUTCSunset(getAdjustedCalendar(), getGeoLocation(), zenith, true);
428        }
429
430        /**
431         * A method that returns the sunset in UTC time without correction for elevation, time zone offset from GMT and
432         * without using daylight savings time. Non-sunrise and sunset calculations such as dawn and dusk, depend on the
433         * amount of visible light, something that is not affected by elevation. This method returns UTC sunset calculated
434         * at sea level. This forms the base for dusk calculations that are calculated as a dip below the horizon after
435         * sunset.
436         * 
437         * @param zenith
438         *            the degrees below the horizon. For time before sunset use negative numbers.
439         * @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
440         *         Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does
441         *         not set, {@link Double#NaN} will be returned. See detailed explanation on top of the page.
442         * @see AstronomicalCalendar#getUTCSunset
443         * @see AstronomicalCalendar#getUTCSeaLevelSunrise
444         */
445        public double getUTCSeaLevelSunset(double zenith) {
446                return getAstronomicalCalculator().getUTCSunset(getAdjustedCalendar(), getGeoLocation(), zenith, false);
447        }
448
449        /**
450         * A method that returns an {@link AstronomicalCalculator#getElevationAdjustment(double) elevation adjusted}
451         * temporal (solar) hour. The day from {@link #getSunrise() sunrise} to {@link #getSunset() sunset} is split into 12
452         * equal parts with each one being a temporal hour.
453         * 
454         * @see #getSunrise()
455         * @see #getSunset()
456         * @see #getTemporalHour(Date, Date)
457         * 
458         * @return the <code>long</code> millisecond length of a temporal hour. If the calculation can't be computed,
459         *         {@link Long#MIN_VALUE} will be returned. See detailed explanation on top of the page.
460         * 
461         * @see #getTemporalHour(Date, Date)
462         */
463        public long getTemporalHour() {
464                return getTemporalHour(getSeaLevelSunrise(), getSeaLevelSunset());
465        }
466
467        /**
468         * A utility method that will allow the calculation of a temporal (solar) hour based on the sunrise and sunset
469         * passed as parameters to this method. An example of the use of this method would be the calculation of a
470         * non-elevation adjusted temporal hour by passing in {@link #getSeaLevelSunrise() sea level sunrise} and
471         * {@link #getSeaLevelSunset() sea level sunset} as parameters.
472         * 
473         * @param startOfday
474         *            The start of the day.
475         * @param endOfDay
476         *            The end of the day.
477         * 
478         * @return the <code>long</code> millisecond length of the temporal hour. If the calculation can't be computed a
479         *         {@link Long#MIN_VALUE} will be returned. See detailed explanation on top of the page.
480         * 
481         * @see #getTemporalHour()
482         */
483        public long getTemporalHour(Date startOfday, Date endOfDay) {
484                if (startOfday == null || endOfDay == null) {
485                        return Long.MIN_VALUE;
486                }
487                return (endOfDay.getTime() - startOfday.getTime()) / 12;
488        }
489
490        /**
491         * A method that returns sundial or solar noon. It occurs when the Sun is <a href=
492         * "https://en.wikipedia.org/wiki/Transit_%28astronomy%29">transiting</a> the <a
493         * href="https://en.wikipedia.org/wiki/Meridian_%28astronomy%29">celestial meridian</a>. The calculations used by
494         * this class depend on the {@link AstronomicalCalculator} used. If this calendar instance is {@link
495         * #setAstronomicalCalculator(AstronomicalCalculator) set} to use the {@link com.kosherjava.zmanim.util.NOAACalculator}
496         * (the default) it will calculate astronomical noon. If the calendar instance is  to use the
497         * {@link com.kosherjava.zmanim.util.SunTimesCalculator}, that does not have code to calculate astronomical noon, the
498         * sun transit is calculated as halfway between sea level sunrise and sea level sunset, which can be slightly off the
499         * real transit time due to changes in declination (the lengthening or shortening day). See <a href=
500         * "https://kosherjava.com/2020/07/02/definition-of-chatzos/">The Definition of Chatzos</a> for details on the proper
501         * definition of solar noon / midday.
502         * 
503         * @return the <code>Date</code> representing Sun's transit. If the calculation can't be computed such as when using
504         *         the {@link com.kosherjava.zmanim.util.SunTimesCalculator USNO calculator} that does not support getting solar
505         *         noon for the Arctic Circle (where there is at least one day a year where the sun does not rise, and one where
506         *         it does not set), a <code>null</code> will be returned. See detailed explanation on top of the page.
507         * @see #getSunTransit(Date, Date)
508         * @see #getTemporalHour()
509         * @see com.kosherjava.zmanim.util.NOAACalculator#getUTCNoon(Calendar, GeoLocation)
510         * @see com.kosherjava.zmanim.util.SunTimesCalculator#getUTCNoon(Calendar, GeoLocation)
511         */
512        public Date getSunTransit() {
513                double noon = getAstronomicalCalculator().getUTCNoon(getAdjustedCalendar(), getGeoLocation());
514                return getDateFromTime(noon, false); 
515        }
516        
517        /**
518         * A method that returns "solar" midnight, or the time when the sun is at its <a
519         * href="https://en.wikipedia.org/wiki/Nadir">nadir</a>. The current calculation is halfway between today and
520         * tomorrow's {@link #getSunTransit() sun transit}.
521         * 
522         * @return the <code>Date</code> of astronomical solar midnight. If the calculation can't be computed such as
523         *         when using the {@link com.kosherjava.zmanim.util.SunTimesCalculator USNO calculator} that does not
524         *         support getting solar noon for the Arctic Circle (where there is at least one day a year where the
525         *         sun does not rise, and one where it does not set), a <code>null</code> will be returned. See
526         *         detailed explanation on top of the page.
527         * @see com.kosherjava.zmanim.util.NOAACalculator#getUTCNoon(Calendar, GeoLocation)
528         * @see com.kosherjava.zmanim.util.SunTimesCalculator#getUTCNoon(Calendar, GeoLocation)
529         */
530        public Date getSolarMidnight() {
531                System.out.println("getSolarMidnight()");
532                AstronomicalCalendar clonedCal = (AstronomicalCalendar) clone();
533                clonedCal.getCalendar().add(Calendar.DATE, 1);
534                return getTimeOffset(getSunTransit(), (clonedCal.getSunTransit().getTime() - getSunTransit().getTime()) / 2);
535        }
536
537        /**
538         * A method that returns sundial or solar noon. It occurs when the Sun is <a href
539         * ="https://en.wikipedia.org/wiki/Transit_%28astronomy%29">transiting</a> the <a
540         * href="https://en.wikipedia.org/wiki/Meridian_%28astronomy%29">celestial meridian</a>. In this class it is
541         * calculated as halfway between the sunrise and sunset passed to this method. This time can be slightly off the
542         * real transit time due to changes in declination (the lengthening or shortening day).
543         * 
544         * @param startOfDay
545         *            the start of day for calculating the sun's transit. This can be sea level sunrise, visual sunrise (or
546         *            any arbitrary start of day) passed to this method.
547         * @param endOfDay
548         *            the end of day for calculating the sun's transit. This can be sea level sunset, visual sunset (or any
549         *            arbitrary end of day) passed to this method.
550         * 
551         * @return the <code>Date</code> representing Sun's transit. If the calculation can't be computed such as in the
552         *         Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does
553         *         not set, <code>null</code> will be returned. See detailed explanation on top of the page.
554         */
555        public Date getSunTransit(Date startOfDay, Date endOfDay) {
556                long temporalHour = getTemporalHour(startOfDay, endOfDay);
557                return getTimeOffset(startOfDay, temporalHour * 6);
558        }
559
560        /**
561         * A method that returns a <code>Date</code> from the time passed in as a parameter.
562         * 
563         * @param time
564         *            The time to be set as the time for the <code>Date</code>. The time expected is in the format: 18.75
565         *            for 6:45:00 PM.time is sunrise and false if it is sunset
566         * @param isSunrise true if the 
567         * @return The Date.
568         */
569        protected Date getDateFromTime(double time, boolean isSunrise) {
570                if (Double.isNaN(time)) {
571                        return null;
572                }
573                double calculatedTime = time;
574                
575                Calendar adjustedCalendar = getAdjustedCalendar();
576                Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
577                cal.clear();// clear all fields
578                cal.set(Calendar.YEAR, adjustedCalendar.get(Calendar.YEAR));
579                cal.set(Calendar.MONTH, adjustedCalendar.get(Calendar.MONTH));
580                cal.set(Calendar.DAY_OF_MONTH, adjustedCalendar.get(Calendar.DAY_OF_MONTH));
581
582                int hours = (int) calculatedTime; // retain only the hours
583                calculatedTime -= hours;
584                int minutes = (int) (calculatedTime *= 60); // retain only the minutes
585                calculatedTime -= minutes;
586                int seconds = (int) (calculatedTime *= 60); // retain only the seconds
587                calculatedTime -= seconds; // remaining milliseconds
588                
589                // Check if a date transition has occurred, or is about to occur - this indicates the date of the event is
590                // actually not the target date, but the day prior or after
591                int localTimeHours = (int)getGeoLocation().getLongitude() / 15;
592                if (isSunrise && localTimeHours + hours > 18) {
593                        cal.add(Calendar.DAY_OF_MONTH, -1);
594                } else if (!isSunrise && localTimeHours + hours < 6) {
595                        cal.add(Calendar.DAY_OF_MONTH, 1);
596                }
597
598                cal.set(Calendar.HOUR_OF_DAY, hours);
599                cal.set(Calendar.MINUTE, minutes);
600                cal.set(Calendar.SECOND, seconds);
601                cal.set(Calendar.MILLISECOND, (int) (calculatedTime * 1000));
602                return cal.getTime();
603        }
604
605        /**
606         * Returns the dip below the horizon before sunrise that matches the offset minutes on passed in as a parameter. For
607         * example passing in 72 minutes for a calendar set to the equinox in Jerusalem returns a value close to 16.1&deg;
608         * Please note that this method is very slow and inefficient and should NEVER be used in a loop.
609         * @todo Improve efficiency of this method by not brute forcing the calculation.
610         * 
611         * @param minutes
612         *            offset
613         * @return the degrees below the horizon before sunrise that match the offset in minutes passed it as a parameter.
614         * @see #getSunsetSolarDipFromOffset(double)
615         */
616        public double getSunriseSolarDipFromOffset(double minutes) {
617                Date offsetByDegrees = getSeaLevelSunrise();
618                Date offsetByTime = getTimeOffset(getSeaLevelSunrise(), -(minutes * MINUTE_MILLIS));
619
620                BigDecimal degrees = new BigDecimal(0);
621                BigDecimal incrementor = new BigDecimal("0.0001");
622
623                while (offsetByDegrees == null || ((minutes < 0.0 && offsetByDegrees.getTime() < offsetByTime.getTime()) ||
624                                (minutes > 0.0 && offsetByDegrees.getTime() > offsetByTime.getTime()))) {
625                        if(minutes > 0.0) {
626                                degrees = degrees.add(incrementor);
627                        } else {
628                                degrees = degrees.subtract(incrementor);
629                        }
630                        offsetByDegrees = getSunriseOffsetByDegrees(GEOMETRIC_ZENITH + degrees.doubleValue());
631                }
632                return degrees.doubleValue();
633        }
634
635        /**
636         * Returns the dip below the horizon after sunset that matches the offset minutes on passed in as a parameter. For
637         * example passing in 72 minutes for a calendar set to the equinox in Jerusalem returns a value close to 16.1&deg;
638         * Please note that this method is very slow and inefficient and should NEVER be used in a loop.
639         * @todo Improve efficiency of this method by not brute forcing the calculation.
640         * 
641         * @param minutes
642         *            offset
643         * @return the degrees below the horizon after sunset that match the offset in minutes passed it as a parameter.
644         * @see #getSunriseSolarDipFromOffset(double)
645         */
646        public double getSunsetSolarDipFromOffset(double minutes) {
647                Date offsetByDegrees = getSeaLevelSunset();
648                Date offsetByTime = getTimeOffset(getSeaLevelSunset(), minutes * MINUTE_MILLIS);
649                BigDecimal degrees = new BigDecimal(0);
650                BigDecimal incrementor = new BigDecimal("0.001");
651                while (offsetByDegrees == null || ((minutes > 0.0 && offsetByDegrees.getTime() < offsetByTime.getTime()) ||
652                                (minutes < 0.0 && offsetByDegrees.getTime() > offsetByTime.getTime()))) {
653                        if(minutes > 0.0) {
654                                degrees = degrees.add(incrementor);
655                        } else {
656                                degrees = degrees.subtract(incrementor);
657                        }
658                        offsetByDegrees = getSunsetOffsetByDegrees(GEOMETRIC_ZENITH + degrees.doubleValue());
659                }
660                return degrees.doubleValue();
661        }
662        
663        /**
664         * A method that returns <a href="https://en.wikipedia.org/wiki/Local_mean_time">local mean time (LMT)</a> time
665         * converted to regular clock time for the number of hours (0.0 to 23.999...) passed to this method. This time is
666         * adjusted from standard time to account for the local latitude. The 360&deg; of the globe divided by 24 calculates
667         * 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.
668         * Lakewood, N.J., with a longitude of -74.222, is 0.7906 away from the closest multiple of 15 at -75&deg;. This is
669         * multiplied by 4 clock minutes (per degree) to yield 3 minutes and 7 seconds for a noon time of 11:56:53am. This
670         * method is not tied to the theoretical 15&deg; time zones, but will adjust to the actual time zone and <a href=
671         * "https://en.wikipedia.org/wiki/Daylight_saving_time">Daylight saving time</a> to return LMT.
672         * 
673         * @param hours
674         *                      the hour (such as 12.0 for noon and 0.0 for midnight) to calculate as LMT. Valid values are in the range of
675         *                      0.0 to 23.999.... An IllegalArgumentException will be thrown if the value does not fit in the expected range.
676         * @return the Date representing the local mean time (LMT) for the number of hours passed in. In Lakewood, NJ, passing 12
677         *         (noon) will return 11:56:50am.
678         * @see GeoLocation#getLocalMeanTimeOffset()
679         */
680        public Date getLocalMeanTime(double hours) {
681                if(hours < 0 || hours >= 24) {
682                        throw new IllegalArgumentException("Hours must between 0 and 23.9999...");
683                }
684                return getTimeOffset(getDateFromTime(hours - getGeoLocation().getTimeZone().getRawOffset()
685                                / (double) HOUR_MILLIS, true), -getGeoLocation().getLocalMeanTimeOffset());
686        }
687        
688        /**
689         * Adjusts the <code>Calendar</code> to deal with edge cases where the location crosses the antimeridian.
690         * 
691         * @see GeoLocation#getAntimeridianAdjustment()
692         * @return the adjusted Calendar
693         */
694        private Calendar getAdjustedCalendar(){
695                int offset = getGeoLocation().getAntimeridianAdjustment();
696                if (offset == 0) {
697                        return getCalendar();
698                }
699                Calendar adjustedCalendar = (Calendar) getCalendar().clone();
700                adjustedCalendar.add(Calendar.DAY_OF_MONTH, offset);
701                return adjustedCalendar;
702        }
703
704        /**
705         * @return an XML formatted representation of the class. It returns the default output of the
706         *         {@link com.kosherjava.zmanim.util.ZmanimFormatter#toXML(AstronomicalCalendar) toXML} method.
707         * @see com.kosherjava.zmanim.util.ZmanimFormatter#toXML(AstronomicalCalendar)
708         * @see java.lang.Object#toString()
709         */
710        public String toString() {
711                return ZmanimFormatter.toXML(this);
712        }
713        
714        /**
715         * @return a JSON formatted representation of the class. It returns the default output of the
716         *         {@link com.kosherjava.zmanim.util.ZmanimFormatter#toJSON(AstronomicalCalendar) toJSON} method.
717         * @see com.kosherjava.zmanim.util.ZmanimFormatter#toJSON(AstronomicalCalendar)
718         * @see java.lang.Object#toString()
719         */
720        public String toJSON() {
721                return ZmanimFormatter.toJSON(this);
722        }
723
724        /**
725         * @see java.lang.Object#equals(Object)
726         */
727        public boolean equals(Object object) {
728                if (this == object) {
729                        return true;
730                }
731                if (!(object instanceof AstronomicalCalendar)) {
732                        return false;
733                }
734                AstronomicalCalendar aCal = (AstronomicalCalendar) object;
735                return getCalendar().equals(aCal.getCalendar()) && getGeoLocation().equals(aCal.getGeoLocation())
736                                && getAstronomicalCalculator().equals(aCal.getAstronomicalCalculator());
737        }
738
739        /**
740         * @see java.lang.Object#hashCode()
741         */
742        public int hashCode() {
743                int result = 17;
744                result = 37 * result + getClass().hashCode(); // needed or this and subclasses will return identical hash
745                result += 37 * result + getCalendar().hashCode();
746                result += 37 * result + getGeoLocation().hashCode();
747                result += 37 * result + getAstronomicalCalculator().hashCode();
748                return result;
749        }
750
751        /**
752         * A method that returns the currently set {@link GeoLocation} which contains location information used for the
753         * astronomical calculations.
754         * 
755         * @return Returns the geoLocation.
756         */
757        public GeoLocation getGeoLocation() {
758                return this.geoLocation;
759        }
760
761        /**
762         * Sets the {@link GeoLocation} <code>Object</code> to be used for astronomical calculations.
763         * 
764         * @param geoLocation
765         *            The geoLocation to set.
766         * @todo Possibly adjust for horizon elevation. It may be smart to just have the calculator check the GeoLocation
767         *       though it doesn't really belong there.
768         */
769        public void setGeoLocation(GeoLocation geoLocation) {
770                this.geoLocation = geoLocation;
771                getCalendar().setTimeZone(geoLocation.getTimeZone()); 
772        }
773
774        /**
775         * A method that returns the currently set AstronomicalCalculator.
776         * 
777         * @return Returns the astronomicalCalculator.
778         * @see #setAstronomicalCalculator(AstronomicalCalculator)
779         */
780        public AstronomicalCalculator getAstronomicalCalculator() {
781                return this.astronomicalCalculator;
782        }
783
784        /**
785         * A method to set the {@link AstronomicalCalculator} used for astronomical calculations. The Zmanim package ships
786         * with a number of different implementations of the <code>abstract</code> {@link AstronomicalCalculator} based on
787         * different algorithms, including the default {@link com.kosherjava.zmanim.util.NOAACalculator} based on <a href=
788         * "https://noaa.gov">NOAA's</a> implementation of Jean Meeus's algorithms as well as {@link
789         * com.kosherjava.zmanim.util.SunTimesCalculator} based on the <a href = "https://www.cnmoc.usff.navy.mil/usno/">US
790         * Naval Observatory's</a> algorithm,. This allows easy runtime switching and comparison of different algorithms.
791         * 
792         * @param astronomicalCalculator
793         *            The astronomicalCalculator to set.
794         */
795        public void setAstronomicalCalculator(AstronomicalCalculator astronomicalCalculator) {
796                this.astronomicalCalculator = astronomicalCalculator;
797        }
798
799        /**
800         * returns the Calendar object encapsulated in this class.
801         * 
802         * @return Returns the calendar.
803         */
804        public Calendar getCalendar() {
805                return this.calendar;
806        }
807
808        /**
809         * @param calendar
810         *            The calendar to set.
811         */
812        public void setCalendar(Calendar calendar) {
813                this.calendar = calendar;
814                if (getGeoLocation() != null) {// if available set the Calendar's timezone to the GeoLocation TimeZone
815                        getCalendar().setTimeZone(getGeoLocation().getTimeZone());
816                }
817        }
818
819        /**
820         * A method that creates a <a href="https://en.wikipedia.org/wiki/Object_copy#Deep_copy">deep copy</a> of the object.
821         * <b>Note:</b> If the {@link java.util.TimeZone} in the cloned {@link com.kosherjava.zmanim.util.GeoLocation} will
822         * be changed from the original, it is critical that
823         * {@link com.kosherjava.zmanim.AstronomicalCalendar#getCalendar()}.
824         * {@link java.util.Calendar#setTimeZone(TimeZone) setTimeZone(TimeZone)} be called in order for the
825         * AstronomicalCalendar to output times in the expected offset after being cloned.
826         * 
827         * @see java.lang.Object#clone()
828         */
829        public Object clone() {
830                AstronomicalCalendar clone = null;
831                try {
832                        clone = (AstronomicalCalendar) super.clone();
833                } catch (CloneNotSupportedException cnse) {
834                        // Required by the compiler. Should never be reached since we implement clone()
835                }
836                clone.setGeoLocation((GeoLocation) getGeoLocation().clone());
837                clone.setCalendar((Calendar) getCalendar().clone());
838                clone.setAstronomicalCalculator((AstronomicalCalculator) getAstronomicalCalculator().clone());
839                return clone;
840        }
841}