001/* 002 * Zmanim Java API 003 * Copyright (C) 2004-2026 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.time.Duration; 020import java.time.Instant; 021import java.time.LocalDate; 022import java.time.LocalDateTime; 023import java.time.LocalTime; 024import java.time.ZoneOffset; 025import java.time.ZonedDateTime; 026import java.util.Objects; 027 028import com.kosherjava.zmanim.util.AstronomicalCalculator; 029import com.kosherjava.zmanim.util.GeoLocation; 030import com.kosherjava.zmanim.util.ZmanimFormatter; 031 032/** 033 * A Java calendar that calculates astronomical times such as {@link getSunrise() sunrise}, {@link 034 * getSunset() sunset} and twilight times. This class contains a {@link getLocalDate() LocalDate} and can therefore 035 * use the standard Calendar functionality to change dates etc. The calculation engine used to calculate the astronomical times can 036 * be changed to a different implementation by implementing the abstract {@link AstronomicalCalculator} and setting it withthe {@link 037 * setAstronomicalCalculator(AstronomicalCalculator)}. A number of different calculation engine implementations are included in the 038 * util package. 039 * <b>Note:</b> There are times when the algorithms can't calculate proper values for sunrise, sunset and twilight. This is usually 040 * caused by trying to calculate times for areas either very far North or South, where sunrise / sunset never happen on that date. 041 * This is common when calculating twilight with a deep dip below the horizon for locations as far south of the North Pole as London, 042 * in the northern hemisphere. The sun never reaches this dip at certain times of the year. When the calculations encounter this 043 * condition a <code>null</code> will be returned when a <code>{@link java.time.Instant}</code> is expected and {@link Long#MIN_VALUE} 044 * when a <code>long</code> is expected. The reason that <code>Exception</code>s are not thrown in these cases is because the lack of 045 * a rise/set or twilight is not an exception, but an expected condition in many parts of the world. 046 * <p> 047 * Here is a simple example of how to use the API to calculate sunrise. 048 * First create the Calendar for the location you would like to calculate sunrise or sunset times for: 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 getZoneId() has to be a valid ZoneId listed in {@link java.time.ZoneId#getAvailableZoneIds()} 056 * ZoneId zoneId = ZoneId.of("America/New_York"); 057 * GeoLocation location = new GeoLocation(locationName, latitude, longitude, elevation, zoneId); 058 * AstronomicalCalendar ac = new AstronomicalCalendar(location); 059 * </pre> 060 * 061 * To get the time of sunrise, first set the date you want (if not set, the date will default to today): 062 * 063 * <pre> 064 * LocalDate localDate = LocalDate.of(1969, Month.FEBRUARY, 8); 065 * ac.setLocalDate(localDate); 066 * Instant sunrise = ac.getSunrise(); 067 * </pre> 068 * 069 * @author © Eliyahu Hershfeld 2004 - 2026 070 */ 071public class AstronomicalCalendar implements Cloneable { 072 073 /** 074 * 90° below the vertical. Used as a basis for most calculations since the location of the sun is 90° below the horizon 075 * at sunrise and sunset. 076 * <b>Note </b>: it is important to note that for sunrise and sunset the {@link AstronomicalCalculator#adjustZenith(double, 077 * double) adjusted zenith} is required to account for the radius of the sun and refraction. The adjusted zenith should not be 078 * used for calculations above or below 90° since they are usually calculated as an offset to 90°. 079 */ 080 public static final double GEOMETRIC_ZENITH = 90; 081 082 /** Sun's zenith at civil twilight (96°). */ 083 public static final double CIVIL_ZENITH = 96; 084 085 /** Sun's zenith at nautical twilight (102°). */ 086 public static final double NAUTICAL_ZENITH = 102; 087 088 /** Sun's zenith at astronomical twilight (108°). */ 089 public static final double ASTRONOMICAL_ZENITH = 108; 090 091 /** constant for milliseconds in a minute (60,000) */ 092 public static final long MINUTE_MILLIS = 60 * 1000; 093 094 /** constant for milliseconds in an hour (3,600,000) */ 095 public static final long HOUR_MILLIS = MINUTE_MILLIS * 60; 096 097 /** 098 * The <code>LocalDate</code> encapsulated by this class to track the current date used by the class 099 */ 100 private LocalDate localDate; 101 102 /** 103 * the {@link GeoLocation} used for calculations. 104 */ 105 private GeoLocation geoLocation; 106 107 /** 108 * the internal {@link AstronomicalCalculator} used for calculating solar based times. 109 */ 110 private AstronomicalCalculator astronomicalCalculator; 111 112 /** 113 * The getSunrise method returns a <code>Instant</code> representing the {@link AstronomicalCalculator 114 * #getElevationAdjustment(double) elevation adjusted} sunrise time. The zenith used for the calculation uses {@link 115 * GEOMETRIC_ZENITH geometric zenith} of 90° plus {@link AstronomicalCalculator#getElevationAdjustment(double)}. This is 116 * adjusted by the {@link AstronomicalCalculator} to add approximately 50/60 of a degree to account for 34 archminutes of 117 * refraction 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>Instant</code> representing the exact sunrise time. If the calculation can't be computed such as in the 121 * Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does not set, a 122 * <code>null</code> will be returned. See detailed explanation on top of the page. 123 * @see AstronomicalCalculator#adjustZenith(double, double) 124 * @see getSeaLevelSunrise() 125 * @see getUTCSunrise(double) 126 */ 127 public Instant getSunrise() { 128 double sunrise = getUTCSunrise(GEOMETRIC_ZENITH); 129 if (Double.isNaN(sunrise)) { 130 return null; 131 } else { 132 return getInstantFromTime(sunrise, SolarEvent.SUNRISE); 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>Instant</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 <code>null</code> will be returned. See detailed explanation on top of the page. 145 * @see getSunrise() 146 * @see getUTCSeaLevelSunrise(double) 147 * @see getSeaLevelSunset() 148 */ 149 public Instant getSeaLevelSunrise() { 150 double sunrise = getUTCSeaLevelSunrise(GEOMETRIC_ZENITH); 151 if (Double.isNaN(sunrise)) { 152 return null; 153 } else { 154 return getInstantFromTime(sunrise, SolarEvent.SUNRISE); 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>Instant</code> of the beginning of civil twilight using a zenith of 96°. If the calculation 163 * can't be computed, <code>null</code> will be returned. See detailed explanation on top of the page. 164 */ 165 public Instant getBeginCivilTwilight() { 166 return getSunriseOffsetByDegrees(CIVIL_ZENITH); 167 } 168 169 /** 170 * A method that returns the beginning of <a href="https://en.wikipedia.org/wiki/Twilight#Nautical_twilight">nautical twilight</a> 171 * using a zenith of {@link NAUTICAL_ZENITH 102°}. 172 * 173 * @return The <code>Instant</code> of the beginning of nautical twilight using a zenith of 102°. If the calculation 174 * can't be computed <code>null</code> will be returned. See detailed explanation on top of the page. 175 */ 176 public Instant getBeginNauticalTwilight() { 177 return getSunriseOffsetByDegrees(NAUTICAL_ZENITH); 178 } 179 180 /** 181 * A method that returns the beginning of <a href="https://en.wikipedia.org/wiki/Twilight#Astronomical_twilight">astronomical 182 * twilight</a> using a zenith of {@link ASTRONOMICAL_ZENITH 108°}. 183 * 184 * @return The <code>Instant</code> of the beginning of astronomical twilight using a zenith of 108°. If the calculation 185 * can't be computed, <code>null</code> will be returned. See detailed explanation on top of the page. 186 */ 187 public Instant getBeginAstronomicalTwilight() { 188 return getSunriseOffsetByDegrees(ASTRONOMICAL_ZENITH); 189 } 190 191 /** 192 * The getSunset method returns an <code>Instant</code> representing the 193 * {@link AstronomicalCalculator#getElevationAdjustment(double) elevation adjusted} sunset time. The zenith used for the 194 * calculation uses {@link GEOMETRIC_ZENITH geometric zenith} of 90° plus {@link AstronomicalCalculator 195 * #getElevationAdjustment(double)}. This is adjusted by the {@link AstronomicalCalculator} to add approximately 50/60 of a 196 * degree to account for 34 archminutes of refraction and 16 archminutes for the sun's radius for a total of {@link 197 * AstronomicalCalculator#adjustZenith(double, double) 90.83333°}. See documentation for the specific implementation of the 198 * {@link AstronomicalCalculator} that you are using. 199 * Note: In certain cases the calculates sunset will occur before sunrise. This will typically happen when a time zone other than 200 * the local timezone is used (calculating Los Angeles sunset using a GMT time zone for example). In this case the sunset date 201 * will be incremented to the following date. 202 * 203 * @return the <code>Instant</code> representing the exact sunset time. If the calculation can't be computed such as in the Arctic 204 * Circle where there is at least one day a year where the sun does not rise, and one where it does not set, a 205 * <code>null</code> will be returned. See detailed explanation on top of the page. 206 * @see AstronomicalCalculator#adjustZenith(double, double) 207 * @see getSeaLevelSunset() 208 * @see getUTCSunset(double) 209 */ 210 public Instant getSunset() { 211 double sunset = getUTCSunset(GEOMETRIC_ZENITH); 212 if (Double.isNaN(sunset)) { 213 return null; 214 } else { 215 return getInstantFromTime(sunset, SolarEvent.SUNSET); 216 } 217 } 218 219 /** 220 * A method that returns the sunset without {@link AstronomicalCalculator#getElevationAdjustment(double) elevation adjustment}. 221 * Non-sunrise and sunset calculations such as dawn and dusk, depend on the amount of visible light, something that is not 222 * affected by elevation. This method returns sunset calculated at sea level. This forms the base for dusk calculations that are 223 * calculated as a dip below the horizon after sunset. 224 * 225 * @return the <code>Instant</code> representing the exact sea-level sunset time. If the calculation can't be computed 226 * such as in the Arctic Circle where there is at least one day a year where the sun does not rise, and one 227 * where it does not set, a <code>null</code> will be returned. See detailed explanation on top of the page. 228 * @see getSunset() 229 * @see getUTCSeaLevelSunset(double) 230 */ 231 public Instant getSeaLevelSunset() { 232 double sunset = getUTCSeaLevelSunset(GEOMETRIC_ZENITH); 233 if (Double.isNaN(sunset)) { 234 return null; 235 } else { 236 return getInstantFromTime(sunset, SolarEvent.SUNSET); 237 } 238 } 239 240 /** 241 * A method that returns the end of <a href="https://en.wikipedia.org/wiki/Twilight#Civil_twilight">civil twilight</a> 242 * using a zenith of {@link CIVIL_ZENITH 96°}. 243 * 244 * @return The <code>Instant</code> of the end of civil twilight using a zenith of {@link CIVIL_ZENITH 96°}. If the 245 * calculation can't be computed, <code>null</code> will be returned. See detailed explanation on top of the page. 246 */ 247 public Instant getEndCivilTwilight() { 248 return getSunsetOffsetByDegrees(CIVIL_ZENITH); 249 } 250 251 /** 252 * A method that returns the end of nautical twilight using a zenith of {@link NAUTICAL_ZENITH 102°}. 253 * 254 * @return The <code>Instant</code> of the end of nautical twilight using a zenith of {@link NAUTICAL_ZENITH 102°}. If 255 * the calculation can't be computed, <code>null</code> will be returned. See detailed explanation on top of the 256 * page. 257 */ 258 public Instant getEndNauticalTwilight() { 259 return getSunsetOffsetByDegrees(NAUTICAL_ZENITH); 260 } 261 262 /** 263 * A method that returns the end of astronomical twilight using a zenith of {@link ASTRONOMICAL_ZENITH 108°}. 264 * 265 * @return the <code>Instant</code> of the end of astronomical twilight using a zenith of {@link ASTRONOMICAL_ZENITH 266 * 108°}. If the calculation can't be computed, <code>null</code> will be returned. See detailed 267 * explanation on top of the page. 268 */ 269 public Instant getEndAstronomicalTwilight() { 270 return getSunsetOffsetByDegrees(ASTRONOMICAL_ZENITH); 271 } 272 273 /** 274 * A utility method that returns a date offset by the offset time passed in as a parameter. This method casts the 275 * offset as a <code>long</code> and calls {@link getTimeOffset(Instant, long)}. 276 * 277 * @param time 278 * the start time 279 * @param offset 280 * the offset in milliseconds to add to the time 281 * @return the {@link java.time.Instant} with the offset added to it 282 */ 283 public static Instant getTimeOffset(Instant time, double offset) { 284 return getTimeOffset(time, (long) offset); 285 } 286 287 /** 288 * A utility method that returns an <code>Instant</code> offset by the offset time passed in. Please note that the level of light 289 * during twilight is not affected by elevation, so if this is being used to calculate an offset before sunrise or after sunset 290 * with the intent of getting a rough "level of light" calculation, the sunrise or sunset time passed to this method should be 291 * sea level sunrise and sunset. 292 * 293 * @param time 294 * the start time 295 * @param offsetMillis 296 * the offset in milliseconds to add to the time. 297 * @return the {@link java.time.Instant} with the offset in milliseconds added to it 298 */ 299 public static Instant getTimeOffset(Instant time, long offsetMillis) { 300 if (time == null || offsetMillis == Long.MIN_VALUE) { 301 return null; 302 } 303 return time.plusMillis(offsetMillis); 304 } 305 306 /** 307 * A utility method that returns the time of an offset by degrees below or above the horizon of {@link getSunrise() 308 * sunrise}. Note that the degree offset is from the vertical, so for a calculation of 14° before sunrise, an offset of 14 309 * + {@link GEOMETRIC_ZENITH} = 104 would have to be passed as a parameter. 310 * 311 * @param offsetZenith 312 * the degrees before {@link getSunrise()} to use in the calculation. For time after sunrise use negative 313 * numbers. Note that the degree offset is from the vertical, so for a calculation of 14° before sunrise, an offset 314 * of 14 + {@link GEOMETRIC_ZENITH} = 104 would have to be passed as a parameter. 315 * @return The {@link java.time.Instant} of the offset after (or before) {@link getSunrise()}. If the calculation 316 * can't be computed such as in the Arctic Circle where there is at least one day a year where the sun does 317 * not rise, and one where it does not set, a <code>null</code> will be returned. See detailed explanation 318 * on top of the page. 319 */ 320 public Instant getSunriseOffsetByDegrees(double offsetZenith) { 321 double dawn = getUTCSunrise(offsetZenith); 322 return Double.isNaN(dawn) ? null 323 : getInstantFromTime(dawn, SolarEvent.SUNRISE); 324 } 325 326 /** 327 * A utility method that returns the time of an offset by degrees below or above the horizon of {@link getSunset() 328 * sunset}. Note that the degree offset is from the vertical, so for a calculation of 14° after sunset, an offset of 14 + 329 * {@link GEOMETRIC_ZENITH} = 104 would have to be passed as a parameter. 330 * 331 * @param offsetZenith 332 * the degrees after {@link getSunset()} to use in the calculation. For time before sunset use negative 333 * numbers. Note that the degree offset is from the vertical, so for a calculation of 14° after sunset, an offset 334 * of 14 + {@link GEOMETRIC_ZENITH} = 104 would have to be passed as a parameter. 335 * @return The {@link java.time.Instant} of the offset after (or before) {@link getSunset()}. If the calculation 336 * can't be computed such as in the Arctic Circle where there is at least one day a year where the sun does not rise, and 337 * and one where it does not set, a <code>null</code> will be returned. See detailed explanation on top of the page. 338 */ 339 public Instant getSunsetOffsetByDegrees(double offsetZenith) { 340 double sunset = getUTCSunset(offsetZenith); 341 return Double.isNaN(sunset) ? null 342 : getInstantFromTime(sunset, SolarEvent.SUNSET); 343 } 344 345 /** 346 * Default constructor will set a default {@link GeoLocation#GeoLocation()}, a default {@link AstronomicalCalculator#getDefault() 347 * AstronomicalCalculator} and default the <code>LocalDate</code> to the current date. 348 */ 349 public AstronomicalCalendar() { 350 this(new GeoLocation()); 351 } 352 353 /** 354 * A constructor that takes in <a href="https://en.wikipedia.org/wiki/Geolocation">geolocation</a> information as a parameter. 355 * The default {@link AstronomicalCalculator#getDefault() AstronomicalCalculator} used for solar calculations is the more 356 * accurate {@link com.kosherjava.zmanim.util.NOAACalculator}. 357 * 358 * @param geoLocation 359 * The location information used for calculating astronomical sun times. 360 * 361 * @see setAstronomicalCalculator(AstronomicalCalculator) for changing the calculator class. 362 */ 363 public AstronomicalCalendar(GeoLocation geoLocation) { 364 setLocalDate(LocalDate.now(geoLocation.getZoneId())); 365 setGeoLocation(geoLocation);// duplicate call 366 setAstronomicalCalculator(AstronomicalCalculator.getDefault()); 367 } 368 369 /** 370 * A method that returns the sunrise in UTC time without correction for time zone offset from GMT and without using daylight 371 * savings time. 372 * 373 * @param zenith 374 * the degrees below the horizon. For time after sunrise use negative numbers. 375 * @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 376 * Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does 377 * not set, {@link Double#NaN} will be returned. See detailed explanation on top of the page. 378 */ 379 public double getUTCSunrise(double zenith) { 380 return getAstronomicalCalculator().getUTCSunrise(getAdjustedLocalDate(), getGeoLocation(), zenith, true); 381 } 382 383 /** 384 * A method that returns the sunrise in UTC time without correction for time zone offset from GMT and without using 385 * daylight savings time. Non-sunrise and sunset calculations such as dawn and dusk, depend on the amount of visible 386 * light, something that is not affected by elevation. This method returns UTC sunrise calculated at sea level. This 387 * forms the base for dawn calculations that are calculated as a dip below the horizon before sunrise. 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 * @see getUTCSunrise(double) 395 * @see getUTCSeaLevelSunset(double) 396 */ 397 public double getUTCSeaLevelSunrise(double zenith) { 398 return getAstronomicalCalculator().getUTCSunrise(getAdjustedLocalDate(), getGeoLocation(), zenith, false); 399 } 400 401 /** 402 * A method that returns the sunset in UTC time without correction for time zone offset from GMT and without using 403 * daylight savings time. 404 * 405 * @param zenith 406 * the degrees below the horizon. For time after sunset 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 getUTCSeaLevelSunset(double) 411 */ 412 public double getUTCSunset(double zenith) { 413 return getAstronomicalCalculator().getUTCSunset(getAdjustedLocalDate(), getGeoLocation(), zenith, true); 414 } 415 416 /** 417 * A method that returns the sunset in UTC time without correction for elevation, time zone offset from GMT and without using 418 * daylight savings time. Non-sunrise and sunset calculations such as dawn and dusk, depend on the amount of visible light, 419 * something that is not affected by elevation. This method returns UTC sunset calculated at sea level. This forms the base for 420 * dusk calculations that are calculated as a dip below the horizon after sunset. 421 * 422 * @param zenith 423 * the degrees below the horizon. For time before sunset use negative numbers. 424 * @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 425 * Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does 426 * not set, {@link Double#NaN} will be returned. See detailed explanation on top of the page. 427 * @see getUTCSunset(double) 428 * @see getUTCSeaLevelSunrise(double) 429 */ 430 public double getUTCSeaLevelSunset(double zenith) { 431 return getAstronomicalCalculator().getUTCSunset(getAdjustedLocalDate(), getGeoLocation(), zenith, false); 432 } 433 434 /** 435 * A method that returns a sea-level based temporal (solar) hour. The day from {@link getSeaLevelSunrise() sea-level sunrise} to 436 * {@link getSeaLevelSunset() sea-level sunset} is split into 12 equal parts with each one being a temporal hour. 437 * 438 * @see getSeaLevelSunrise() 439 * @see getSeaLevelSunset() 440 * @see getTemporalHour(Instant, Instant) 441 * 442 * @return the <code>long</code> millisecond length of a temporal hour. If the calculation can't be computed, 443 * {@link Long#MIN_VALUE} will be returned. See detailed explanation on top of the page. 444 * 445 */ 446 public long getTemporalHour() { 447 return getTemporalHour(getSeaLevelSunrise(), getSeaLevelSunset()); 448 } 449 450 /** 451 * A utility method that will allow the calculation of a temporal (solar) hour based on the sunrise and sunset passed as 452 * parameters to this method. An example of the use of this method would be the calculation of a elevation adjusted temporal 453 * hour by passing in {@link getSunrise() sunrise} and {@link getSunset() sunset} as parameters. 454 * 455 * @param startOfDay 456 * The start of the day. 457 * @param endOfDay 458 * The end of the day. 459 * 460 * @return the <code>long</code> millisecond length of the temporal hour. If the calculation can't be computed a 461 * {@link Long#MIN_VALUE} will be returned. See detailed explanation on top of the page. 462 * 463 * @see getTemporalHour() 464 */ 465 public long getTemporalHour(Instant startOfDay, Instant endOfDay) { 466 if (startOfDay == null || endOfDay == null) { 467 return Long.MIN_VALUE; 468 } 469 470 return Duration.between(startOfDay, endOfDay).toMillis() / 12; 471 } 472 473 /** 474 * A method that returns sundial or solar noon. It occurs when the Sun is <a href= 475 * "https://en.wikipedia.org/wiki/Transit_%28astronomy%29">transiting</a> the <a 476 * href="https://en.wikipedia.org/wiki/Meridian_%28astronomy%29">celestial meridian</a>. The calculations used by this class 477 * depend on the {@link AstronomicalCalculator} used. If this calendar instance is {@link setAstronomicalCalculator( 478 * AstronomicalCalculator) set} to use the {@link com.kosherjava.zmanim.util.NOAACalculator} (the default) it will calculate 479 * astronomical noon. If the calendar instance is to use the {@link com.kosherjava.zmanim.util.SunTimesCalculator}, that does 480 * not have code to calculate astronomical noon, the sun transit is calculated as halfway between sea level sunrise and sea level 481 * sunset, which can be slightly off the real transit time due to changes in declination (the lengthening or shortening day). See 482 * <a href="https://kosherjava.com/2020/07/02/definition-of-chatzos/">The Definition of Chatzos</a> for details on the proper 483 * definition of solar noon / midday. 484 * 485 * @return the <code>Instant</code> representing Sun's transit. If the calculation can't be computed such as when using the {@link 486 * com.kosherjava.zmanim.util.SunTimesCalculator USNO calculator} that does not support getting solar noon for the Arctic 487 * Circle (where there is at least one day a year where the sun does not rise, and one where it does not set), a 488 * <code>null</code> will be returned. See detailed explanation on top of the page. 489 * @see getSunTransit(Instant, Instant) 490 * @see getTemporalHour() 491 * @see com.kosherjava.zmanim.util.NOAACalculator#getUTCNoon(Calendar, GeoLocation) 492 * @see com.kosherjava.zmanim.util.SunTimesCalculator#getUTCNoon(Calendar, GeoLocation) 493 */ 494 public Instant getSunTransit() { 495 double noon = getAstronomicalCalculator().getUTCNoon(getAdjustedLocalDate(), getGeoLocation()); 496 return getInstantFromTime(noon, SolarEvent.NOON); 497 } 498 499 /** 500 * A method that returns solar midnight as the <b>end of the day</b> (that may actually be after midnight of the day it is 501 * being calculated for). For example calculating solar midnight for February 8, will calculate it for midnight between February 502 * 8 and February 9. It occurs when the Sun is <a href="https://en.wikipedia.org/wiki/Transit_%28astronomy%29">transiting</a> the 503 * lower <a href="https://en.wikipedia.org/wiki/Meridian_%28astronomy%29">celestial meridian</a>, or when the sun is at it's 504 * <a href="https://en.wikipedia.org/wiki/Nadir">nadir</a>. The calculations used by this class depend on the {@link 505 * AstronomicalCalculator} used. If this calendar instance is {@link setAstronomicalCalculator(AstronomicalCalculator) set} to use 506 * the {@link com.kosherjava.zmanim.util.NOAACalculator} (the default) it will calculate astronomical midnight. If the calendar 507 * instance is to use the {@link com.kosherjava.zmanim.util.SunTimesCalculator USNO Calculator}, that does not have code to 508 * calculate astronomical noon, midnight is calculated as 12 hours after halfway between sea level sunrise and sea level sunset 509 * of that day. This can be slightly off the real transit time due to changes in declination (the lengthening or shortening day). 510 * See <a href="https://kosherjava.com/2020/07/02/definition-of-chatzos/">The Definition of Chatzos</a> for details on the proper 511 * definition of solar noon / midday. 512 * 513 * @return the <code>Instant</code> representing Sun's lower transit at the <b>end of the current day</b>. If the calculation 514 * can't be computed such as when using the {@link com.kosherjava.zmanim.util.SunTimesCalculator USNO calculator} that does 515 * not support getting solar noon or midnight for the Arctic Circle (where there is at least one day a year where the sun 516 * does not rise, and one where it does not set), a <code>null</code> will be returned. This is not relevant when using the 517 * {@link com.kosherjava.zmanim.util.NOAACalculator NOAA Calculator} that is never expected to return <code>null</code>. 518 * See the detailed explanation on top of the page. 519 * 520 * @see getSunTransit() 521 * @see com.kosherjava.zmanim.util.NOAACalculator#getUTCNoon(Calendar, GeoLocation) 522 * @see com.kosherjava.zmanim.util.SunTimesCalculator#getUTCNoon(Calendar, GeoLocation) 523 */ 524 public Instant getSolarMidnight() { 525 double noon = getAstronomicalCalculator().getUTCMidnight(getAdjustedLocalDate(), getGeoLocation()); 526 return getInstantFromTime(noon, SolarEvent.MIDNIGHT); 527 } 528 529 /** 530 * A method that returns sundial or solar noon (or midnight) calculated as halfway between the times passed in. It is close to, 531 * but not exactly occurs when the Sun is <a href="https://en.wikipedia.org/wiki/Transit_%28astronomy%29">transiting</a> the 532 * <a href="https://en.wikipedia.org/wiki/Meridian_%28astronomy%29">celestial meridian</a>. It will not exactly match the 533 * astronomical transit, due to changes in declination (the lengthening or shortening day). 534 * 535 * @param startOfDay 536 * the start of day for calculating the sun's transit. This can be sea level sunrise, visual sunrise (or any arbitrary 537 * start of day) passed to this method. 538 * @param endOfDay 539 * the end of day for calculating the sun's transit. This can be sea level sunset, visual sunset (or any arbitrary end 540 * of day) passed to this method. 541 * 542 * @return the <code>Instant</code> representing Sun's transit. If the calculation can't be computed such as in the 543 * Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does 544 * not set, <code>null</code> will be returned. See detailed explanation on top of the page. 545 */ 546 public Instant getSunTransit(Instant startOfDay, Instant endOfDay) { 547 long temporalHour = getTemporalHour(startOfDay, endOfDay); 548 if (temporalHour == Long.MIN_VALUE) { 549 return null; 550 } 551 return getTimeOffset(startOfDay, temporalHour * 6); 552 } 553 554 /** 555 * An enum to indicate what type of solar event is being calculated. 556 */ 557 protected enum SolarEvent { 558 /**SUNRISE A solar event related to sunrise*/SUNRISE, /**SUNSET A solar event related to sunset*/SUNSET, 559 /**NOON A solar event related to noon*/NOON, /**MIDNIGHT A solar event related to midnight*/MIDNIGHT, 560 /**NONE solar event representing azimuth or elevation calculations that that can be any time of the day*/ NONE; 561 } 562 563 /** 564 * Return the time at a given azimuth. This often will not occur and a null will be returned. 565 * @param azimuth the azimuth that you want to get the time of day for. 566 * @return the time that the azimuth will be reached. There are cases where this azimuth will never be reached for the date 567 * and location, and a null will be returned in that case. 568 * @see com.kosherjava.zmanim.util.AstronomicalCalculator#getTimeAtAzimuth(LocalDate, GeoLocation, double) 569 */ 570 public Instant getTimeAtAzimuth(double azimuth) { 571 double rawAzimuth = getAstronomicalCalculator().getTimeAtAzimuth(getAdjustedLocalDate(), getGeoLocation(), azimuth); 572 return getInstantFromTime(rawAzimuth, SolarEvent.NONE); 573 } 574 575 /** 576 * A method that returns an <code>Instant</code> from the time passed in as a parameter. 577 * 578 * @param time 579 * The time to be set as the time for the <code>Instant</code>. The time expected is in the format: 18.75 580 * for 6:45:00 PM.time is sunrise and false if it is sunset 581 * @param solarEvent the type of {@link SolarEvent} 582 * @return The Instant object representation of the time double 583 */ 584 protected Instant getInstantFromTime(double time, SolarEvent solarEvent) { 585 if (Double.isNaN(time)) { 586 return null; 587 } 588 589 LocalDate date = getAdjustedLocalDate(); 590 591 double localTimeHours = (getGeoLocation().getLongitude() / 15) + time; 592 593 if (solarEvent == SolarEvent.SUNRISE && localTimeHours > 18) { 594 date = date.minusDays(1); 595 } else if (solarEvent == SolarEvent.SUNSET && localTimeHours < 6) { 596 date = date.plusDays(1); 597 } else if (solarEvent == SolarEvent.MIDNIGHT && localTimeHours < 12) { 598 date = date.plusDays(1); 599 } else if (solarEvent == SolarEvent.NOON) { 600 if (localTimeHours < 0) { 601 date = date.plusDays(1); 602 } else if (localTimeHours > 24) { 603 date = date.minusDays(1); 604 } 605 } 606 607 // Math.round(time * HOUR_MILLIS) * 1_000_000L could be used below, but this exactly matches the pre-3.0 Date-based code. 608 LocalDateTime dateTime = date.atStartOfDay().plusNanos((long)(time * HOUR_MILLIS) * 1_000_000L); 609 610 // The computed time is in UTC fractional hours; anchor in UTC before converting. 611 return ZonedDateTime.of(dateTime, ZoneOffset.UTC).toInstant(); 612 } 613 614 /** 615 * Returns the sun's elevation (number of degrees) below the horizon before sunrise that matches the offset minutes 616 * on passed in as a parameter. For example passing in 72 minutes for a calendar set to the equinox in Jerusalem 617 * returns a value close to 16.1°. 618 * 619 * @param minutes 620 * minutes before sunrise 621 * @return the degrees below the horizon before sunrise that match the offset in minutes passed it as a parameter. If 622 * the calculation can't be computed (no sunrise occurs on this day) a {@link Double#NaN} will be returned. 623 * @deprecated This method is slow and inefficient and should NEVER be used in a loop. This method should be replaced 624 * by calls to {@link AstronomicalCalculator#getSolarElevation(Calendar, GeoLocation)}. That method will 625 * efficiently return the the solar elevation (the sun's position in degrees below (or above) the horizon) 626 * at the given time even in the arctic when there is no sunrise. 627 * @see AstronomicalCalculator#getSolarElevation(Calendar, GeoLocation) 628 * @see getSunsetSolarDipFromOffset(double) 629 */ 630 @Deprecated(forRemoval=false) 631 public double getSunriseSolarDipFromOffset(double minutes) { 632 Instant offsetByDegrees = getSeaLevelSunrise(); 633 if(offsetByDegrees == null) { 634 return Double.NaN; 635 } 636 Instant offsetByTime = getTimeOffset(getSeaLevelSunrise(), -(minutes * MINUTE_MILLIS)); 637 638 BigDecimal degrees = new BigDecimal(0); 639 BigDecimal incrementor = new BigDecimal("0.0001"); 640 641 while (offsetByDegrees == null || ((minutes < 0.0 && offsetByDegrees.toEpochMilli() < offsetByTime.toEpochMilli()) || 642 (minutes > 0.0 && offsetByDegrees.toEpochMilli() > offsetByTime.toEpochMilli()))) { 643 if (minutes > 0.0) { 644 degrees = degrees.add(incrementor); 645 } else { 646 degrees = degrees.subtract(incrementor); 647 } 648 offsetByDegrees = getSunriseOffsetByDegrees(GEOMETRIC_ZENITH + degrees.doubleValue()); 649 } 650 return degrees.doubleValue(); 651 } 652 653 /** 654 * Returns the sun's elevation (number of degrees) below the horizon after sunset that matches the offset minutes 655 * passed in as a parameter. For example passing in 72 minutes for a calendar set to the equinox in Jerusalem 656 * returns a value close to 16.1°. 657 * 658 * @param minutes 659 * minutes after sunset 660 * @return the degrees below the horizon after sunset that match the offset in minutes passed it as a parameter. If 661 * the calculation can't be computed (no sunset occurs on this day) a {@link Double#NaN} will be returned. 662 * @deprecated This method is slow and inefficient and should NEVER be used in a loop. This method should be replaced 663 * by calls to {@link AstronomicalCalculator#getSolarElevation(ZonedDateTime, GeoLocation)}. That method will 664 * efficiently return the the solar elevation (the sun's position in degrees below (or above) the horizon) 665 * at the given time even in the arctic when there is no sunrise. 666 * @see AstronomicalCalculator#getSolarElevation(ZonedDateTime, GeoLocation) 667 * @see getSunriseSolarDipFromOffset(double) 668 */ 669 @Deprecated(forRemoval=false) 670 public double getSunsetSolarDipFromOffset(double minutes) { 671 Instant offsetByDegrees = getSeaLevelSunset(); 672 if(offsetByDegrees == null) { 673 return Double.NaN; 674 } 675 Instant offsetByTime = getTimeOffset(getSeaLevelSunset(), minutes * MINUTE_MILLIS); 676 BigDecimal degrees = new BigDecimal(0); 677 BigDecimal incrementor = new BigDecimal("0.001"); 678 while (offsetByDegrees == null || ((minutes > 0.0 && offsetByDegrees.toEpochMilli() < offsetByTime.toEpochMilli()) || 679 (minutes < 0.0 && offsetByDegrees.toEpochMilli() > offsetByTime.toEpochMilli()))) { 680 if (minutes > 0.0) { 681 degrees = degrees.add(incrementor); 682 } else { 683 degrees = degrees.subtract(incrementor); 684 } 685 offsetByDegrees = getSunsetOffsetByDegrees(GEOMETRIC_ZENITH + degrees.doubleValue()); 686 } 687 return degrees.doubleValue(); 688 } 689 690 /** 691 * A method that returns <a href="https://en.wikipedia.org/wiki/Local_mean_time">local mean time (LMT)</a> time converted to 692 * regular clock time for the local wall-clock time passed to this method. This time is adjusted from standard time to account for 693 * the local latitude. The 360° of the globe divided by 24 calculates to 15° per hour with 4 minutes per degree, so at a 694 * longitude of 0 , 15, 30 etc... noon is at exactly 12:00pm. Lakewood, N.J., with a longitude of -74.222, is 0.7906 away from the 695 * closest multiple of 15 at -75°. This is multiplied by 4 clock minutes (per degree) to yield 3 minutes and 7 seconds for a 696 * noon time of 11:56:53am. This method is not tied to the theoretical 15° time zones, but will adjust to the actual time zone 697 * and <a href="https://en.wikipedia.org/wiki/Daylight_saving_time">Daylight saving time</a> to return LMT. 698 * 699 * @param localTime 700 * the local wall-clock time (such as 12:00 for noon and 00:00 for midnight) to calculate as LMT. 701 * @return the <code>Instant</code> representing the local mean time (LMT) for the time passed in. In Lakewood, 702 * NJ, passing noon will return 11:56:50am. 703 * @see GeoLocation#getLocalMeanTimeOffset(Instant) 704 */ 705 public Instant getLocalMeanTime(LocalTime localTime) { 706 Instant localMeanTime = LocalDateTime.of(getAdjustedLocalDate(), localTime).toInstant(ZoneOffset.UTC); 707 long longitudeOffsetMillis = (long) (getGeoLocation().getLongitude() * 4 * MINUTE_MILLIS); 708 return getTimeOffset(localMeanTime, -longitudeOffsetMillis); 709 } 710 711 /** 712 * Adjusts the <code>LocalDate</code> to deal with edge cases where the location crosses the antimeridian. 713 * 714 * @see GeoLocation#getAntimeridianAdjustment(Instant) 715 * @return the adjusted Calendar 716 */ 717 protected LocalDate getAdjustedLocalDate(){ 718 int offset = getGeoLocation().getAntimeridianAdjustment(getMidnightLastNight().toInstant()); 719 return offset == 0 ? getLocalDate() : getLocalDate().plusDays(offset); 720 } 721 722 /** 723 * Used by Molad based <em>zmanim</em> to determine if <em>zmanim</em> occur during the current day. This is also used as the 724 * anchor for current timezone-offset calculations. 725 * @return midnight at the start of the current local date in the configured timezone 726 */ 727 protected ZonedDateTime getMidnightLastNight() { 728 return ZonedDateTime.of(getLocalDate(),LocalTime.MIDNIGHT,getGeoLocation().getZoneId()); 729 } 730 731 /** 732 * Used by Molad based <em>zmanim</em> to determine if <em>zmanim</em> occur during the current day. 733 * @return following midnight 734 */ 735 protected ZonedDateTime getMidnightTonight() { 736 return ZonedDateTime.of(getLocalDate().plusDays(1),LocalTime.MIDNIGHT,getGeoLocation().getZoneId()); 737 } 738 739 /** 740 * Returns an XML formatted representation of the class using the default output of the 741 * {@link com.kosherjava.zmanim.util.ZmanimFormatter#toXML(AstronomicalCalendar) toXML} method. 742 * @return an XML formatted representation of the class. It returns the default output of the 743 * {@link com.kosherjava.zmanim.util.ZmanimFormatter#toXML(AstronomicalCalendar) toXML} method. 744 * @see com.kosherjava.zmanim.util.ZmanimFormatter#toXML(AstronomicalCalendar) 745 * @see java.lang.Object#toString() 746 */ 747 public String toString() { 748 return ZmanimFormatter.toXML(this); 749 } 750 751 /** 752 * Returns a JSON formatted representation of the class using the default output of the 753 * {@link com.kosherjava.zmanim.util.ZmanimFormatter#toJSON(AstronomicalCalendar) toJSON} method. 754 * @return a JSON formatted representation of the class. It returns the default output of the 755 * {@link com.kosherjava.zmanim.util.ZmanimFormatter#toJSON(AstronomicalCalendar) toJSON} method. 756 * @see com.kosherjava.zmanim.util.ZmanimFormatter#toJSON(AstronomicalCalendar) 757 * @see java.lang.Object#toString() 758 */ 759 public String toJSON() { 760 return ZmanimFormatter.toJSON(this); 761 } 762 763 /** 764 * @see java.lang.Object#equals(Object) 765 */ 766 public boolean equals(Object object) { 767 if (this == object) { 768 return true; 769 } 770 if (object == null || getClass() != object.getClass()) { 771 return false; 772 } 773 AstronomicalCalendar aCal = (AstronomicalCalendar) object; 774 return Objects.equals(getLocalDate(), aCal.getLocalDate()) 775 && Objects.equals(getGeoLocation(), aCal.getGeoLocation()) 776 && Objects.equals(getAstronomicalCalculator(), aCal.getAstronomicalCalculator()); 777 } 778 779 /** 780 * @see java.lang.Object#hashCode() 781 */ 782 public int hashCode() { 783 int result = 17; 784 result = 37 * result + getClass().hashCode(); // needed or this and subclasses will return identical hash 785 result += 37 * result + Objects.hashCode(getLocalDate()); 786 result += 37 * result + Objects.hashCode(getGeoLocation()); 787 result += 37 * result + Objects.hashCode(getAstronomicalCalculator()); 788 return result; 789 } 790 791 /** 792 * A method that returns the currently set {@link GeoLocation} which contains location information used for the 793 * astronomical calculations. 794 * 795 * @return Returns the geoLocation. 796 */ 797 public GeoLocation getGeoLocation() { 798 return this.geoLocation; 799 } 800 801 /** 802 * Sets the {@link GeoLocation} <code>Object</code> to be used for astronomical calculations. 803 * 804 * @param geoLocation 805 * The geoLocation to set. 806 * @todo Possibly adjust for horizon elevation. It may be smart to just have the calculator check the GeoLocation 807 * though it doesn't really belong there. 808 */ 809 public void setGeoLocation(GeoLocation geoLocation) { 810 this.geoLocation = geoLocation; 811 } 812 813 /** 814 * A method that returns the currently set AstronomicalCalculator. 815 * 816 * @return Returns the astronomicalCalculator. 817 * @see setAstronomicalCalculator(AstronomicalCalculator) 818 */ 819 public AstronomicalCalculator getAstronomicalCalculator() { 820 return this.astronomicalCalculator; 821 } 822 823 /** 824 * A method to set the {@link AstronomicalCalculator} used for astronomical calculations. The Zmanim package ships 825 * with a number of different implementations of the <code>abstract</code> {@link AstronomicalCalculator} based on 826 * different algorithms, including the default {@link com.kosherjava.zmanim.util.NOAACalculator} based on <a href= 827 * "https://noaa.gov">NOAA's</a> implementation of Jean Meeus's algorithms as well as {@link 828 * com.kosherjava.zmanim.util.SunTimesCalculator} based on the <a href = "https://www.cnmoc.usff.navy.mil/usno/">US 829 * Naval Observatory's</a> algorithm. This allows easy runtime switching and comparison of different algorithms. 830 * 831 * @param astronomicalCalculator 832 * The astronomicalCalculator to set. 833 */ 834 public void setAstronomicalCalculator(AstronomicalCalculator astronomicalCalculator) { 835 this.astronomicalCalculator = astronomicalCalculator; 836 } 837 838 /** 839 * returns the <code>LocalDate</code> object encapsulated in this class. 840 * 841 * @return Returns the <code>LocalDate</code>. 842 */ 843 public LocalDate getLocalDate() { 844 return this.localDate; 845 } 846 847 /** 848 * Sets the <code>LocalDate</code> object for us in this class. 849 * @param localDate 850 * The <code>LocalDate</code> to set. 851 */ 852 public void setLocalDate(LocalDate localDate) { 853 this.localDate = localDate; 854 } 855 856 /** 857 * A method that creates a <a href="https://en.wikipedia.org/wiki/Object_copy#Deep_copy">deep copy</a> of the object. 858 * 859 * @see java.lang.Object#clone() 860 */ 861 public Object clone() { 862 AstronomicalCalendar clone = null; 863 try { 864 clone = (AstronomicalCalendar) super.clone(); 865 } catch (CloneNotSupportedException cnse) { 866 // Required by the compiler. Should never be reached since we implement clone() 867 } 868 if (clone != null) { 869 clone.setGeoLocation((GeoLocation) getGeoLocation().clone()); // consider converting the GeoLocation class to be immutable to avoid the deep copy 870 clone.setAstronomicalCalculator((AstronomicalCalculator) getAstronomicalCalculator().clone()); // likely not needed 871 } 872 return clone; 873 } 874}