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 }