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