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