flipphoneguy/tools
← Visible Zmanim
← Back to the calculator

How it works

This page explains every calculation in the package, from the sun's position to the terrain horizon. No astronomy background is assumed.

1. Where the sun is

The sun's position comes from Skyfield using NASA JPL's DE440s ephemeris, the same data used for spacecraft navigation. For a place and a moment it gives the sun's altitude (degrees above a flat horizon, negative below it) and azimuth (compass direction, 0° north, 90° east). These are the true geometric positions; refraction is added separately (section 3).

Skyfield applies light travel time, aberration, nutation and the observer's exact position on the Earth, which makes each position slow to compute. Since the sun moves very smoothly, sun.Track asks Skyfield for exact positions once an hour and interpolates in between with a cubic spline. The interpolation error is below 0.00001°, far smaller than anything else here, and it makes positions about 200 times faster.

Chatzos is the moment the sun crosses the local meridian (its hour angle is zero). This is the "astronomical chatzos" KosherJava uses by default, not the midpoint between sunrise and sunset.

2. What sunrise means

Sunrise is the moment the top edge of the sun first becomes visible; sunset is the moment it disappears. Two things make this different from "the sun's center at altitude 0":

So at sea-level sunrise the sun's center is really about 0.83° below the horizon. This is KosherJava's standard definition (34′ refraction plus the sun's radius), and the sea_level version reproduces it.

3. The threshold: one formula for all three versions

The package describes every horizon with one number per direction, the threshold: the true altitude the sun's top edge must reach for its light to clear the horizon and reach the observer. Sunrise is when the top edge's true altitude rises past the threshold.

For a flat sea-level horizon, the threshold is minus the horizontal refraction, -34′. That is the KosherJava definition.

For anything else, the light ray is followed backwards from the observer:

  1. The ray leaves the observer at the apparent angle e of an obstacle (a mountain top, or the sea horizon).
  2. Near the ground it bends slightly downward. Its curvature is k / R, where R is the Earth's radius and k is the terrestrial refraction coefficient (0.13, the usual surveying value). By the time it reaches the obstacle, at a central angle γ (distance divided by the Earth's radius), its angle above the obstacle's own horizontal is e_p = e + γ(1 − k).
  3. From the obstacle it climbs out of the atmosphere and is bent by ordinary astronomical refraction R(e_p). That depends on the angle (less refraction at higher angles) and on the air density at the obstacle's height (thinner air bends less).
  4. Turning back to the observer's frame gives:
threshold = e_p − R(e_p) − γ

If e_p is negative, the ray is still going down when it passes the obstacle, so it continues to the point where it is level (over lower ground or the sea) before rising. The formula is applied at that point.

This one formula covers all three versions:

Refraction uses Bennett's formula, scaled so that 0° at sea level gives exactly the configured horizontal refraction (34′), and multiplied by the standard-atmosphere air density at the height where the ray leaves the terrain.

4. The terrain horizon

horizon.compute_profile builds the threshold for every direction around the observer:

The profile is saved per point and settings, so the same address is instant the next time.

5. When the sun clears the horizon

For each day, the solver steps through the half day before chatzos (for sunrise) and after it (for sunset), 20 seconds at a time. At each step it checks whether any part of the sun's disk is above the threshold, testing every ray that crosses the disk. It then narrows down the crossing to about 0.01 s.

Because the threshold is looked up in the direction the sun actually is at each moment, the sun's sideways movement along the horizon is handled. That movement matters a lot: at 41° north the sun moves about 4° sideways while it climbs 5°.

6. Terrain data

All data is bare earth (a terrain model, not a surface model), so trees and buildings are not included.

Distance US and border areas England Everywhere else
up to 3 km USGS 3DEP 1 m lidar Environment Agency 1 m lidar GEDTM30
3 to 50 km USGS 3DEP 1/3 arc-second (~10 m) GEDTM30 GEDTM30
beyond 50 km GEDTM30 GEDTM30 GEDTM30

Where a dataset has no data (open sea, across a border), the next one fills in. Where none has data, the height is 0 (sea level).

The files are read straight from their public cloud copies (the English lidar from the Environment Agency's coverage service), one block at a time, at the coarsest zoom level that is still fine enough for the distance (about 0.2% of the distance). Far terrain therefore costs little. Blocks are stored as compressed whole centimeters relative to each block's lowest point, which is accurate to 5 mm and about 4.5 times smaller than raw. Stored blocks are kept in two places: pinned (regions downloaded ahead of time, never deleted) and cache (everything else, oldest removed when the cache passes its size limit).

Accuracy of the data, roughly: lidar to about 10 cm, the 1/3 arc-second data to about 1 to 2 m, GEDTM30 to about 4 m. A height error matters most up close: 3 m of error at 2 km shifts sunrise by about 25 seconds at 41° north, and the same error at 20 km by about 3 seconds.

7. Zmanim

From each version's sunrise and sunset, the GRA hour is (sunset − sunrise) / 12. The MGA hour uses alos and tzais 72 minutes before sunrise and after sunset instead. Sof zman shma, tefila, mincha gedola, mincha ketana and plag are 3, 4, 6.5, 9.5 and 10.75 hours into the day, as in KosherJava's getSofZmanShmaGRA() and related methods. Degree-based times (alos 16.1°, tzais 8.5°, tzais 16.1°) use the sun's true geometric center with no refraction and no radius, as KosherJava does, and they don't depend on the horizon.

8. Limits

The full source code and API documentation are on GitHub.