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 © 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° 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° zenith. Because the Sun is not a point, and
079 * because the atmosphere refracts light, this 90° 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° 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° 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°). */
092 public static final double CIVIL_ZENITH = 96;
093
094 /** Sun's zenith at nautical twilight (102°). */
095 public static final double NAUTICAL_ZENITH = 102;
096
097 /** Sun's zenith at astronomical twilight (108°). */
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°. 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°}. 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°}.
166 *
167 * @return The <code>Date</code> of the beginning of civil twilight using
168 * a zenith of 96°. 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°}.
179 *
180 * @return The <code>Date</code> of the beginning of nautical twilight
181 * using a zenith of 102°. 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°}.
192 *
193 * @return The <code>Date</code> of the beginning of astronomical twilight
194 * using a zenith of 108°. 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°. 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°}. 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°}.
282 *
283 * @return The <code>Date</code> of the end of civil twilight using a
284 * zenith of {@link #CIVIL_ZENITH 96°}. 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°}.
295 *
296 * @return The <code>Date</code> of the end of nautical twilight using a
297 * zenith of {@link #NAUTICAL_ZENITH 102°}. 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°}.
308 *
309 * @return The The <code>Date</code> of the end of astronomical twilight
310 * using a zenith of {@link #ASTRONOMICAL_ZENITH 108°}. 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 * <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">
715 * <Sunrise>2007-02-18T06:45:27-05:00</Sunrise>
716 * <TemporalHour>PT54M17.529S</TemporalHour>
717 * ...
718 * </AstronomicalTimes>
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 * <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">
733 * <Sunrise>2007-02-18T06:45:27-05:00</Sunrise>
734 * <TemporalHour>PT54M17.529S</TemporalHour>
735 * ...
736 * </AstronomicalTimes>
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 }