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