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