001/*
002 * Zmanim Java API
003 * Copyright (C) 2004-2024 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 - 2024
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 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, SolarEvent.SUNSET);
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, SolarEvent.SUNRISE);
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, SolarEvent.SUNSET);
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 more accurate {@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, SolarEvent.NOON);
515        }
516
517        /**
518         * A method that returns solar midnight. It occurs when the Sun is <a href=
519         * "https://en.wikipedia.org/wiki/Transit_%28astronomy%29">transiting</a> the lower <a
520         * href="https://en.wikipedia.org/wiki/Meridian_%28astronomy%29">celestial meridian</a>, or when the sun is at it's
521         * <a href="https://en.wikipedia.org/wiki/Nadir">nadir</a>. The calculations used by this class depend on the {@link
522         * AstronomicalCalculator} used. If this calendar instance is {@link #setAstronomicalCalculator(AstronomicalCalculator)
523         * set} to use the {@link com.kosherjava.zmanim.util.NOAACalculator} (the default) it will calculate astronomical
524         * midnight. If the calendar instance is to use the {@link com.kosherjava.zmanim.util.SunTimesCalculator}, that does not
525         * have code to calculate astronomical noon, midnight is calculated as halfway between sea level sunrise and sea level
526         * sunset on the other side of the world (180&deg; awa)y, which can be slightly off the real transit time due to changes
527         * in declination (the lengthening or shortening day). See <a href=
528         * "https://kosherjava.com/2020/07/02/definition-of-chatzos/">The Definition of Chatzos</a> for details on the proper
529         * definition of solar noon / midday.
530         * 
531         * @return the <code>Date</code> representing Sun's lower transit. If the calculation can't be computed such as when using
532         *         the {@link com.kosherjava.zmanim.util.SunTimesCalculator USNO calculator} that does not support getting solar
533         *         midnight for the Arctic Circle (where there is at least one day a year where the sun does not rise, and one
534         *         where it does not set), a <code>null</code> will be returned. See detailed explanation on top of the page.
535         * 
536         * @see #getSunTransit()
537         * @see com.kosherjava.zmanim.util.NOAACalculator#getUTCNoon(Calendar, GeoLocation)
538         * @see com.kosherjava.zmanim.util.SunTimesCalculator#getUTCNoon(Calendar, GeoLocation)
539         */
540        public Date getSunLowerTransit() {
541                Calendar cal = getAdjustedCalendar();
542                GeoLocation lowerGeoLocation = (GeoLocation) getGeoLocation().clone();
543                double meridian = lowerGeoLocation.getLongitude();
544                double lowerMeridian = meridian + 180;
545                if (lowerMeridian > 180){
546                        lowerMeridian = lowerMeridian - 360;
547                        cal.add(Calendar.DAY_OF_MONTH, -1);
548                }
549                lowerGeoLocation.setLongitude(lowerMeridian);
550                double noon = getAstronomicalCalculator().getUTCNoon(cal, lowerGeoLocation);
551                return getDateFromTime(noon, SolarEvent.MIDNIGHT);
552        }
553        
554        /**
555         * A method that returns "solar" midnight, or the time when the sun is at its <a
556         * href="https://en.wikipedia.org/wiki/Nadir">nadir</a>. The current calculation is halfway between today and
557         * tomorrow's {@link #getSunTransit() sun transit}.
558         * 
559         * @return the <code>Date</code> of astronomical solar midnight. If the calculation can't be computed such as
560         *         when using the {@link com.kosherjava.zmanim.util.SunTimesCalculator USNO calculator} that does not
561         *         support getting solar noon for the Arctic Circle (where there is at least one day a year where the
562         *         sun does not rise, and one where it does not set), a <code>null</code> will be returned. See
563         *         detailed explanation on top of the page.
564         * @see com.kosherjava.zmanim.util.NOAACalculator#getUTCNoon(Calendar, GeoLocation)
565         * @see com.kosherjava.zmanim.util.SunTimesCalculator#getUTCNoon(Calendar, GeoLocation)
566         */
567        public Date getSolarMidnight() {
568                AstronomicalCalendar clonedCal = (AstronomicalCalendar) clone();
569                clonedCal.getCalendar().add(Calendar.DATE, 1);
570                return getTimeOffset(getSunTransit(), (clonedCal.getSunTransit().getTime() - getSunTransit().getTime()) / 2);
571        }
572
573        /**
574         * A method that returns sundial or solar noon. It occurs when the Sun is <a href
575         * ="https://en.wikipedia.org/wiki/Transit_%28astronomy%29">transiting</a> the <a
576         * href="https://en.wikipedia.org/wiki/Meridian_%28astronomy%29">celestial meridian</a>. In this class it is
577         * calculated as halfway between the sunrise and sunset passed to this method. This time can be slightly off the
578         * real transit time due to changes in declination (the lengthening or shortening day).
579         * 
580         * @param startOfDay
581         *            the start of day for calculating the sun's transit. This can be sea level sunrise, visual sunrise (or
582         *            any arbitrary start of day) passed to this method.
583         * @param endOfDay
584         *            the end of day for calculating the sun's transit. This can be sea level sunset, visual sunset (or any
585         *            arbitrary end of day) passed to this method.
586         * 
587         * @return the <code>Date</code> representing Sun's transit. If the calculation can't be computed such as in the
588         *         Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does
589         *         not set, <code>null</code> will be returned. See detailed explanation on top of the page.
590         */
591        public Date getSunTransit(Date startOfDay, Date endOfDay) {
592                long temporalHour = getTemporalHour(startOfDay, endOfDay);
593                return getTimeOffset(startOfDay, temporalHour * 6);
594        }
595
596        /**
597         * An enum to indicate what type of solar event is being calculated.
598         */
599        protected enum SolarEvent {
600                /**SUNRISE A solar event related to sunrise*/SUNRISE, /**SUNSET A solar event related to sunset*/SUNSET,
601                /**NOON A solar event related to noon*/NOON, /**MIDNIGHT A solar event related to midnight*/MIDNIGHT
602        }
603        /**
604         * A method that returns a <code>Date</code> from the time passed in as a parameter.
605         * 
606         * @param time
607         *            The time to be set as the time for the <code>Date</code>. The time expected is in the format: 18.75
608         *            for 6:45:00 PM.time is sunrise and false if it is sunset
609         * @param solarEvent the type of {@link SolarEvent}
610         * @return The Date object representation of the time double
611         */
612        protected Date getDateFromTime(double time, SolarEvent solarEvent) {
613                if (Double.isNaN(time)) {
614                        return null;
615                }
616                double calculatedTime = time;
617                
618                Calendar adjustedCalendar = getAdjustedCalendar();
619                Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
620                cal.clear();// clear all fields
621                cal.set(Calendar.YEAR, adjustedCalendar.get(Calendar.YEAR));
622                cal.set(Calendar.MONTH, adjustedCalendar.get(Calendar.MONTH));
623                cal.set(Calendar.DAY_OF_MONTH, adjustedCalendar.get(Calendar.DAY_OF_MONTH));
624
625                int hours = (int) calculatedTime; // retain only the hours
626                calculatedTime -= hours;
627                int minutes = (int) (calculatedTime *= 60); // retain only the minutes
628                calculatedTime -= minutes;
629                int seconds = (int) (calculatedTime *= 60); // retain only the seconds
630                calculatedTime -= seconds; // remaining milliseconds
631                
632                // Check if a date transition has occurred, or is about to occur - this indicates the date of the event is
633                // actually not the target date, but the day prior or after
634                int localTimeHours = (int)getGeoLocation().getLongitude() / 15;
635                if (solarEvent == SolarEvent.SUNRISE && localTimeHours + hours > 18) {
636                        cal.add(Calendar.DAY_OF_MONTH, -1);
637                } else if (solarEvent == SolarEvent.SUNSET && localTimeHours + hours < 6) {
638                        cal.add(Calendar.DAY_OF_MONTH, 1);
639                } else if (solarEvent == SolarEvent.MIDNIGHT && localTimeHours + hours > 12) {
640                        cal.add(Calendar.DAY_OF_MONTH, -1);
641                }
642
643                cal.set(Calendar.HOUR_OF_DAY, hours);
644                cal.set(Calendar.MINUTE, minutes);
645                cal.set(Calendar.SECOND, seconds);
646                cal.set(Calendar.MILLISECOND, (int) (calculatedTime * 1000));
647                return cal.getTime();
648        }
649
650        /**
651         * Returns the dip below the horizon before sunrise that matches the offset minutes on passed in as a parameter. For
652         * example passing in 72 minutes for a calendar set to the equinox in Jerusalem returns a value close to 16.1&deg;
653         * Please note that this method is very slow and inefficient and should NEVER be used in a loop.
654         * @todo Improve efficiency of this method by not brute forcing the calculation.
655         * 
656         * @param minutes
657         *            offset
658         * @return the degrees below the horizon before sunrise that match the offset in minutes passed it as a parameter.
659         * @see #getSunsetSolarDipFromOffset(double)
660         */
661        public double getSunriseSolarDipFromOffset(double minutes) {
662                Date offsetByDegrees = getSeaLevelSunrise();
663                Date offsetByTime = getTimeOffset(getSeaLevelSunrise(), -(minutes * MINUTE_MILLIS));
664
665                BigDecimal degrees = new BigDecimal(0);
666                BigDecimal incrementor = new BigDecimal("0.0001");
667
668                while (offsetByDegrees == null || ((minutes < 0.0 && offsetByDegrees.getTime() < offsetByTime.getTime()) ||
669                                (minutes > 0.0 && offsetByDegrees.getTime() > offsetByTime.getTime()))) {
670                        if (minutes > 0.0) {
671                                degrees = degrees.add(incrementor);
672                        } else {
673                                degrees = degrees.subtract(incrementor);
674                        }
675                        offsetByDegrees = getSunriseOffsetByDegrees(GEOMETRIC_ZENITH + degrees.doubleValue());
676                }
677                return degrees.doubleValue();
678        }
679
680        /**
681         * Returns the dip below the horizon after sunset that matches the offset minutes on passed in as a parameter. For
682         * example passing in 72 minutes for a calendar set to the equinox in Jerusalem returns a value close to 16.1&deg;
683         * Please note that this method is very slow and inefficient and should NEVER be used in a loop.
684         * @todo Improve efficiency of this method by not brute forcing the calculation.
685         * 
686         * @param minutes
687         *            offset
688         * @return the degrees below the horizon after sunset that match the offset in minutes passed it as a parameter.
689         * @see #getSunriseSolarDipFromOffset(double)
690         */
691        public double getSunsetSolarDipFromOffset(double minutes) {
692                Date offsetByDegrees = getSeaLevelSunset();
693                Date offsetByTime = getTimeOffset(getSeaLevelSunset(), minutes * MINUTE_MILLIS);
694                BigDecimal degrees = new BigDecimal(0);
695                BigDecimal incrementor = new BigDecimal("0.001");
696                while (offsetByDegrees == null || ((minutes > 0.0 && offsetByDegrees.getTime() < offsetByTime.getTime()) ||
697                                (minutes < 0.0 && offsetByDegrees.getTime() > offsetByTime.getTime()))) {
698                        if (minutes > 0.0) {
699                                degrees = degrees.add(incrementor);
700                        } else {
701                                degrees = degrees.subtract(incrementor);
702                        }
703                        offsetByDegrees = getSunsetOffsetByDegrees(GEOMETRIC_ZENITH + degrees.doubleValue());
704                }
705                return degrees.doubleValue();
706        }
707        
708        /**
709         * A method that returns <a href="https://en.wikipedia.org/wiki/Local_mean_time">local mean time (LMT)</a> time
710         * converted to regular clock time for the number of hours (0.0 to 23.999...) passed to this method. This time is
711         * adjusted from standard time to account for the local latitude. The 360&deg; of the globe divided by 24 calculates
712         * 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.
713         * Lakewood, N.J., with a longitude of -74.222, is 0.7906 away from the closest multiple of 15 at -75&deg;. This is
714         * multiplied by 4 clock minutes (per degree) to yield 3 minutes and 7 seconds for a noon time of 11:56:53am. This
715         * method is not tied to the theoretical 15&deg; time zones, but will adjust to the actual time zone and <a href=
716         * "https://en.wikipedia.org/wiki/Daylight_saving_time">Daylight saving time</a> to return LMT.
717         * 
718         * @param hours
719         *                      the hour (such as 12.0 for noon and 0.0 for midnight) to calculate as LMT. Valid values are in the range of
720         *                      0.0 to 23.999.... An IllegalArgumentException will be thrown if the value does not fit in the expected range.
721         * @return the Date representing the local mean time (LMT) for the number of hours passed in. In Lakewood, NJ, passing 12
722         *         (noon) will return 11:56:50am.
723         * @see GeoLocation#getLocalMeanTimeOffset()
724         */
725        public Date getLocalMeanTime(double hours) {
726                if (hours < 0 || hours >= 24) {
727                        throw new IllegalArgumentException("Hours must between 0 and 23.9999...");
728                }
729                return getTimeOffset(getDateFromTime(hours - getGeoLocation().getTimeZone().getRawOffset()
730                                / (double) HOUR_MILLIS, SolarEvent.SUNRISE), -getGeoLocation().getLocalMeanTimeOffset());
731        }
732        
733        /**
734         * Adjusts the <code>Calendar</code> to deal with edge cases where the location crosses the antimeridian.
735         * 
736         * @see GeoLocation#getAntimeridianAdjustment()
737         * @return the adjusted Calendar
738         */
739        private Calendar getAdjustedCalendar(){
740                int offset = getGeoLocation().getAntimeridianAdjustment();
741                if (offset == 0) {
742                        return getCalendar();
743                }
744                Calendar adjustedCalendar = (Calendar) getCalendar().clone();
745                adjustedCalendar.add(Calendar.DAY_OF_MONTH, offset);
746                return adjustedCalendar;
747        }
748
749        /**
750         * @return an XML formatted representation of the class. It returns the default output of the
751         *         {@link com.kosherjava.zmanim.util.ZmanimFormatter#toXML(AstronomicalCalendar) toXML} method.
752         * @see com.kosherjava.zmanim.util.ZmanimFormatter#toXML(AstronomicalCalendar)
753         * @see java.lang.Object#toString()
754         */
755        public String toString() {
756                return ZmanimFormatter.toXML(this);
757        }
758        
759        /**
760         * @return a JSON formatted representation of the class. It returns the default output of the
761         *         {@link com.kosherjava.zmanim.util.ZmanimFormatter#toJSON(AstronomicalCalendar) toJSON} method.
762         * @see com.kosherjava.zmanim.util.ZmanimFormatter#toJSON(AstronomicalCalendar)
763         * @see java.lang.Object#toString()
764         */
765        public String toJSON() {
766                return ZmanimFormatter.toJSON(this);
767        }
768
769        /**
770         * @see java.lang.Object#equals(Object)
771         */
772        public boolean equals(Object object) {
773                if (this == object) {
774                        return true;
775                }
776                if (!(object instanceof AstronomicalCalendar)) {
777                        return false;
778                }
779                AstronomicalCalendar aCal = (AstronomicalCalendar) object;
780                return getCalendar().equals(aCal.getCalendar()) && getGeoLocation().equals(aCal.getGeoLocation())
781                                && getAstronomicalCalculator().equals(aCal.getAstronomicalCalculator());
782        }
783
784        /**
785         * @see java.lang.Object#hashCode()
786         */
787        public int hashCode() {
788                int result = 17;
789                result = 37 * result + getClass().hashCode(); // needed or this and subclasses will return identical hash
790                result += 37 * result + getCalendar().hashCode();
791                result += 37 * result + getGeoLocation().hashCode();
792                result += 37 * result + getAstronomicalCalculator().hashCode();
793                return result;
794        }
795
796        /**
797         * A method that returns the currently set {@link GeoLocation} which contains location information used for the
798         * astronomical calculations.
799         * 
800         * @return Returns the geoLocation.
801         */
802        public GeoLocation getGeoLocation() {
803                return this.geoLocation;
804        }
805
806        /**
807         * Sets the {@link GeoLocation} <code>Object</code> to be used for astronomical calculations.
808         * 
809         * @param geoLocation
810         *            The geoLocation to set.
811         * @todo Possibly adjust for horizon elevation. It may be smart to just have the calculator check the GeoLocation
812         *       though it doesn't really belong there.
813         */
814        public void setGeoLocation(GeoLocation geoLocation) {
815                this.geoLocation = geoLocation;
816                getCalendar().setTimeZone(geoLocation.getTimeZone()); 
817        }
818
819        /**
820         * A method that returns the currently set AstronomicalCalculator.
821         * 
822         * @return Returns the astronomicalCalculator.
823         * @see #setAstronomicalCalculator(AstronomicalCalculator)
824         */
825        public AstronomicalCalculator getAstronomicalCalculator() {
826                return this.astronomicalCalculator;
827        }
828
829        /**
830         * A method to set the {@link AstronomicalCalculator} used for astronomical calculations. The Zmanim package ships
831         * with a number of different implementations of the <code>abstract</code> {@link AstronomicalCalculator} based on
832         * different algorithms, including the default {@link com.kosherjava.zmanim.util.NOAACalculator} based on <a href=
833         * "https://noaa.gov">NOAA's</a> implementation of Jean Meeus's algorithms as well as {@link
834         * com.kosherjava.zmanim.util.SunTimesCalculator} based on the <a href = "https://www.cnmoc.usff.navy.mil/usno/">US
835         * Naval Observatory's</a> algorithm. This allows easy runtime switching and comparison of different algorithms.
836         * 
837         * @param astronomicalCalculator
838         *            The astronomicalCalculator to set.
839         */
840        public void setAstronomicalCalculator(AstronomicalCalculator astronomicalCalculator) {
841                this.astronomicalCalculator = astronomicalCalculator;
842        }
843
844        /**
845         * returns the Calendar object encapsulated in this class.
846         * 
847         * @return Returns the calendar.
848         */
849        public Calendar getCalendar() {
850                return this.calendar;
851        }
852
853        /**
854         * @param calendar
855         *            The calendar to set.
856         */
857        public void setCalendar(Calendar calendar) {
858                this.calendar = calendar;
859                if (getGeoLocation() != null) {// if available set the Calendar's timezone to the GeoLocation TimeZone
860                        getCalendar().setTimeZone(getGeoLocation().getTimeZone());
861                }
862        }
863
864        /**
865         * A method that creates a <a href="https://en.wikipedia.org/wiki/Object_copy#Deep_copy">deep copy</a> of the object.
866         * <b>Note:</b> If the {@link java.util.TimeZone} in the cloned {@link com.kosherjava.zmanim.util.GeoLocation} will
867         * be changed from the original, it is critical that
868         * {@link com.kosherjava.zmanim.AstronomicalCalendar#getCalendar()}.
869         * {@link java.util.Calendar#setTimeZone(TimeZone) setTimeZone(TimeZone)} be called in order for the
870         * AstronomicalCalendar to output times in the expected offset after being cloned.
871         * 
872         * @see java.lang.Object#clone()
873         */
874        public Object clone() {
875                AstronomicalCalendar clone = null;
876                try {
877                        clone = (AstronomicalCalendar) super.clone();
878                } catch (CloneNotSupportedException cnse) {
879                        // Required by the compiler. Should never be reached since we implement clone()
880                }
881        if (clone != null) {
882                        clone.setGeoLocation((GeoLocation) getGeoLocation().clone());
883                        clone.setCalendar((Calendar) getCalendar().clone());
884                        clone.setAstronomicalCalculator((AstronomicalCalculator) getAstronomicalCalculator().clone());
885                }
886                return clone;
887        }
888}