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