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