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