Skip to content

Apparent solar time uses an unsourced equation-of-time approximation that is off by up to 81 seconds #58

Description

@seonghobae

Gap

_equation_of_time_minutes in src/four_pillars/calendar.py is a three-term sine approximation with no cited source:

day_number = moment.timetuple().tm_yday
b = 2.0 * math.pi * (day_number - 81) / 364.0
return 9.87 * math.sin(2 * b) - 7.53 * math.cos(b) - 1.5 * math.sin(b)

It is the only thing standing between a customer's civil clock and apparent solar time when time_basis is apparent_solar. Meanwhile solar.py computes apparent solar longitude from a bounded VSOP87 Earth series in Terrestrial Time with FK5, nutation, and aberration corrections. The same product therefore holds a rigorous engine and a hand-rolled approximation for two halves of the same problem.

Measured error

Evaluated against a standard NOAA-style equation of time across 2026, sampling the 1st, 11th, and 21st of every month.

date approximation reference difference
02-11 −14.57 min −14.27 min −0.30 min
08-11 −4.84 min −5.29 min +0.45 min
09-11 +4.19 min +3.12 min +1.07 min
10-11 +14.12 min +13.09 min +1.03 min
12-11 +5.66 min +7.01 min −1.35 min

Worst observed difference: 1.35 minutes, or 81 seconds, on 2026-12-11. The error is systematic by season, not random noise, and it is largest in the autumn and winter months.

Customer-visible consequence, stated honestly

Narrow, and I want to be precise rather than alarming.

  • time_basis defaults to civil, and apparent_solar additionally requires a longitude, so this affects only charts a caller deliberately requests in apparent solar time.
  • Hour-pillar boundaries are two hours apart, so 81 seconds changes a 시주 only for a birth within 81 seconds of one.
  • Solar-term boundaries already raise a warning within six hours, so an affected birth is one the report is already telling the reader to double check.

What is not narrow is the contract. The project's stated standard is that deterministic calculation evidence is authoritative and that standards decisions carry APA 7 references with clear claim limits. An unsourced approximation with an unmeasured error bound in the normalization path does not meet that standard, whatever its practical size.

What a repair requires

The equation of time follows from quantities solar.py already computes internally, using the standard formulation E = L₀ − 0.0057183° − α + Δψ·cos ε, where L₀ is the geometric mean longitude of the Sun, α the apparent right ascension, Δψ the nutation in longitude, and ε the true obliquity. Today solar.py exports only apparent_solar_longitude; L₀, α, and ε are not available, and _nutation_longitude is private.

So this is not a one-line substitution. It adds obliquity and right ascension to the solar module and needs validation against published values rather than against itself.

Migration, which is the part that needs deciding first

normalized_birth is part of the fingerprint payload. Changing the equation of time changes the normalized moment for every apparent_solar chart, and therefore its fingerprint.

The project already has the precedent for this: when apparent solar longitude moved to the VSOP87 series, deterministic calculation evidence advanced to calendar-1.1.0. The same treatment applies here, a bump to calendar-1.2.0, with the changelog stating that apparent-solar charts computed under the previous version will not reproduce their prior fingerprint.

Proposal

  1. Export obliquity and apparent right ascension from solar.py, validated against published equation-of-time values rather than against the existing approximation.
  2. Replace _equation_of_time_minutes with the derived value and record the reference.
  3. Bump CALCULATION_VERSION and state the fingerprint consequence for apparent_solar charts.
  4. Keep a regression test that pins the result against independent reference values, so the accuracy bound is enforced rather than assumed.

Filed with the measurement rather than repaired in place, because getting celestial mechanics subtly wrong in the normalization path would be worse than the 81 seconds it replaces, and because the fingerprint bump is a decision the project should make deliberately.

Internal decision target: 2026-09-22. Next review: 2026-09-19.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions