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