Skip to content

Commit bceb48a

Browse files
authored
Start using java.time classes
Breaking changes
1 parent f18e6b3 commit bceb48a

1 file changed

Lines changed: 83 additions & 88 deletions

File tree

src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java

Lines changed: 83 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* Zmanim Java API
3-
* Copyright (C) 2004-2025 Eliyahu Hershfeld
3+
* Copyright (C) 2004-2026 Eliyahu Hershfeld
44
*
55
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General
66
* Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option)
@@ -15,7 +15,8 @@
1515
*/
1616
package com.kosherjava.zmanim.util;
1717

18-
import java.util.Calendar;
18+
import java.time.ZoneOffset;
19+
import java.time.ZonedDateTime;
1920

2021
/**
2122
* Implementation of sunrise and sunset methods to calculate astronomical times based on the <a
@@ -28,7 +29,7 @@
2829
* to account for elevation. The algorithm can be found in the <a
2930
* href="https://en.wikipedia.org/wiki/Sunrise_equation">Wikipedia Sunrise Equation</a> article.
3031
*
31-
* @author &copy; Eliyahu Hershfeld 2011 - 2025
32+
* @author &copy; Eliyahu Hershfeld 2011 - 2026
3233
*/
3334
public class NOAACalculator extends AstronomicalCalculator {
3435

@@ -70,10 +71,10 @@ public String getCalculatorName() {
7071
/**
7172
* @see com.kosherjava.zmanim.util.AstronomicalCalculator#getUTCSunrise(Calendar, GeoLocation, double, boolean)
7273
*/
73-
public double getUTCSunrise(Calendar calendar, GeoLocation geoLocation, double zenith, boolean adjustForElevation) {
74+
public double getUTCSunrise(ZonedDateTime zdt, GeoLocation geoLocation, double zenith, boolean adjustForElevation) {
7475
double elevation = adjustForElevation ? geoLocation.getElevation() : 0;
7576
double adjustedZenith = adjustZenith(zenith, elevation);
76-
double sunrise = getSunRiseSetUTC(calendar, geoLocation.getLatitude(), -geoLocation.getLongitude(),
77+
double sunrise = getSunRiseSetUTC(zdt, geoLocation.getLatitude(), -geoLocation.getLongitude(),
7778
adjustedZenith, SolarEvent.SUNRISE);
7879
sunrise = sunrise / 60;
7980
return sunrise > 0 ? sunrise % 24 : sunrise % 24 + 24; // ensure that the time is >= 0 and < 24
@@ -82,10 +83,10 @@ public double getUTCSunrise(Calendar calendar, GeoLocation geoLocation, double z
8283
/**
8384
* @see com.kosherjava.zmanim.util.AstronomicalCalculator#getUTCSunset(Calendar, GeoLocation, double, boolean)
8485
*/
85-
public double getUTCSunset(Calendar calendar, GeoLocation geoLocation, double zenith, boolean adjustForElevation) {
86+
public double getUTCSunset(ZonedDateTime zdt, GeoLocation geoLocation, double zenith, boolean adjustForElevation) {
8687
double elevation = adjustForElevation ? geoLocation.getElevation() : 0;
8788
double adjustedZenith = adjustZenith(zenith, elevation);
88-
double sunset = getSunRiseSetUTC(calendar, geoLocation.getLatitude(), -geoLocation.getLongitude(),
89+
double sunset = getSunRiseSetUTC(zdt, geoLocation.getLatitude(), -geoLocation.getLongitude(),
8990
adjustedZenith, SolarEvent.SUNSET);
9091
sunset = sunset / 60;
9192
return sunset > 0 ? sunset % 24 : sunset % 24 + 24; // ensure that the time is >= 0 and < 24
@@ -94,22 +95,25 @@ public double getUTCSunset(Calendar calendar, GeoLocation geoLocation, double ze
9495
/**
9596
* Return the <a href="https://en.wikipedia.org/wiki/Julian_day">Julian day</a> from a Java Calendar.
9697
*
97-
* @param calendar
98-
* The Java Calendar
98+
* @param zonedDateTime
99+
* The ZonedDateTime
99100
* @return the Julian day corresponding to the date Note: Number is returned for the start of the Julian
100101
* day. Fractional days / time should be added later.
101102
*/
102-
private static double getJulianDay(Calendar calendar) {
103-
int year = calendar.get(Calendar.YEAR);
104-
int month = calendar.get(Calendar.MONTH) + 1;
105-
int day = calendar.get(Calendar.DAY_OF_MONTH);
106-
if (month <= 2) {
107-
year -= 1;
108-
month += 12;
109-
}
110-
int a = year / 100;
111-
int b = 2 - a + a / 4;
112-
return Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + b - 1524.5;
103+
private static double getJulianDay(ZonedDateTime zonedDateTime) {
104+
int year = zonedDateTime.getYear();
105+
int month = zonedDateTime.getMonthValue();
106+
int day = zonedDateTime.getDayOfMonth();
107+
108+
if (month <= 2) {
109+
year -= 1;
110+
month += 12;
111+
}
112+
113+
int a = year / 100;
114+
int b = 2 - a + a / 4;
115+
116+
return Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + b - 1524.5;
113117
}
114118

115119
/**
@@ -309,16 +313,16 @@ private static double getSunHourAngle(double latitude, double solarDeclination,
309313
/**
310314
* @see com.kosherjava.zmanim.util.AstronomicalCalculator#getSolarElevation(Calendar, GeoLocation)
311315
*/
312-
public double getSolarElevation(Calendar calendar, GeoLocation geoLocation) {
313-
return getSolarElevationAzimuth(calendar, geoLocation, false);
316+
public double getSolarElevation(ZonedDateTime zdt, GeoLocation geoLocation) {
317+
return getSolarElevationAzimuth(zdt, geoLocation, false);
314318

315319
}
316320

317321
/**
318322
* @see com.kosherjava.zmanim.util.AstronomicalCalculator#getSolarAzimuth(Calendar, GeoLocation)
319323
*/
320-
public double getSolarAzimuth(Calendar calendar, GeoLocation geoLocation) {
321-
return getSolarElevationAzimuth(calendar, geoLocation, true);
324+
public double getSolarAzimuth(ZonedDateTime zdt, GeoLocation geoLocation) {
325+
return getSolarElevationAzimuth(zdt, geoLocation, true);
322326
}
323327

324328
/**
@@ -327,7 +331,7 @@ public double getSolarAzimuth(Calendar calendar, GeoLocation geoLocation) {
327331
* and time. Can be negative if the sun is below the horizon. Elevation is based on sea-level and is not
328332
* adjusted for altitude.
329333
*
330-
* @param calendar
334+
* @param zonedDateTime
331335
* time of calculation
332336
* @param geoLocation
333337
* The location for calculating the elevation or azimuth.
@@ -338,59 +342,50 @@ public double getSolarAzimuth(Calendar calendar, GeoLocation geoLocation) {
338342
* @see #getSolarElevation(Calendar, GeoLocation)
339343
* @see #getSolarAzimuth(Calendar, GeoLocation)
340344
*/
341-
private double getSolarElevationAzimuth(Calendar calendar, GeoLocation geoLocation, boolean isAzimuth) {
342-
double latitude = geoLocation.getLatitude();
343-
double longitude = geoLocation.getLongitude();
344-
345-
Calendar cloned = (Calendar) calendar.clone();
346-
int offset = - adjustHourForTimeZone(cloned);
347-
cloned.add(Calendar.MILLISECOND, offset);
348-
int minute = cloned.get(Calendar.MINUTE);
349-
int second = cloned.get(Calendar.SECOND);
350-
int hour = cloned.get(Calendar.HOUR_OF_DAY);
351-
int milli = cloned.get(Calendar.MILLISECOND);
352-
353-
double time = (hour + (minute + (second + (milli / 1000.0)) / 60.0) / 60.0 ) / 24.0;
354-
double julianDay = getJulianDay(cloned) + time;
355-
double julianCenturies = getJulianCenturiesFromJulianDay(julianDay);
356-
double eot = getEquationOfTime(julianCenturies);
357-
double theta = getSunDeclination(julianCenturies);
358-
359-
double adjustment = time + eot / 1440;
360-
double trueSolarTime = ((adjustment + longitude / 360) + 2) % 1; // adding 2 to ensure that it never ends up negative
361-
double hourAngelRad = trueSolarTime * Math.PI * 2 - Math.PI;
362-
double cosZenith = Math.sin(Math.toRadians(latitude)) * Math.sin(Math.toRadians(theta))
363-
+ Math.cos(Math.toRadians(latitude)) * Math.cos(Math.toRadians(theta)) * Math.cos(hourAngelRad);
364-
double zenith = Math.toDegrees(Math.acos(cosZenith > 1 ? 1 : cosZenith < -1 ? -1 : cosZenith));
365-
double azDenom = Math.cos(Math.toRadians(latitude)) * Math.sin(Math.toRadians(zenith));
366-
double refractionAdjustment = 0;
367-
double elevation = 90.0 - (zenith - refractionAdjustment);
368-
double azimuth = 0;
369-
double azRad = (Math.sin(Math.toRadians(latitude)) * Math.cos(Math.toRadians(zenith))
370-
- Math.sin(Math.toRadians(theta))) / azDenom;
371-
if(Math.abs(azDenom) > 0.001) {
372-
azimuth = 180 - Math.toDegrees(Math.acos(azRad > 1 ? 1 : azRad < -1? -1 : azRad)) * (hourAngelRad > 0 ? -1 : 1) ;
373-
} else {
374-
azimuth = latitude > 0 ? 180 : 0;
375-
}
376-
return isAzimuth ? azimuth % 360 : elevation;
345+
private double getSolarElevationAzimuth(ZonedDateTime zonedDateTime, GeoLocation geoLocation, boolean isAzimuth) {
346+
347+
double lat = Math.toRadians(geoLocation.getLatitude());
348+
double lon = geoLocation.getLongitude();
349+
350+
ZonedDateTime utc = zonedDateTime.withZoneSameInstant(ZoneOffset.UTC);
351+
352+
double fractionalDay =
353+
(utc.getHour()
354+
+ (utc.getMinute()
355+
+ (utc.getSecond() + utc.getNano() / 1_000_000_000.0) / 60.0) / 60.0) / 24.0;
356+
357+
double jd = getJulianDay(utc) + fractionalDay;
358+
double jc = getJulianCenturiesFromJulianDay(jd);
359+
360+
double decl = Math.toRadians(getSunDeclination(jc));
361+
double eot = getEquationOfTime(jc);
362+
363+
double trueSolarTime = ((fractionalDay + eot / 1440.0 + lon / 360.0) + 2) % 1;
364+
double hourAngle = trueSolarTime * 2 * Math.PI - Math.PI;
365+
366+
double cosZenith =
367+
Math.sin(lat) * Math.sin(decl)
368+
+ Math.cos(lat) * Math.cos(decl) * Math.cos(hourAngle);
369+
370+
double zenith = Math.acos(Math.max(-1, Math.min(1, cosZenith)));
371+
double elevation = 90 - Math.toDegrees(zenith);
372+
373+
double azimuth;
374+
double azDenom = Math.cos(lat) * Math.sin(zenith);
375+
376+
if (Math.abs(azDenom) > 0.001) {
377+
double az =
378+
(Math.sin(lat) * Math.cos(zenith) - Math.sin(decl)) / azDenom;
379+
380+
azimuth = 180 - Math.toDegrees(Math.acos(Math.max(-1, Math.min(1, az))))
381+
* (hourAngle > 0 ? -1 : 1);
382+
} else {
383+
azimuth = geoLocation.getLatitude() > 0 ? 180 : 0;
384+
}
385+
386+
return isAzimuth ? (azimuth + 360) % 360 : elevation;
377387
}
378388

379-
/**
380-
* Returns the hour of day adjusted for the timezone and DST. This is needed for the azimuth and elevation
381-
* calculations.
382-
* @param calendar the Calendar to extract the hour from. This must have the timezone set to the proper timezone.
383-
* @return the adjusted hour corrected for timezone and DST offset.
384-
*/
385-
private int adjustHourForTimeZone(Calendar calendar) {
386-
int offset = calendar.getTimeZone().getRawOffset();
387-
int dstOffset = calendar.getTimeZone().getDSTSavings();
388-
if(calendar.getTimeZone().inDaylightTime(calendar.getTime())) {
389-
offset = offset + dstOffset;
390-
}
391-
return offset;
392-
}
393-
394389
/**
395390
* Return the <a href="https://en.wikipedia.org/wiki/Universal_Coordinated_Time">Universal Coordinated Time</a> (UTC)
396391
* of <a href="https://en.wikipedia.org/wiki/Noon#Solar_noon">solar noon</a> for the given day at the given location
@@ -401,15 +396,15 @@ private int adjustHourForTimeZone(Calendar calendar) {
401396
* @see com.kosherjava.zmanim.util.AstronomicalCalculator#getUTCNoon(Calendar, GeoLocation)
402397
* @see #getSolarNoonMidnightUTC(double, double, SolarEvent)
403398
*
404-
* @param calendar
405-
* The Calendar representing the date to calculate solar noon for
399+
* @param zonedDateTime
400+
* The zonedDateTime representing the date to calculate solar noon for
406401
* @param geoLocation
407402
* The location information used for astronomical calculating sun times. This class uses only requires
408403
* the longitude for calculating noon since it is the same time anywhere along the longitude line.
409404
* @return the time in minutes from zero UTC
410405
*/
411-
public double getUTCNoon(Calendar calendar, GeoLocation geoLocation) {
412-
double noon = getSolarNoonMidnightUTC(getJulianDay(calendar), -geoLocation.getLongitude(), SolarEvent.NOON);
406+
public double getUTCNoon(ZonedDateTime zonedDateTime, GeoLocation geoLocation) {
407+
double noon = getSolarNoonMidnightUTC(getJulianDay(zonedDateTime), -geoLocation.getLongitude(), SolarEvent.NOON);
413408
noon = noon / 60;
414409
return noon > 0 ? noon % 24 : noon % 24 + 24; // ensure that the time is >= 0 and < 24
415410
}
@@ -425,15 +420,15 @@ public double getUTCNoon(Calendar calendar, GeoLocation geoLocation) {
425420
* @see com.kosherjava.zmanim.util.AstronomicalCalculator#getUTCNoon(Calendar, GeoLocation)
426421
* @see #getSolarNoonMidnightUTC(double, double, SolarEvent)
427422
*
428-
* @param calendar
429-
* The Calendar representing the date to calculate solar noon for
423+
* @param zonedDateTime
424+
* The <code>ZonedDateTime</code> representing the date to calculate solar noon for
430425
* @param geoLocation
431426
* The location information used for astronomical calculating sun times. This class uses only requires
432427
* the longitude for calculating noon since it is the same time anywhere along the longitude line.
433428
* @return the time in minutes from zero UTC
434429
*/
435-
public double getUTCMidnight(Calendar calendar, GeoLocation geoLocation) {
436-
double midnight = getSolarNoonMidnightUTC(getJulianDay(calendar), -geoLocation.getLongitude(), SolarEvent.MIDNIGHT);
430+
public double getUTCMidnight(ZonedDateTime zonedDateTime, GeoLocation geoLocation) {
431+
double midnight = getSolarNoonMidnightUTC(getJulianDay(zonedDateTime), -geoLocation.getLongitude(), SolarEvent.MIDNIGHT);
437432
midnight = midnight / 60;
438433
return midnight > 0 ? midnight % 24 : midnight % 24 + 24; // ensure that the time is >= 0 and < 24
439434
}
@@ -474,8 +469,8 @@ private static double getSolarNoonMidnightUTC(double julianDay, double longitude
474469
* of sunrise or sunset in minutes for the given day at the given location on earth.
475470
* @todo Possibly increase the number of passes for improved accuracy, especially in the Arctic areas.
476471
*
477-
* @param calendar
478-
* The calendar
472+
* @param zonedDateTime
473+
* The <code>ZonedDateTime</code>.
479474
* @param latitude
480475
* The latitude of observer in degrees
481476
* @param longitude
@@ -486,9 +481,9 @@ private static double getSolarNoonMidnightUTC(double julianDay, double longitude
486481
* If the calculation is for {@link SolarEvent#SUNRISE SUNRISE} or {@link SolarEvent#SUNSET SUNSET}
487482
* @return the time in minutes from zero Universal Coordinated Time (UTC)
488483
*/
489-
private static double getSunRiseSetUTC(Calendar calendar, double latitude, double longitude, double zenith,
484+
private static double getSunRiseSetUTC(ZonedDateTime zonedDateTime, double latitude, double longitude, double zenith,
490485
SolarEvent solarEvent) {
491-
double julianDay = getJulianDay(calendar);
486+
double julianDay = getJulianDay(zonedDateTime);
492487

493488
// Find the time of solar noon at the location, and use that declination.
494489
// This is better than start of the Julian day

0 commit comments

Comments
 (0)