Open tooling, agent-run engineering, and the products built on both.

sun-track

One day of sunlight, as the library computes it

This figure is drawn in your browser from a JSON file in this repository, which is sun-track's own output. Without JavaScript the numbers are still readable there.

Place and date

Solar elevation across one UTC day.
  • 00:00
  • 06:00
  • 12:00
  • 18:00
  • 24:00
  • Apparent solar elevation, −60° to +90°
  • Golden band, −4° to +6°
  • Horizon, 0°
Every stretch of this UTC day that is_golden_elevation() returns true for, found by testing the elevation once a minute.
Golden window (UTC)Length

Real library output, not sample data — generated by sun-track: the curve is solar_elevation_deg() sampled every five minutes, the windows are is_golden_elevation() sampled every minute. Check any point with python -m sun_track sun 55.0 0.0 2026-01-15T08:00:00Z. The coordinates and dates were picked for this page; the numbers were not.

Why a clock is the wrong test

Software that tags a photo as “golden hour” usually tests the wall-clock hour. That is wrong the moment you leave the equator or the equinox, and the figure above is the argument. At 55° north on the Greenwich meridian the morning band runs 07:49–09:22 UTC in mid-January and 02:46–04:20 UTC at the June solstice: same place, same band, five hours apart. A fixed 07:00–08:00 rule overlaps the January one by about eleven minutes and misses the June one entirely.

Move east rather than forward in time and the band moves too, at four minutes per degree of longitude — 15° east of Greenwich, that solstice band lands at 01:46–03:20, exactly an hour earlier. Move to the equator and it stops being an hour and a half wide at all; it is closer to three quarters of an hour, because the sun crosses the same elevations far more steeply. Both are in the picker rather than in a claim you have to take on trust.

What sets the quality of the light is how high the sun is, and therefore how much atmosphere it is shining through. That is computable from a position and an instant, and then thresholdable. solar_elevation_deg() is that number; is_golden_elevation() is that threshold, and the band — −4° to +6° of apparent elevation, both edges included — is a photographic judgement that lives in one place in the source rather than at every call site.

What the figure is

Everything plotted above came out of the library: the curve is solar_elevation_deg() evaluated every five minutes across one UTC day, and the table is every stretch of that day is_golden_elevation() returns true for, tested once a minute. Nothing is a lookup table or a fitted approximation of the library’s behaviour — the values are its output, checked into this site as a JSON file so the page can stay static. The four places and dates were chosen to isolate one variable at a time; the numbers were not chosen at all, and any of them reproduces in one line:

1$ python -m sun_track sun 55.0 0.0 2026-01-15T08:00:00Z
2-2.624 golden

The other half

The second module has nothing to do with the sun and is useful alone. A camera that writes a timestamp but no coordinates is not a camera without a position, as long as something in the same pocket logged a GPX track. TrackIndex.position_at() is that join: it reads every *.gpx file in a directory, merges them into one time-sorted index, and interpolates linearly between the two track points bracketing the instant you ask about. Three ways that join goes subtly wrong are made impossible rather than documented. It will not extrapolate past the end of a track. It will not silently match a capture to a point hours away — there is a maximum gap, generous by default because “somewhere along this road” beats “unknown” for placing a photo on a map, and tightenable per call. And it will not compare a naive local timestamp against UTC track times: a datetime with no timezone raises rather than guessing, because guessing the offset is the one error that dwarfs every other term in the calculation. The two compose — position from the track, elevation at that position — but neither imports the other.

Limits, stated plainly

The declination and equation-of-time terms are Fourier fits in day-of-year, not a full ephemeris. Near the equinoxes that costs a few tenths of a degree of apparent elevation; near the solstices, a few hundredths. Call it half a degree worst case — twenty times tighter than the ten-degree band the library gates on, and nowhere near good enough for pointing a telescope. If you need arc-seconds, use an ephemeris library.

Standard-library Python, no runtime dependencies, no network call in any code path. pip install sun-track. Source: github.com/Back-Road-Creative/sun-track, published on PyPI.

← Back to Home