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 }