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>
This commit is contained in:
co-authored by
Grok (agent loop)
Cursor
parent
2f8263b6ac
commit
0bd254cf18
+4
-8
@@ -89,13 +89,11 @@ def calculate_gulika(
|
||||
) -> dict[str, Any]:
|
||||
"""Return Gulika from local moment/location using Swiss sunrise and sunset."""
|
||||
ayanamsa_name = normalize_ayanamsa_name(ayanamsa)
|
||||
daynight = determine_daytime(moment, lat=lat, lon=lon, tz=tz)
|
||||
daynight = determine_daytime(moment, lat=lat, lon=lon, tz=tz, rising="hindu")
|
||||
is_day = bool(daynight["is_daytime"])
|
||||
period = "day" if is_day else "night"
|
||||
start_jd = daynight["sunrise_jd_ut"] if is_day else daynight["sunset_jd_ut"]
|
||||
end_jd = daynight["sunset_jd_ut"] if is_day else daynight["sunrise_jd_ut"] + 1.0
|
||||
if end_jd <= start_jd:
|
||||
end_jd += 1.0
|
||||
end_jd = daynight["sunset_jd_ut"] if is_day else daynight["next_sunrise_jd_ut"]
|
||||
start_year, start_month, start_day, _ = swe.revjul(start_jd + float(tz) / 24.0)
|
||||
period_weekday = datetime(int(start_year), int(start_month), int(start_day)).weekday()
|
||||
if method == "saturn_part_start":
|
||||
@@ -143,13 +141,11 @@ def _temporal_upagraha(
|
||||
ayanamsa: str = DEFAULT_AYANAMSA_NAME,
|
||||
) -> dict[str, Any]:
|
||||
ayanamsa_name = normalize_ayanamsa_name(ayanamsa)
|
||||
daynight = determine_daytime(moment, lat=lat, lon=lon, tz=tz)
|
||||
daynight = determine_daytime(moment, lat=lat, lon=lon, tz=tz, rising="hindu")
|
||||
is_day = bool(daynight["is_daytime"])
|
||||
period = "day" if is_day else "night"
|
||||
start_jd = daynight["sunrise_jd_ut"] if is_day else daynight["sunset_jd_ut"]
|
||||
end_jd = daynight["sunset_jd_ut"] if is_day else daynight["sunrise_jd_ut"] + 1.0
|
||||
if end_jd <= start_jd:
|
||||
end_jd += 1.0
|
||||
end_jd = daynight["sunset_jd_ut"] if is_day else daynight["next_sunrise_jd_ut"]
|
||||
start_year, start_month, start_day, _ = swe.revjul(start_jd + float(tz) / 24.0)
|
||||
weekday = datetime(int(start_year), int(start_month), int(start_day)).weekday()
|
||||
ruler_table = DAY_RULERS if is_day else NIGHT_RULERS
|
||||
|
||||
+62
-13
@@ -12,23 +12,71 @@ class SahamDayNightError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def determine_daytime(moment: datetime, *, lat: float, lon: float, tz: float) -> dict[str, Any]:
|
||||
def determine_daytime(
|
||||
moment: datetime,
|
||||
*,
|
||||
lat: float,
|
||||
lon: float,
|
||||
tz: float,
|
||||
rising: str = "apparent",
|
||||
) -> dict[str, Any]:
|
||||
"""Day/night window.
|
||||
|
||||
``rising="apparent"`` is the historical disk-edge window. Rectification and
|
||||
Panchanga rely on that default and must not pick up Hindu rising.
|
||||
``rising="hindu"`` is disk-center, no refraction, with the real next
|
||||
sunrise. Saham day/night, Gulika, and Shayanadi pass it explicitly.
|
||||
"""
|
||||
if rising not in ("apparent", "hindu"):
|
||||
raise SahamDayNightError("unsupported sunrise convention")
|
||||
if not -90 <= float(lat) <= 90 or not -180 <= float(lon) <= 180:
|
||||
raise SahamDayNightError("invalid WGS84 latitude/longitude")
|
||||
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)
|
||||
if rise_status != 0 or set_status != 0:
|
||||
raise SahamDayNightError("sunrise_or_sunset_unavailable_for_location_date")
|
||||
sunrise_jd, sunset_jd = rise[0], sunset[0]
|
||||
# Normalize the next daily events around the queried instant.
|
||||
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)
|
||||
if rising == "apparent":
|
||||
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)
|
||||
if rise_status != 0 or set_status != 0:
|
||||
raise SahamDayNightError("sunrise_or_sunset_unavailable_for_location_date")
|
||||
sunrise_jd, sunset_jd = rise[0], sunset[0]
|
||||
# Normalize the next daily events around the queried instant.
|
||||
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.",
|
||||
}
|
||||
|
||||
flags = swe.FLG_SWIEPH | swe.FLG_TRUEPOS
|
||||
|
||||
def event(after, kind):
|
||||
status, times = swe.rise_trans(after, swe.SUN, kind | swe.BIT_HINDU_RISING, geopos, flags=flags)
|
||||
if status != 0:
|
||||
raise SahamDayNightError("sunrise_or_sunset_unavailable_for_location_date")
|
||||
return times[0]
|
||||
|
||||
# Consecutive rise/set events. Adding one Julian day to a sunrise is not
|
||||
# the next sunrise, and it moves the night-segment endpoint.
|
||||
midnight_jd = swe.julday(local.year, local.month, local.day, -float(tz))
|
||||
sunrise_jd = event(midnight_jd, swe.CALC_RISE)
|
||||
if sunrise_jd > jd:
|
||||
midnight_jd -= 1.0
|
||||
sunrise_jd = event(midnight_jd, swe.CALC_RISE)
|
||||
next_sunrise_jd = event(midnight_jd + 1.0, swe.CALC_RISE)
|
||||
sunset_jd = event(midnight_jd, swe.CALC_SET)
|
||||
if not sunrise_jd <= jd < next_sunrise_jd or not sunrise_jd < sunset_jd < next_sunrise_jd:
|
||||
raise SahamDayNightError("solar_day_window_unavailable")
|
||||
is_day = sunrise_jd <= jd < sunset_jd
|
||||
return {
|
||||
"scope": "saham_daynight_swiss",
|
||||
"status": "computed",
|
||||
@@ -36,6 +84,7 @@ def determine_daytime(moment: datetime, *, lat: float, lon: float, tz: float) ->
|
||||
"julian_day_ut": jd,
|
||||
"sunrise_jd_ut": sunrise_jd,
|
||||
"sunset_jd_ut": sunset_jd,
|
||||
"method": "swisseph.rise_trans",
|
||||
"next_sunrise_jd_ut": next_sunrise_jd,
|
||||
"method": "swisseph.rise_trans_hindu_rising",
|
||||
"boundary": "Formula-specific +30 degree exceptions must be applied by the Saham rule layer, not inferred from house placement.",
|
||||
}
|
||||
|
||||
+1
-1
@@ -1614,7 +1614,7 @@ def calc_all_sahams(planet_lons: Dict[str, float],
|
||||
'boundary': 'No solar-house day/night proxy is permitted in production.',
|
||||
}
|
||||
from saham_daynight import determine_daytime
|
||||
daynight = determine_daytime(birth_dt, lat=float(lat), lon=float(lon), tz=float(tz))
|
||||
daynight = determine_daytime(birth_dt, lat=float(lat), lon=float(lon), tz=float(tz), rising="hindu")
|
||||
sun_lon = planet_lons.get('Sun', 0)
|
||||
moon_lon = planet_lons.get('Moon', 0)
|
||||
mars_lon = planet_lons.get('Mars', 0)
|
||||
|
||||
Reference in New Issue
Block a user