Files
Jyotisha/tests/test_saham_daynight.py
T
jesse-uxGrok (agent loop)Cursor
0bd254cf18 fix(gulika): end the night segment at the real next Hindu sunrise
Upstream 9f9f16b3 and 872eaecf. Saham, Gulika, and Shayanadi opt into Hindu rising. The default window used by rectification and Panchanga stays the apparent-disk one. BUG-1388.

Co-Authored-By: Grok (agent loop)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 16:49:12 +08:00

77 lines
3.4 KiB
Python

from datetime import datetime
import pytest
import swisseph as swe
from scripts.saham_daynight import determine_daytime
def test_swiss_daynight_does_not_use_solar_house_proxy():
noon = determine_daytime(datetime(2026, 7, 12, 12, 0), lat=39.9042, lon=116.4074, tz=8)
midnight = determine_daytime(datetime(2026, 7, 12, 0, 0), lat=39.9042, lon=116.4074, tz=8)
assert noon["status"] == "computed"
assert noon["is_daytime"] is True
assert midnight["is_daytime"] is False
assert noon["method"] == "swisseph.rise_trans"
def _legacy_apparent_daytime(moment, *, lat, lon, tz):
"""The pre-T10 window. Rectification and Panchanga still call this default."""
local = moment.replace(tzinfo=None)
jd = swe.julday(local.year, local.month, local.day, local.hour + local.minute / 60 + local.second / 3600 - float(tz))
geopos = (float(lon), float(lat), 0.0)
rise_status, rise = swe.rise_trans(jd - 1.0, swe.SUN, swe.CALC_RISE, geopos)
set_status, sunset = swe.rise_trans(jd - 1.0, swe.SUN, swe.CALC_SET, geopos)
assert rise_status == 0 and set_status == 0
sunrise_jd, sunset_jd = rise[0], sunset[0]
while sunrise_jd > jd:
sunrise_jd -= 1.0
while sunset_jd > jd:
sunset_jd -= 1.0
is_day = sunrise_jd <= jd < sunset_jd if sunrise_jd < sunset_jd else not (sunset_jd <= jd < sunrise_jd)
return {
"scope": "saham_daynight_swiss",
"status": "computed",
"is_daytime": is_day,
"julian_day_ut": jd,
"sunrise_jd_ut": sunrise_jd,
"sunset_jd_ut": sunset_jd,
"method": "swisseph.rise_trans",
"boundary": "Formula-specific +30 degree exceptions must be applied by the Saham rule layer, not inferred from house placement.",
}
def test_default_daytime_matches_rectification_apparent_sunrise():
# active_rectification_event_engine._sunrise_local_naive calls noon with no rising argument.
for date in ("2000-01-01", "2026-06-21", "2026-12-21"):
noon = datetime.fromisoformat(f"{date}T12:00:00")
current = determine_daytime(noon, lat=39.9042, lon=116.4074, tz=8)
previous = _legacy_apparent_daytime(noon, lat=39.9042, lon=116.4074, tz=8)
assert current == previous
assert "next_sunrise_jd_ut" not in current
@pytest.mark.parametrize("hour", [0, 4, 12, 23])
@pytest.mark.parametrize("month,day", [(3, 20), (6, 21), (9, 22), (12, 21)])
def test_hindu_consecutive_solar_events_are_recomputed_not_shifted(month, day, hour):
moment = datetime(2026, month, day, hour)
result = determine_daytime(moment, lat=39.9, lon=116.4, tz=8, rising="hindu")
rise = result["sunrise_jd_ut"]
flags = swe.FLG_SWIEPH | swe.FLG_TRUEPOS
year, month_num, day_num, _ = swe.revjul(rise + 8 / 24)
midnight = swe.julday(year, month_num, day_num, -8)
def event(start, kind):
status, times = swe.rise_trans(
start, swe.SUN, kind | swe.BIT_HINDU_RISING, (116.4, 39.9, 0), flags=flags,
)
assert status == 0
return times[0]
assert rise == pytest.approx(event(midnight, swe.CALC_RISE), abs=1e-8, rel=0)
assert result["sunset_jd_ut"] == pytest.approx(event(midnight, swe.CALC_SET), abs=1e-8, rel=0)
assert result["next_sunrise_jd_ut"] == pytest.approx(event(midnight + 1, swe.CALC_RISE), abs=1e-8, rel=0)
assert rise <= result["julian_day_ut"] < result["next_sunrise_jd_ut"]
assert result["method"] == "swisseph.rise_trans_hindu_rising"