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