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