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
- Export obliquity and apparent right ascension from
solar.py, validated against published equation-of-time values rather than against the existing approximation.
- Replace
_equation_of_time_minutes with the derived value and record the reference.
- Bump
CALCULATION_VERSION and state the fingerprint consequence for apparent_solar charts.
- 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.
Gap
_equation_of_time_minutesinsrc/four_pillars/calendar.pyis a three-term sine approximation with no cited source:It is the only thing standing between a customer's civil clock and apparent solar time when
time_basisisapparent_solar. Meanwhilesolar.pycomputes 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.
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_basisdefaults tocivil, andapparent_solaradditionally requires a longitude, so this affects only charts a caller deliberately requests in apparent solar time.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.pyalready computes internally, using the standard formulationE = L₀ − 0.0057183° − α + Δψ·cos ε, whereL₀is the geometric mean longitude of the Sun,αthe apparent right ascension,Δψthe nutation in longitude, andεthe true obliquity. Todaysolar.pyexports onlyapparent_solar_longitude;L₀,α, andεare not available, and_nutation_longitudeis 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_birthis part of the fingerprint payload. Changing the equation of time changes the normalized moment for everyapparent_solarchart, 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 tocalendar-1.2.0, with the changelog stating that apparent-solar charts computed under the previous version will not reproduce their prior fingerprint.Proposal
solar.py, validated against published equation-of-time values rather than against the existing approximation._equation_of_time_minuteswith the derived value and record the reference.CALCULATION_VERSIONand state the fingerprint consequence forapparent_solarcharts.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.