001 /* 002 * Zmanim Java API 003 * Copyright (C) 2004-2008 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 - 2009 073 * @version 1.1 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 590 // 24 591 Calendar cal = new GregorianCalendar(); 592 cal.clear(); 593 cal.set(Calendar.YEAR, getCalendar().get(Calendar.YEAR)); 594 cal.set(Calendar.MONTH, getCalendar().get(Calendar.MONTH)); 595 cal 596 .set(Calendar.DAY_OF_MONTH, getCalendar().get( 597 Calendar.DAY_OF_MONTH)); 598 599 int hours = (int) time; // cut off minutes 600 601 time -= hours; 602 int minutes = (int) (time *= 60); 603 time -= minutes; 604 int seconds = (int) (time *= 60); 605 time -= seconds; // milliseconds 606 607 cal.set(Calendar.HOUR_OF_DAY, hours); 608 cal.set(Calendar.MINUTE, minutes); 609 cal.set(Calendar.SECOND, seconds); 610 cal.set(Calendar.MILLISECOND, (int) (time * 1000)); 611 return cal.getTime(); 612 } 613 614 /** 615 * Will return the dip below the horizon before sunrise that matches the 616 * offset minutes on passed in. For example passing in 72 minutes for a 617 * calendar set to the equinox in Jerusalem returns a value close to 618 * 16.1° 619 * Please note that this method is very slow and inefficient and should NEVER be used in a loop. 620 * TODO: Improve efficiency. 621 * @param minutes 622 * offset 623 * @return the degrees below the horizon that match the offset on the 624 * equinox in Jerusalem at sea level. 625 */ 626 public double getSunriseSolarDipFromOffset(double minutes) { 627 Date offsetByDegrees = getSeaLevelSunrise(); 628 Date offsetByTime = getTimeOffset(getSeaLevelSunrise(), -(minutes * MINUTE_MILLIS)); 629 630 java.math.BigDecimal degrees = new java.math.BigDecimal(0); 631 java.math.BigDecimal incrementor = new java.math.BigDecimal("0.0001"); 632 while (offsetByDegrees == null 633 || offsetByDegrees.getTime() > offsetByTime.getTime()) { 634 degrees = degrees.add(incrementor); 635 offsetByDegrees = getSunriseOffsetByDegrees(GEOMETRIC_ZENITH + degrees.doubleValue()); 636 } 637 return degrees.doubleValue(); 638 } 639 640 /** 641 * Will return the dip below the horizon after sunset that matches the 642 * offset minutes on passed in. For example passing in 72 minutes for a 643 * calendar set to the equinox in Jerusalem returns a value close to 644 * 16.1° 645 * Please note that this method is very slow and inefficient and should NEVER be used in a loop. 646 * <em><b>TODO:</b></em> Improve efficiency. 647 * @param minutes offset 648 * @return the degrees below the horizon that match the offset on the 649 * equinox in Jerusalem at sea level. 650 * @see #getSunriseSolarDipFromOffset(double) 651 */ 652 public double getSunsetSolarDipFromOffset(double minutes) { 653 Date offsetByDegrees = getSeaLevelSunset(); 654 Date offsetByTime = getTimeOffset(getSeaLevelSunset(), minutes * MINUTE_MILLIS); 655 656 java.math.BigDecimal degrees = new java.math.BigDecimal(0); 657 java.math.BigDecimal incrementor = new java.math.BigDecimal("0.0001"); 658 while (offsetByDegrees == null 659 || offsetByDegrees.getTime() < offsetByTime.getTime()) { 660 degrees = degrees.add(incrementor); 661 offsetByDegrees = getSunsetOffsetByDegrees(GEOMETRIC_ZENITH + degrees.doubleValue()); 662 } 663 return degrees.doubleValue(); 664 } 665 666 /** 667 * @return an XML formatted representation of the class. It returns the 668 * default output of the 669 * {@link net.sourceforge.zmanim.util.ZmanimFormatter#toXML(AstronomicalCalendar) toXML} 670 * method. 671 * @see net.sourceforge.zmanim.util.ZmanimFormatter#toXML(AstronomicalCalendar) 672 * @see java.lang.Object#toString() 673 */ 674 public String toString() { 675 return ZmanimFormatter.toXML(this); 676 } 677 678 /** 679 * @see java.lang.Object#equals(Object) 680 */ 681 public boolean equals(Object object) { 682 if (this == object) 683 return true; 684 if (!(object instanceof AstronomicalCalendar)) 685 return false; 686 AstronomicalCalendar aCal = (AstronomicalCalendar) object; 687 return getCalendar().equals(aCal.getCalendar()) 688 && getGeoLocation().equals(aCal.getGeoLocation()) 689 && getAstronomicalCalculator().equals( 690 aCal.getAstronomicalCalculator()); 691 } 692 693 /** 694 * @see java.lang.Object#hashCode() 695 */ 696 public int hashCode() { 697 int result = 17; 698 // needed or this and subclasses will return identical hash 699 result = 37 * result + getClass().hashCode(); 700 result += 37 * result + getCalendar().hashCode(); 701 result += 37 * result + getGeoLocation().hashCode(); 702 result += 37 * result + getAstronomicalCalculator().hashCode(); 703 return result; 704 } 705 706 /** 707 * A method that returns the currently set {@link GeoLocation} that contains 708 * location information used for the astronomical calculations. 709 * 710 * @return Returns the geoLocation. 711 */ 712 public GeoLocation getGeoLocation() { 713 return geoLocation; 714 } 715 716 /** 717 * Set the {@link GeoLocation} to be used for astronomical calculations. 718 * 719 * @param geoLocation 720 * The geoLocation to set. 721 */ 722 public void setGeoLocation(GeoLocation geoLocation) { 723 this.geoLocation = geoLocation; 724 // if not set the output will be in the original timezone. The call 725 // below is also in the constructor 726 getCalendar().setTimeZone(geoLocation.getTimeZone()); 727 } 728 729 /** 730 * A method to return the current AstronomicalCalculator set. 731 * 732 * @return Returns the astronimicalCalculator. 733 * @see #setAstronomicalCalculator(AstronomicalCalculator) 734 */ 735 public AstronomicalCalculator getAstronomicalCalculator() { 736 return astronomicalCalculator; 737 } 738 739 /** 740 * A method to set the {@link AstronomicalCalculator} used for astronomical 741 * calculations. The Zmanim package ships with a number of different 742 * implementations of the <code>abstract</code> 743 * {@link AstronomicalCalculator} based on different algorithms, including 744 * {@link net.sourceforge.zmanim.util.SunTimesCalculator one implementation} 745 * based on the <a href = "http://aa.usno.navy.mil/">US Naval Observatory's</a> 746 * algorithm, and 747 * {@link net.sourceforge.zmanim.util.JSuntimeCalculator another} based on 748 * <a href=""http://noaa.gov">NOAA's</a> algorithm. This allows easy 749 * runtime switching and comparison of different algorithms. 750 * 751 * @param astronomicalCalculator 752 * The astronimicalCalculator to set. 753 */ 754 public void setAstronomicalCalculator( 755 AstronomicalCalculator astronomicalCalculator) { 756 this.astronomicalCalculator = astronomicalCalculator; 757 } 758 759 /** 760 * returns the Calendar object encapsulated in this class. 761 * 762 * @return Returns the calendar. 763 */ 764 public Calendar getCalendar() { 765 return calendar; 766 } 767 768 /** 769 * @param calendar 770 * The calendar to set. 771 */ 772 public void setCalendar(Calendar calendar) { 773 this.calendar = calendar; 774 if (getGeoLocation() != null) {// set the timezone if possible 775 // Always set the Calendar's timezone to match the GeoLocation 776 // TimeZone 777 getCalendar().setTimeZone(getGeoLocation().getTimeZone()); 778 } 779 } 780 781 /** 782 * A method that creates a <a 783 * href="http://en.wikipedia.org/wiki/Object_copy#Deep_copy">deep copy</a> 784 * of the object. <br /> 785 * <b>Note:</b> If the {@link java.util.TimeZone} in the cloned 786 * {@link net.sourceforge.zmanim.util.GeoLocation} will be changed from the 787 * original, it is critical that 788 * {@link net.sourceforge.zmanim.AstronomicalCalendar#getCalendar()}.{@link java.util.Calendar#setTimeZone(TimeZone) setTimeZone(TimeZone)} 789 * be called in order for the AstronomicalCalendar to output times in the 790 * expected offset after being cloned. 791 * 792 * @see java.lang.Object#clone() 793 * @since 1.1 794 */ 795 public Object clone() { 796 AstronomicalCalendar clone = null; 797 try { 798 clone = (AstronomicalCalendar) super.clone(); 799 } catch (CloneNotSupportedException cnse) { 800 System.out 801 .print("Required by the compiler. Should never be reached since we implement clone()"); 802 } 803 clone.setGeoLocation((GeoLocation) getGeoLocation().clone()); 804 clone.setCalendar((Calendar) getCalendar().clone()); 805 clone 806 .setAstronomicalCalculator((AstronomicalCalculator) getAstronomicalCalculator() 807 .clone()); 808 return clone; 809 } 810 }