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