Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/main/java/com/kosherjava/zmanim/util/SPACalculator.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ public class SPACalculator extends AstronomicalCalculator {
/** Julian days per century. */
private static final double JULIAN_DAYS_PER_CENTURY = 36525.0;

/**
* The largest difference, in degrees, between the Sun's zenith at the solved time and the requested zenith that is still
* accepted as a sunrise or sunset. When the secant iteration in {@link #solveRiseSet} ends further off than this, it did
* not converge, and there is no event on that date. Converged solutions end below 1e-6° (floating-point noise in the
* Julian day), and failed ones almost always above 1e-3°. 1e-4° leaves a wide margin above the noise and is still
* below the SPA's stated accuracy of ±0.0003°.
*/
private static final double MAX_ZENITH_RESIDUAL = 1e-4;

/** Whether to apply the ΔT (TT−UT) correction. Defaults to {@code true}. */
private boolean applyDeltaT = true;

Expand Down Expand Up @@ -331,6 +340,11 @@ private double solveRiseSet(LocalDate localDate, GeoLocation geoLocation, double
t1 = t2;
f1 = topocentricTrueZenith(jdDay + t1 / 1440.0, lat, geoLocation.getLongitude(), elevation) - adjustedZenith;
}
// Near the poles the closed-form guess can report an event the topocentric Sun never reaches. The iteration then
// wanders off without converging, and the last guess is an arbitrary time, not a solution.
if (!(Math.abs(f1) <= MAX_ZENITH_RESIDUAL)) {
return Double.NaN;
}
return t1;
}

Expand Down
13 changes: 13 additions & 0 deletions src/test/java/com/kosherjava/zmanim/util/SPACalculatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@ public void getUTCSunset() {
}
}

/**
* Near the South Pole in March the closed-form first guess says the Sun reaches 10.2&deg; below the horizon, but the
* topocentric Sun only gets to about 10.04&deg;. The secant iteration never converges and used to return an arbitrary
* time. There is no event, so the result must be NaN.
*/
@Test
public void unconvergedRiseSetIsNoEvent() {
GeoLocation location = new GeoLocation("Antarctica", -77.26876171057897, -92.86928302120835, 3965.3333110036533,
java.time.ZoneId.of("Etc/GMT+6"));
double actual = calculator().getUTCSunrise(date("2127-03-14"), location, 100.2, false);
assertTrue("Sun never reaches 100.2° zenith; expected NaN but got " + actual, Double.isNaN(actual));
}

@Test
public void getUTCNoon() {
Object[][] cases = {
Expand Down
Loading