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