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