001    /*
002     * Zmanim Java API
003     * Copyright (C) 2004-2011 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 - 2011
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             * {@link AstronomicalCalculator#getElevationAdjustment(double) elevation
136             * adjusted} sunrise time. The zenith used for the calculation uses
137             * {@link #GEOMETRIC_ZENITH geometric zenith} of 90&deg; plus
138             * {@link AstronomicalCalculator#getElevationAdjustment(double)}. This is
139             * adjusted by the {@link AstronomicalCalculator} to add approximately 50/60
140             * of a degree to account for 34 archminutes of refraction and 16
141             * archminutes for the sun's radius for a total of
142             * {@link AstronomicalCalculator#adjustZenith 90.83333&deg;}. See
143             * documentation for the specific implementation of the
144             * {@link AstronomicalCalculator} that you are using.
145             * 
146             * @return the <code>Date</code> representing the exact sunrise time. If the
147             *         calculation can't be computed such as in the Arctic Circle where
148             *         there is at least one day a year where the sun does not rise, and
149             *         one where it does not set, a null will be returned. See detailed
150             *         explanation on top of the page.
151             * @see AstronomicalCalculator#adjustZenith
152             * @see #getSeaLevelSunrise()
153             * @see AstronomicalCalendar#getUTCSunrise
154             */
155            public Date getSunrise() {
156                    double sunrise = getUTCSunrise(GEOMETRIC_ZENITH);
157                    if (Double.isNaN(sunrise)) {
158                            return null;
159                    } else {
160                            return getDateFromTime(sunrise);
161                    }
162            }
163    
164            /**
165             * Method that returns the sunrise without
166             * {@link AstronomicalCalculator#getElevationAdjustment(double) elevation
167             * adjustment}. Non-sunrise and sunset calculations such as dawn and dusk,
168             * depend on the amount of visible light, something that is not affected by
169             * elevation. This method returns sunrise calculated at sea level. This
170             * forms the base for dawn calculations that are calculated as a dip below
171             * the horizon before sunrise.
172             * 
173             * @return the <code>Date</code> representing the exact sea-level sunrise
174             *         time. If the calculation can't be computed such as in the Arctic
175             *         Circle where there is at least one day a year where the sun does
176             *         not rise, and one where it does not set, a null will be returned.
177             *         See detailed explanation on top of the page.
178             * @see AstronomicalCalendar#getSunrise
179             * @see AstronomicalCalendar#getUTCSeaLevelSunrise
180             * @see #getSeaLevelSunset()
181             */
182            public Date getSeaLevelSunrise() {
183                    double sunrise = getUTCSeaLevelSunrise(GEOMETRIC_ZENITH);
184                    if (Double.isNaN(sunrise)) {
185                            return null;
186                    } else {
187                            return getDateFromTime(sunrise);
188                    }
189            }
190    
191            /**
192             * A method to return the the beginning of civil twilight (dawn) using a
193             * zenith of {@link #CIVIL_ZENITH 96&deg;}.
194             * 
195             * @return The <code>Date</code> of the beginning of civil twilight using a
196             *         zenith of 96&deg;. If the calculation can't be computed (see
197             *         explanation on top of the page), null will be returned.
198             * @see #CIVIL_ZENITH
199             */
200            public Date getBeginCivilTwilight() {
201                    return getSunriseOffsetByDegrees(CIVIL_ZENITH);
202            }
203    
204            /**
205             * A method to return the the beginning of nautical twilight using a zenith
206             * of {@link #NAUTICAL_ZENITH 102&deg;}.
207             * 
208             * @return The <code>Date</code> of the beginning of nautical twilight using
209             *         a zenith of 102&deg;. If the calculation can't be computed (see
210             *         explanation on top of the page) null will be returned.
211             * @see #NAUTICAL_ZENITH
212             */
213            public Date getBeginNauticalTwilight() {
214                    return getSunriseOffsetByDegrees(NAUTICAL_ZENITH);
215            }
216    
217            /**
218             * A method that returns the the beginning of astronomical twilight using a
219             * zenith of {@link #ASTRONOMICAL_ZENITH 108&deg;}.
220             * 
221             * @return The <code>Date</code> of the beginning of astronomical twilight
222             *         using a zenith of 108&deg;. If the calculation can't be computed
223             *         (see explanation on top of the page), null will be returned.
224             * @see #ASTRONOMICAL_ZENITH
225             */
226            public Date getBeginAstronomicalTwilight() {
227                    return getSunriseOffsetByDegrees(ASTRONOMICAL_ZENITH);
228            }
229    
230            /**
231             * The getSunset method Returns a <code>Date</code> representing the
232             * {@link AstronomicalCalculator#getElevationAdjustment(double) elevation
233             * adjusted} sunset time. The zenith used for the calculation uses
234             * {@link #GEOMETRIC_ZENITH geometric zenith} of 90&deg; plus
235             * {@link AstronomicalCalculator#getElevationAdjustment(double)}. This is
236             * adjusted by the {@link AstronomicalCalculator} to add approximately 50/60
237             * of a degree to account for 34 archminutes of refraction and 16
238             * archminutes for the sun's radius for a total of
239             * {@link AstronomicalCalculator#adjustZenith 90.83333&deg;}. See
240             * documentation for the specific implementation of the
241             * {@link AstronomicalCalculator} that you are using. Note: In certain cases
242             * the calculates sunset will occur before sunrise. This will typically
243             * happen when a timezone other than the local timezone is used (calculating
244             * Los Angeles sunset using a GMT timezone for example). In this case the
245             * sunset date will be incremented to the following date.
246             * 
247             * @return the <code>Date</code> representing the exact sunset time. If the
248             *         calculation can't be computed such as in the Arctic Circle where
249             *         there is at least one day a year where the sun does not rise, and
250             *         one where it does not set, a null will be returned. See detailed
251             *         explanation on top of the page.
252             * @see AstronomicalCalculator#adjustZenith
253             * @see #getSeaLevelSunset()
254             * @see AstronomicalCalendar#getUTCSunset
255             */
256            public Date getSunset() {
257                    double sunset = getUTCSunset(GEOMETRIC_ZENITH);
258                    if (Double.isNaN(sunset)) {
259                            return null;
260                    } else {
261                            return getAdjustedSunsetDate(getDateFromTime(sunset), getSunrise());
262                    }
263            }
264    
265            /**
266             * A method that will roll the sunset time forward a day if sunset occurs
267             * before sunrise. This will typically happen when a timezone other than the
268             * local timezone is used (calculating Los Angeles sunset using a GMT
269             * timezone for example). In this case the sunset date will be incremented
270             * to the following date.
271             * 
272             * @param sunset
273             *            the sunset date to adjust if needed
274             * @param sunrise
275             *            the sunrise to compare to the sunset
276             * @return the adjusted sunset date. If the calculation can't be computed
277             *         such as in the Arctic Circle where there is at least one day a
278             *         year where the sun does not rise, and one where it does not set,
279             *         a null will be returned. See detailed explanation on top of the
280             *         page.
281             */
282            private Date getAdjustedSunsetDate(Date sunset, Date sunrise) {
283                    if (sunset != null && sunrise != null && sunrise.compareTo(sunset) >= 0) {
284                            Calendar clonedCalendar = (Calendar) getCalendar().clone();
285                            clonedCalendar.setTime(sunset);
286                            clonedCalendar.add(Calendar.DAY_OF_MONTH, 1);
287                            return clonedCalendar.getTime();
288                    } else {
289                            return sunset;
290                    }
291            }
292    
293            /**
294             * Method that returns the sunset without
295             * {@link AstronomicalCalculator#getElevationAdjustment(double) elevation
296             * adjustment}. Non-sunrise and sunset calculations such as dawn and dusk,
297             * depend on the amount of visible light, something that is not affected by
298             * elevation. This method returns sunset calculated at sea level. This forms
299             * the base for dusk calculations that are calculated as a dip below the
300             * horizon after sunset.
301             * 
302             * @return the <code>Date</code> representing the exact sea-level sunset
303             *         time. If the calculation can't be computed such as in the Arctic
304             *         Circle where there is at least one day a year where the sun does
305             *         not rise, and one where it does not set, a null will be returned.
306             *         See detailed explanation on top of the page.
307             * @see AstronomicalCalendar#getSunset
308             * @see AstronomicalCalendar#getUTCSeaLevelSunset 2see {@link #getSunset()}
309             */
310            public Date getSeaLevelSunset() {
311                    double sunset = getUTCSeaLevelSunset(GEOMETRIC_ZENITH);
312                    if (Double.isNaN(sunset)) {
313                            return null;
314                    } else {
315                            return getAdjustedSunsetDate(getDateFromTime(sunset),
316                                            getSeaLevelSunrise());
317                    }
318            }
319    
320            /**
321             * A method to return the the end of civil twilight using a zenith of
322             * {@link #CIVIL_ZENITH 96&deg;}.
323             * 
324             * @return The <code>Date</code> of the end of civil twilight using a zenith
325             *         of {@link #CIVIL_ZENITH 96&deg;}. If the calculation can't be
326             *         computed (see explanation on top of the page), null will be
327             *         returned.
328             * @see #CIVIL_ZENITH
329             */
330            public Date getEndCivilTwilight() {
331                    return getSunsetOffsetByDegrees(CIVIL_ZENITH);
332            }
333    
334            /**
335             * A method to return the the end of nautical twilight using a zenith of
336             * {@link #NAUTICAL_ZENITH 102&deg;}.
337             * 
338             * @return The <code>Date</code> of the end of nautical twilight using a
339             *         zenith of {@link #NAUTICAL_ZENITH 102&deg;}. If the calculation
340             *         can't be computed (see explanation on top of the page), null will
341             *         be returned.
342             * @see #NAUTICAL_ZENITH
343             */
344            public Date getEndNauticalTwilight() {
345                    return getSunsetOffsetByDegrees(NAUTICAL_ZENITH);
346            }
347    
348            /**
349             * A method to return the the end of astronomical twilight using a zenith of
350             * {@link #ASTRONOMICAL_ZENITH 108&deg;}.
351             * 
352             * @return The The <code>Date</code> of the end of astronomical twilight
353             *         using a zenith of {@link #ASTRONOMICAL_ZENITH 108&deg;}. If the
354             *         calculation can't be computed (see explanation on top of the
355             *         page), null will be returned.
356             * @see #ASTRONOMICAL_ZENITH
357             */
358            public Date getEndAstronomicalTwilight() {
359                    return getSunsetOffsetByDegrees(ASTRONOMICAL_ZENITH);
360            }
361    
362            /**
363             * Utility method that returns a date offset by the offset time passed in.
364             * This method casts the offset as a <code>long</code> and calls
365             * {@link #getTimeOffset(Date, long)}.
366             * 
367             * @param time
368             *            the start time
369             * @param offset
370             *            the offset in milliseconds to add to the time
371             * @return the {@link java.util.Date}with the offset added to it
372             */
373            public Date getTimeOffset(Date time, double offset) {
374                    return getTimeOffset(time, (long) offset);
375            }
376    
377            /**
378             * A utility method to return a date offset by the offset time passed in.
379             * 
380             * @param time
381             *            the start time
382             * @param offset
383             *            the offset in milliseconds to add to the time.
384             * @return the {@link java.util.Date} with the offset in milliseconds added
385             *         to it
386             */
387            public Date getTimeOffset(Date time, long offset) {
388                    if (time == null || offset == Long.MIN_VALUE) {
389                            return null;
390                    }
391                    return new Date(time.getTime() + offset);
392            }
393    
394            /**
395             * A utility method to return the time of an offset by degrees below or
396             * above the horizon of {@link #getSunrise() sunrise}.
397             * 
398             * @param offsetZenith
399             *            the degrees before {@link #getSunrise()} to use in the
400             *            calculation. For time after sunrise use negative numbers.
401             * @return The {@link java.util.Date} of the offset after (or before)
402             *         {@link #getSunrise()}. If the calculation can't be computed such
403             *         as in the Arctic Circle where there is at least one day a year
404             *         where the sun does not rise, and one where it does not set, a
405             *         null will be returned. See detailed explanation on top of the
406             *         page.
407             */
408            public Date getSunriseOffsetByDegrees(double offsetZenith) {
409                    double alos = getUTCSunrise(offsetZenith);
410                    if (Double.isNaN(alos)) {
411                            return null;
412                    } else {
413                            return getDateFromTime(alos);
414                    }
415            }
416    
417            /**
418             * A utility method to return the time of an offset by degrees below or
419             * above the horizon of {@link #getSunset() sunset}.
420             * 
421             * @param offsetZenith
422             *            the degrees after {@link #getSunset()} to use in the
423             *            calculation. For time before sunset use negative numbers.
424             * @return The {@link java.util.Date}of the offset after (or before)
425             *         {@link #getSunset()}. If the calculation can't be computed such
426             *         as in the Arctic Circle where there is at least one day a year
427             *         where the sun does not rise, and one where it does not set, a
428             *         null will be returned. See detailed explanation on top of the
429             *         page.
430             */
431            public Date getSunsetOffsetByDegrees(double offsetZenith) {
432                    double sunset = getUTCSunset(offsetZenith);
433                    if (Double.isNaN(sunset)) {
434                            return null;
435                    } else {
436                            return getAdjustedSunsetDate(getDateFromTime(sunset),
437                                            getSunriseOffsetByDegrees(offsetZenith));
438                    }
439            }
440    
441            /**
442             * Default constructor will set a default {@link GeoLocation#GeoLocation()},
443             * a default {@link AstronomicalCalculator#getDefault()
444             * AstronomicalCalculator} and default the calendar to the current date.
445             */
446            public AstronomicalCalendar() {
447                    this(new GeoLocation());
448            }
449    
450            /**
451             * A constructor that takes in as a parameter geolocation information
452             * 
453             * @param geoLocation
454             *            The location information used for astronomical calculating sun
455             *            times.
456             */
457            public AstronomicalCalendar(GeoLocation geoLocation) {
458                    setCalendar(Calendar.getInstance(geoLocation.getTimeZone()));
459                    setGeoLocation(geoLocation);// duplicate call
460                    setAstronomicalCalculator(AstronomicalCalculator.getDefault());
461            }
462    
463            /**
464             * Method that returns the sunrise in UTC time without correction for time
465             * zone offset from GMT and without using daylight savings time.
466             * 
467             * @param zenith
468             *            the degrees below the horizon. For time after sunrise use
469             *            negative numbers.
470             * @return The time in the format: 18.75 for 18:45:00 UTC/GMT. If the
471             *         calculation can't be computed such as in the Arctic Circle where
472             *         there is at least one day a year where the sun does not rise, and
473             *         one where it does not set, {@link Double#NaN} will be returned.
474             */
475            public double getUTCSunrise(double zenith) {
476                    return getAstronomicalCalculator().getUTCSunrise(this, zenith, true);
477            }
478    
479            /**
480             * Method that returns the sunrise in UTC time without correction for time
481             * zone offset from GMT and without using daylight savings time. Non-sunrise
482             * and sunset calculations such as dawn and dusk, depend on the amount of
483             * visible light, something that is not affected by elevation. This method
484             * returns UTC sunrise calculated at sea level. This forms the base for dawn
485             * calculations that are calculated as a dip below the horizon before
486             * sunrise.
487             * 
488             * @param zenith
489             *            the degrees below the horizon. For time after sunrise use
490             *            negative numbers.
491             * @return The time in the format: 18.75 for 18:45:00 UTC/GMT. If the
492             *         calculation can't be computed such as in the Arctic Circle where
493             *         there is at least one day a year where the sun does not rise, and
494             *         one where it does not set, {@link Double#NaN} will be returned.
495             *         See detailed explanation on top of the page.
496             * @see AstronomicalCalendar#getUTCSunrise
497             * @see AstronomicalCalendar#getUTCSeaLevelSunset
498             */
499            public double getUTCSeaLevelSunrise(double zenith) {
500                    return getAstronomicalCalculator().getUTCSunrise(this, zenith, false);
501            }
502    
503            /**
504             * Method that returns the sunset in UTC time without correction for time
505             * zone offset from GMT and without using daylight savings time.
506             * 
507             * @param zenith
508             *            the degrees below the horizon. For time after before sunset
509             *            use negative numbers.
510             * @return The time in the format: 18.75 for 18:45:00 UTC/GMT. If the
511             *         calculation can't be computed such as in the Arctic Circle where
512             *         there is at least one day a year where the sun does not rise, and
513             *         one where it does not set, {@link Double#NaN} will be returned.
514             *         See detailed explanation on top of the page.
515             * @see AstronomicalCalendar#getUTCSeaLevelSunset
516             */
517            public double getUTCSunset(double zenith) {
518                    return getAstronomicalCalculator().getUTCSunset(this, zenith, true);
519            }
520    
521            /**
522             * Method that returns the sunset in UTC time without correction for
523             * elevation, time zone offset from GMT and without using daylight savings
524             * time. Non-sunrise and sunset calculations such as dawn and dusk, depend
525             * on the amount of visible light, something that is not affected by
526             * elevation. This method returns UTC sunset calculated at sea level. This
527             * forms the base for dusk calculations that are calculated as a dip below
528             * the horizon after sunset.
529             * 
530             * @param zenith
531             *            the degrees below the horizon. For time before sunset use
532             *            negative numbers.
533             * @return The time in the format: 18.75 for 18:45:00 UTC/GMT. If the
534             *         calculation can't be computed such as in the Arctic Circle where
535             *         there is at least one day a year where the sun does not rise, and
536             *         one where it does not set, {@link Double#NaN} will be returned.
537             *         See detailed explanation on top of the page.
538             * @see AstronomicalCalendar#getUTCSunset
539             * @see AstronomicalCalendar#getUTCSeaLevelSunrise
540             */
541            public double getUTCSeaLevelSunset(double zenith) {
542                    return getAstronomicalCalculator().getUTCSunset(this, zenith, false);
543            }
544    
545            /**
546             * A method that adds time zone offset and daylight savings time to the raw
547             * UTC time.
548             * 
549             * @param time
550             *            The UTC time to be adjusted.
551             * @return The time adjusted for the time zone offset and daylight savings
552             *         time.
553             */
554            private double getOffsetTime(double time) {
555                    boolean dst = getCalendar().getTimeZone().inDaylightTime(
556                                    getCalendar().getTime());
557                    double dstOffset = 0;
558                    // be nice to Newfies and use a double
559                    double gmtOffset = getCalendar().getTimeZone().getRawOffset()
560                                    / (60 * MINUTE_MILLIS);
561                    if (dst) {
562                            dstOffset = getCalendar().getTimeZone().getDSTSavings()
563                                            / (60 * MINUTE_MILLIS);
564                    }
565                    return time + gmtOffset + dstOffset;
566            }
567    
568            /**
569             * Method to return an
570             * {@link AstronomicalCalculator#getElevationAdjustment(double) elevation
571             * adjusted} temporal (solar) hour. The day from {@link #getSunrise()
572             * sunrise} to {@link #getSunset() sunset} is split into 12 equal parts with
573             * each one being a temporal hour.
574             * 
575             * @see #getSunrise()
576             * @see #getSunset()
577             * @see #getTemporalHour(Date, Date)
578             * 
579             * @return the <code>long</code> millisecond length of a temporal hour. If
580             *         the calculation can't be computed (see explanation on top of the
581             *         page), {@link Long#MIN_VALUE} will be returned.
582             */
583            public long getTemporalHour() {
584                    return getTemporalHour(getSunrise(), getSunset());
585            }
586    
587            /**
588             * Utility method that will allow the calculation of a temporal (solar) hour
589             * based on the sunrise and sunset passed to this method. An example of the
590             * use of this method would be the calculation of a non-elevation adjusted
591             * temporal hour by passing in {@link #getSeaLevelSunrise() sea level
592             * sunrise} and {@link #getSeaLevelSunset() sea level sunset}.
593             * 
594             * @param sunrise
595             *            The start of the day.
596             * @param sunset
597             *            The end of the day.
598             * @see #getTemporalHour()
599             * @return the <code>long</code> millisecond length of the temporal hour. If
600             *         the calculation can't be computed (see explanation on top of the
601             *         page) {@link Long#MIN_VALUE} will be returned.
602             */
603            public long getTemporalHour(Date sunrise, Date sunset) {
604                    if (sunrise == null || sunset == null) {
605                            return Long.MIN_VALUE;
606                    }
607                    return (sunset.getTime() - sunrise.getTime()) / 12;
608            }
609    
610            /**
611             * A method that returns sundial or solar noon. It occurs when the Sun is <a
612             * href
613             * ="http://en.wikipedia.org/wiki/Transit_%28astronomy%29">transitting</a>
614             * the <a
615             * href="http://en.wikipedia.org/wiki/Meridian_%28astronomy%29">celestial
616             * meridian</a>. In this class it is calculated as halfway between sunrise
617             * and sunset, which can be slightly off the real transit time due to the
618             * lengthening or shortening day.
619             * 
620             * @return the <code>Date</code> representing Sun's transit. If the
621             *         calculation can't be computed such as in the Arctic Circle where
622             *         there is at least one day a year where the sun does not rise, and
623             *         one where it does not set, null will be returned.
624             */
625            public Date getSunTransit() {
626                    return getTimeOffset(getSunrise(), getTemporalHour() * 6);
627            }
628    
629            /**
630             * A method that returns a <code>Date</code> from the time passed in
631             * 
632             * @param time
633             *            The time to be set as the time for the <code>Date</code>. The
634             *            time expected is in the format: 18.75 for 6:45:00 PM
635             * @return The Date.
636             */
637            protected Date getDateFromTime(double time) {
638                    if (Double.isNaN(time)) {
639                            return null;
640                    }
641                    double calculatedTime=time;
642                    calculatedTime = getOffsetTime(calculatedTime);
643                    calculatedTime = (calculatedTime + 240) % 24; // the calculators sometimes return a double
644                    // that is negative or slightly greater than 24
645                    Calendar cal = Calendar.getInstance();
646                    cal.clear();
647                    cal.set(Calendar.YEAR, getCalendar().get(Calendar.YEAR));
648                    cal.set(Calendar.MONTH, getCalendar().get(Calendar.MONTH));
649                    cal.set(Calendar.DAY_OF_MONTH, getCalendar().get(Calendar.DAY_OF_MONTH));
650    
651                    int hours = (int) calculatedTime; // cut off minutes
652    
653                    calculatedTime -= hours;
654                    int minutes = (int) (calculatedTime *= 60);
655                    calculatedTime -= minutes;
656                    int seconds = (int) (calculatedTime *= 60);
657                    calculatedTime -= seconds; // milliseconds
658    
659                    cal.set(Calendar.HOUR_OF_DAY, hours);
660                    cal.set(Calendar.MINUTE, minutes);
661                    cal.set(Calendar.SECOND, seconds);
662                    cal.set(Calendar.MILLISECOND, (int) (calculatedTime * 1000));
663                    return cal.getTime();
664            }
665    
666            /**
667             * Will return the dip below the horizon before sunrise that matches the
668             * offset minutes on passed in. For example passing in 72 minutes for a
669             * calendar set to the equinox in Jerusalem returns a value close to
670             * 16.1&deg; Please note that this method is very slow and inefficient and
671             * should NEVER be used in a loop. <em><b>TODO:</b></em> Improve efficiency.
672             * 
673             * @param minutes
674             *            offset
675             * @return the degrees below the horizon that match the offset on the
676             *         equinox in Jerusalem at sea level.
677             * @see #getSunsetSolarDipFromOffset(double)
678             */
679            public double getSunriseSolarDipFromOffset(double minutes) {
680                    Date offsetByDegrees = getSeaLevelSunrise();
681                    Date offsetByTime = getTimeOffset(getSeaLevelSunrise(),
682                                    -(minutes * MINUTE_MILLIS));
683    
684                    java.math.BigDecimal degrees = new java.math.BigDecimal(0);
685                    java.math.BigDecimal incrementor = new java.math.BigDecimal("0.0001");
686                    while (offsetByDegrees == null
687                                    || offsetByDegrees.getTime() > offsetByTime.getTime()) {
688                            degrees = degrees.add(incrementor);
689                            offsetByDegrees = getSunriseOffsetByDegrees(GEOMETRIC_ZENITH
690                                            + degrees.doubleValue());
691                    }
692                    return degrees.doubleValue();
693            }
694    
695            /**
696             * Will return the dip below the horizon after sunset that matches the
697             * offset minutes on passed in. For example passing in 72 minutes for a
698             * calendar set to the equinox in Jerusalem returns a value close to
699             * 16.1&deg; Please note that this method is very slow and inefficient and
700             * should NEVER be used in a loop. <em><b>TODO:</b></em> Improve efficiency.
701             * 
702             * @param minutes
703             *            offset
704             * @return the degrees below the horizon that match the offset on the
705             *         equinox in Jerusalem at sea level.
706             * @see #getSunriseSolarDipFromOffset(double)
707             */
708            public double getSunsetSolarDipFromOffset(double minutes) {
709                    Date offsetByDegrees = getSeaLevelSunset();
710                    Date offsetByTime = getTimeOffset(getSeaLevelSunset(), minutes
711                                    * MINUTE_MILLIS);
712    
713                    java.math.BigDecimal degrees = new java.math.BigDecimal(0);
714                    java.math.BigDecimal incrementor = new java.math.BigDecimal("0.0001");
715                    while (offsetByDegrees == null
716                                    || offsetByDegrees.getTime() < offsetByTime.getTime()) {
717                            degrees = degrees.add(incrementor);
718                            offsetByDegrees = getSunsetOffsetByDegrees(GEOMETRIC_ZENITH
719                                            + degrees.doubleValue());
720                    }
721                    return degrees.doubleValue();
722            }
723    
724            /**
725             * @return an XML formatted representation of the class. It returns the
726             *         default output of the
727             *         {@link net.sourceforge.zmanim.util.ZmanimFormatter#toXML(AstronomicalCalendar)
728             *         toXML} method.
729             * @see net.sourceforge.zmanim.util.ZmanimFormatter#toXML(AstronomicalCalendar)
730             * @see java.lang.Object#toString()
731             */
732            public String toString() {
733                    return ZmanimFormatter.toXML(this);
734            }
735    
736            /**
737             * @see java.lang.Object#equals(Object)
738             */
739            public boolean equals(Object object) {
740                    if (this == object)
741                            return true;
742                    if (!(object instanceof AstronomicalCalendar))
743                            return false;
744                    AstronomicalCalendar aCal = (AstronomicalCalendar) object;
745                    return getCalendar().equals(aCal.getCalendar())
746                                    && getGeoLocation().equals(aCal.getGeoLocation())
747                                    && getAstronomicalCalculator().equals(
748                                                    aCal.getAstronomicalCalculator());
749            }
750    
751            /**
752             * @see java.lang.Object#hashCode()
753             */
754            public int hashCode() {
755                    int result = 17;
756                    result = 37 * result + getClass().hashCode(); // needed or this and
757                                                                                                                    // subclasses will
758                                                                                                                    // return identical hash
759                    result += 37 * result + getCalendar().hashCode();
760                    result += 37 * result + getGeoLocation().hashCode();
761                    result += 37 * result + getAstronomicalCalculator().hashCode();
762                    return result;
763            }
764    
765            /**
766             * A method that returns the currently set {@link GeoLocation} that contains
767             * location information used for the astronomical calculations.
768             * 
769             * @return Returns the geoLocation.
770             */
771            public GeoLocation getGeoLocation() {
772                    return this.geoLocation;
773            }
774    
775            /**
776             * Set the {@link GeoLocation} to be used for astronomical calculations.
777             * 
778             * @param geoLocation
779             *            The geoLocation to set.
780             */
781            public void setGeoLocation(GeoLocation geoLocation) {
782                    this.geoLocation = geoLocation;
783                    // if not set the output will be in the original timezone. The call
784                    // below is also in the constructor
785                    getCalendar().setTimeZone(geoLocation.getTimeZone());
786            }
787    
788            /**
789             * A method to return the current AstronomicalCalculator set.
790             * 
791             * @return Returns the astronimicalCalculator.
792             * @see #setAstronomicalCalculator(AstronomicalCalculator)
793             */
794            public AstronomicalCalculator getAstronomicalCalculator() {
795                    return this.astronomicalCalculator;
796            }
797    
798            /**
799             * A method to set the {@link AstronomicalCalculator} used for astronomical
800             * calculations. The Zmanim package ships with a number of different
801             * implementations of the <code>abstract</code>
802             * {@link AstronomicalCalculator} based on different algorithms, including
803             * {@link net.sourceforge.zmanim.util.SunTimesCalculator one implementation}
804             * based on the <a href = "http://aa.usno.navy.mil/">US Naval
805             * Observatory's</a> algorithm, and
806             * {@link net.sourceforge.zmanim.util.JSuntimeCalculator another} based on
807             * <a href=""http://noaa.gov">NOAA's</a> algorithm. This allows easy runtime
808             * switching and comparison of different algorithms.
809             * 
810             * @param astronomicalCalculator
811             *            The astronimicalCalculator to set.
812             */
813            public void setAstronomicalCalculator(
814                            AstronomicalCalculator astronomicalCalculator) {
815                    this.astronomicalCalculator = astronomicalCalculator;
816            }
817    
818            /**
819             * returns the Calendar object encapsulated in this class.
820             * 
821             * @return Returns the calendar.
822             */
823            public Calendar getCalendar() {
824                    return this.calendar;
825            }
826    
827            /**
828             * @param calendar
829             *            The calendar to set.
830             */
831            public void setCalendar(Calendar calendar) {
832                    this.calendar = calendar;
833                    if (getGeoLocation() != null) {// set the timezone if possible
834                            // Always set the Calendar's timezone to match the GeoLocation
835                            // TimeZone
836                            getCalendar().setTimeZone(getGeoLocation().getTimeZone());
837                    }
838            }
839    
840            /**
841             * A method that creates a <a
842             * href="http://en.wikipedia.org/wiki/Object_copy#Deep_copy">deep copy</a>
843             * of the object. <br />
844             * <b>Note:</b> If the {@link java.util.TimeZone} in the cloned
845             * {@link net.sourceforge.zmanim.util.GeoLocation} will be changed from the
846             * original, it is critical that
847             * {@link net.sourceforge.zmanim.AstronomicalCalendar#getCalendar()}.
848             * {@link java.util.Calendar#setTimeZone(TimeZone) setTimeZone(TimeZone)} be
849             * called in order for the AstronomicalCalendar to output times in the
850             * expected offset after being cloned.
851             * 
852             * @see java.lang.Object#clone()
853             * @since 1.1
854             */
855            public Object clone() {
856                    AstronomicalCalendar clone = null;
857                    try {
858                            clone = (AstronomicalCalendar) super.clone();
859                    } catch (CloneNotSupportedException cnse) {
860                            System.out
861                                            .print("Required by the compiler. Should never be reached since we implement clone()");
862                    }
863                    clone.setGeoLocation((GeoLocation) getGeoLocation().clone());
864                    clone.setCalendar((Calendar) getCalendar().clone());
865                    clone.setAstronomicalCalculator((AstronomicalCalculator) getAstronomicalCalculator()
866                                    .clone());
867                    return clone;
868            }
869    }