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