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 }