1
1
/*
2
2
* Zmanim Java API
3
- * Copyright (C) 2004-2024 Eliyahu Hershfeld
3
+ * Copyright (C) 2004-2025 Eliyahu Hershfeld
4
4
*
5
5
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General
6
6
* Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option)
17
17
18
18
import java .util .Calendar ;
19
19
20
-
21
20
/**
22
21
* Implementation of sunrise and sunset methods to calculate astronomical times based on the <a
23
22
* href="https://noaa.gov">NOAA</a> algorithm. This calculator uses the Java algorithm based on the implementation by <a
29
28
* to account for elevation. The algorithm can be found in the <a
30
29
* href="https://en.wikipedia.org/wiki/Sunrise_equation">Wikipedia Sunrise Equation</a> article.
31
30
*
32
- * @author © Eliyahu Hershfeld 2011 - 2024
31
+ * @author © Eliyahu Hershfeld 2011 - 2025
33
32
*/
34
33
public class NOAACalculator extends AstronomicalCalculator {
34
+
35
35
/**
36
36
* The <a href="https://en.wikipedia.org/wiki/Julian_day">Julian day</a> of January 1, 2000, known as
37
37
* <a href="https://en.wikipedia.org/wiki/Epoch_(astronomy)#J2000">J2000.0</a>.
@@ -44,12 +44,20 @@ public class NOAACalculator extends AstronomicalCalculator {
44
44
private static final double JULIAN_DAYS_PER_CENTURY = 36525.0 ;
45
45
46
46
/**
47
- * An enum to indicate what type of solar event is being calculated.
47
+ * An <code>enum</code> to indicate what type of solar event ({@link #SUNRISE SUNRISE}, {@link #SUNSET SUNSET},
48
+ * {@link #NOON NOON} or {@link #MIDNIGHT MIDNIGHT}) is being calculated.
49
+ * .
48
50
*/
49
51
protected enum SolarEvent {
50
- /**SUNRISE A solar event related to sunrise*/ SUNRISE , /**SUNSET A solar event related to sunset*/ SUNSET
51
- // possibly add the following in the future, if added, an IllegalArgumentException should be thrown in getSunHourAngle
52
- // /**NOON A solar event related to noon*/NOON, /**MIDNIGHT A solar event related to midnight*/MIDNIGHT
52
+ /**SUNRISE A solar event related to sunrise*/ SUNRISE , /**SUNSET A solar event related to sunset*/ SUNSET ,
53
+ /**NOON A solar event related to noon*/ NOON , /**MIDNIGHT A solar event related to midnight*/ MIDNIGHT
54
+ }
55
+
56
+ /**
57
+ * Default constructor of the NOAACalculator.
58
+ */
59
+ public NOAACalculator () {
60
+ super ();
53
61
}
54
62
55
63
/**
@@ -84,7 +92,7 @@ public double getUTCSunset(Calendar calendar, GeoLocation geoLocation, double ze
84
92
}
85
93
86
94
/**
87
- * Return the <a href="https://en.wikipedia.org/wiki/Julian_day">Julian day</a> from a Java Calendar
95
+ * Return the <a href="https://en.wikipedia.org/wiki/Julian_day">Julian day</a> from a Java Calendar.
88
96
*
89
97
* @param calendar
90
98
* The Java Calendar
@@ -297,7 +305,7 @@ private static double getEquationOfTime(double julianCenturies) {
297
305
* @param zenith
298
306
* the zenith
299
307
* @param solarEvent
300
- * If the hour angle is for sunrise or sunset
308
+ * If the hour angle is for {@link SolarEvent#SUNRISE SUNRISE} or {@link SolarEvent#SUNSET SUNSET}
301
309
* @return hour angle of sunrise in <a href="https://en.wikipedia.org/wiki/Radian">radians</a>
302
310
*/
303
311
private static double getSunHourAngle (double latitude , double solarDeclination , double zenith , SolarEvent solarEvent ) {
@@ -380,7 +388,7 @@ public static double getSolarAzimuth(Calendar calendar, double latitude, double
380
388
* "https://kosherjava.com/2020/07/02/definition-of-chatzos/">The Definition of <em>Chatzos</em></a> for details on
381
389
* solar noon calculations.
382
390
* @see com.kosherjava.zmanim.util.AstronomicalCalculator#getUTCNoon(Calendar, GeoLocation)
383
- * @see #getSolarNoonUTC (double, double)
391
+ * @see #getSolarNoonMidnightUTC (double, double, SolarEvent )
384
392
*
385
393
* @param calendar
386
394
* The Calendar representing the date to calculate solar noon for
@@ -390,56 +398,81 @@ public static double getSolarAzimuth(Calendar calendar, double latitude, double
390
398
* @return the time in minutes from zero UTC
391
399
*/
392
400
public double getUTCNoon (Calendar calendar , GeoLocation geoLocation ) {
393
- double noon = getSolarNoonUTC (getJulianDay (calendar ), -geoLocation .getLongitude ());
401
+ double noon = getSolarNoonMidnightUTC (getJulianDay (calendar ), -geoLocation .getLongitude (), SolarEvent . NOON );
394
402
noon = noon / 60 ;
395
403
return noon > 0 ? noon % 24 : noon % 24 + 24 ; // ensure that the time is >= 0 and < 24
396
404
}
405
+
406
+ /**
407
+ * Return the <a href="https://en.wikipedia.org/wiki/Universal_Coordinated_Time">Universal Coordinated Time</a>
408
+ * (UTC) of the <a href="https://en.wikipedia.org/wiki/Midnight">solar midnight</a> for the end of the given civil
409
+ * day at the given location on earth (about 12 hours after solar noon). This implementation returns true solar
410
+ * midnight as opposed to the time halfway between sunrise and sunset. Other calculators may return a more
411
+ * simplified calculation of halfway between sunrise and sunset. See <a href=
412
+ * "https://kosherjava.com/2020/07/02/definition-of-chatzos/">The Definition of <em>Chatzos</em></a> for details on
413
+ * solar noon / midnight calculations.
414
+ * @see com.kosherjava.zmanim.util.AstronomicalCalculator#getUTCNoon(Calendar, GeoLocation)
415
+ * @see #getSolarNoonMidnightUTC(double, double, SolarEvent)
416
+ *
417
+ * @param calendar
418
+ * The Calendar representing the date to calculate solar noon for
419
+ * @param geoLocation
420
+ * The location information used for astronomical calculating sun times. This class uses only requires
421
+ * the longitude for calculating noon since it is the same time anywhere along the longitude line.
422
+ * @return the time in minutes from zero UTC
423
+ */
424
+ public double getUTCMidnight (Calendar calendar , GeoLocation geoLocation ) {
425
+ double midnight = getSolarNoonMidnightUTC (getJulianDay (calendar ), -geoLocation .getLongitude (), SolarEvent .MIDNIGHT );
426
+ midnight = midnight / 60 ;
427
+ return midnight > 0 ? midnight % 24 : midnight % 24 + 24 ; // ensure that the time is >= 0 and < 24
428
+ }
397
429
398
430
/**
399
431
* Return the <a href="https://en.wikipedia.org/wiki/Universal_Coordinated_Time">Universal Coordinated Time</a> (UTC)
400
- * of <a href="http://en.wikipedia.org/wiki/Noon#Solar_noon">solar noon</a> for the given day at the given location
401
- * on earth.
402
- * @todo Refactor to possibly use the getSunRiseSetUTC (to be renamed) and remove the need for this method.
432
+ * of the current day <a href="http://en.wikipedia.org/wiki/Noon#Solar_noon">solar noon</a> or the the upcoming
433
+ * midnight (about 12 hours after solar noon) of the given day at the given location on earth.
403
434
*
404
435
* @param julianDay
405
- * the Julian day since <a href=
436
+ * The Julian day since <a href=
406
437
* "https://en.wikipedia.org/wiki/Epoch_(astronomy)#J2000">J2000.0</a>.
407
438
* @param longitude
408
- * the longitude of observer in degrees
409
- *
439
+ * The longitude of observer in degrees
440
+ * @param solarEvent
441
+ * If the calculation is for {@link SolarEvent#NOON NOON} or {@link SolarEvent#MIDNIGHT MIDNIGHT}
442
+ *
410
443
* @return the time in minutes from zero UTC
411
444
*
412
445
* @see com.kosherjava.zmanim.util.AstronomicalCalculator#getUTCNoon(Calendar, GeoLocation)
413
446
* @see #getUTCNoon(Calendar, GeoLocation)
414
447
*/
415
- private static double getSolarNoonUTC (double julianDay , double longitude ) {
448
+ private static double getSolarNoonMidnightUTC (double julianDay , double longitude , SolarEvent solarEvent ) {
449
+ julianDay = (solarEvent == SolarEvent .NOON ) ? julianDay : julianDay + 0.5 ;
416
450
// First pass for approximate solar noon to calculate equation of time
417
451
double tnoon = getJulianCenturiesFromJulianDay (julianDay + longitude / 360.0 );
418
452
double equationOfTime = getEquationOfTime (tnoon );
419
- double solNoonUTC = 720 + (longitude * 4 ) - equationOfTime ; // minutes
453
+ double solNoonUTC = (longitude * 4 ) - equationOfTime ; // minutes
420
454
421
455
// second pass
422
- double newt = getJulianCenturiesFromJulianDay (julianDay - 0.5 + solNoonUTC / 1440.0 );
456
+ double newt = getJulianCenturiesFromJulianDay (julianDay + solNoonUTC / 1440.0 );
423
457
equationOfTime = getEquationOfTime (newt );
424
- return 720 + (longitude * 4 ) - equationOfTime ;
458
+ return ( solarEvent == SolarEvent . NOON ? 720 : 1440 ) + (longitude * 4 ) - equationOfTime ;
425
459
}
426
460
427
461
/**
428
462
* Return the <a href="https://en.wikipedia.org/wiki/Universal_Coordinated_Time">Universal Coordinated Time</a> (UTC)
429
463
* of sunrise or sunset in minutes for the given day at the given location on earth.
430
- * @todo support solar noon and possibly increase the number of passes in the Arctic areas.
431
- * This would remove the need for getSolarNoonUTC.
464
+ * @todo Possibly increase the number of passes for improved accuracy, especially in the Arctic areas.
432
465
*
433
466
* @param calendar
434
- * the calendar
467
+ * The calendar
435
468
* @param latitude
436
- * the latitude of observer in degrees
469
+ * The latitude of observer in degrees
437
470
* @param longitude
438
- * longitude of observer in degrees
471
+ * Longitude of observer in degrees
439
472
* @param zenith
440
- * zenith
473
+ * Zenith
441
474
* @param solarEvent
442
- * Is the calculation for sunrise or sunset
475
+ * If the calculation is for {@link SolarEvent#SUNRISE SUNRISE} or {@link SolarEvent#SUNSET SUNSET}
443
476
* @return the time in minutes from zero Universal Coordinated Time (UTC)
444
477
*/
445
478
private static double getSunRiseSetUTC (Calendar calendar , double latitude , double longitude , double zenith ,
@@ -448,7 +481,12 @@ private static double getSunRiseSetUTC(Calendar calendar, double latitude, doubl
448
481
449
482
// Find the time of solar noon at the location, and use that declination.
450
483
// This is better than start of the Julian day
451
- double noonmin = getSolarNoonUTC (julianDay , longitude );
484
+ // TODO really not needed since the Julian day starts from local fixed noon. Changing this would be more
485
+ // efficient but would likely cause a very minor discrepancy in the calculated times (likely not reducing
486
+ // accuracy, just slightly different, thus potentially breaking test cases). Regardless, it would be within
487
+ // milliseconds.
488
+ double noonmin = getSolarNoonMidnightUTC (julianDay , longitude , SolarEvent .NOON );
489
+
452
490
double tnoon = getJulianCenturiesFromJulianDay (julianDay + noonmin / 1440.0 );
453
491
454
492
// First calculates sunrise and approximate length of day
0 commit comments