Goldener Henkel ist drin

This commit is contained in:
Eskimue
2026-04-03 16:17:29 +02:00
parent 3574fb99c7
commit 4f1c06f863
4 changed files with 251 additions and 2 deletions
+84
View File
@@ -698,3 +698,87 @@ Stack trace:
[Fri Apr 3 15:04:24 2026] 127.0.0.1:64920 Accepted
[Fri Apr 3 15:04:26 2026] 127.0.0.1:64920 [200]: GET /favicon.ico
[Fri Apr 3 15:04:26 2026] 127.0.0.1:64920 Closing
[Fri Apr 3 15:11:51 2026] 127.0.0.1:57308 Accepted
[Fri Apr 3 15:12:02 2026] 127.0.0.1:57308 [200]: GET /monatsvorhersage_old.php?year=2026&month=9
[Fri Apr 3 15:12:02 2026] 127.0.0.1:57308 Closing
[Fri Apr 3 15:12:32 2026] 127.0.0.1:59860 Accepted
[Fri Apr 3 15:12:47 2026] 127.0.0.1:59860 [200]: GET /monatsvorhersage_old.php?year=2026&month=2
[Fri Apr 3 15:12:47 2026] 127.0.0.1:59860 Closing
[Fri Apr 3 15:12:47 2026] 127.0.0.1:58680 Accepted
[Fri Apr 3 15:12:57 2026] 127.0.0.1:58680 [200]: GET /monatsvorhersage_old.php?year=2026&month=9
[Fri Apr 3 15:12:57 2026] 127.0.0.1:58680 Closing
[Fri Apr 3 15:12:57 2026] 127.0.0.1:52748 Accepted
[Fri Apr 3 15:12:57 2026] 127.0.0.1:51924 Accepted
[Fri Apr 3 15:12:59 2026] 127.0.0.1:52748 [200]: GET /favicon.ico
[Fri Apr 3 15:12:59 2026] 127.0.0.1:52748 Closing
[Fri Apr 3 15:12:59 2026] 127.0.0.1:64367 Accepted
[Fri Apr 3 15:12:59 2026] 127.0.0.1:51924 [200]: GET /css/style.css
[Fri Apr 3 15:12:59 2026] 127.0.0.1:64367 [200]: GET /js/astronomy.browser.min.js
[Fri Apr 3 15:12:59 2026] 127.0.0.1:51924 Closing
[Fri Apr 3 15:12:59 2026] 127.0.0.1:64367 Closing
[Fri Apr 3 15:12:59 2026] 127.0.0.1:64871 Accepted
[Fri Apr 3 15:12:59 2026] 127.0.0.1:51645 Accepted
[Fri Apr 3 15:12:59 2026] 127.0.0.1:64871 [200]: GET /images/star16x16.png
[Fri Apr 3 15:12:59 2026] 127.0.0.1:51645 [200]: GET /images/star16x16_rays.png
[Fri Apr 3 15:12:59 2026] 127.0.0.1:64871 Closing
[Fri Apr 3 15:12:59 2026] 127.0.0.1:51645 Closing
[Fri Apr 3 15:12:59 2026] 127.0.0.1:56330 Accepted
[Fri Apr 3 15:13:00 2026] 127.0.0.1:56330 [200]: GET /favicon.ico
[Fri Apr 3 15:13:00 2026] 127.0.0.1:56330 Closing
[Fri Apr 3 15:19:13 2026] 127.0.0.1:52242 Accepted
[Fri Apr 3 15:19:24 2026] 127.0.0.1:52242 [200]: GET /monatsvorhersage_old.php?year=2026&month=11
[Fri Apr 3 15:19:24 2026] 127.0.0.1:52242 Closing
[Fri Apr 3 15:19:24 2026] 127.0.0.1:52243 Accepted
[Fri Apr 3 15:19:37 2026] 127.0.0.1:52243 [200]: GET /monatsvorhersage_old.php?year=2026&month=10
[Fri Apr 3 15:19:37 2026] 127.0.0.1:52243 Closing
[Fri Apr 3 15:19:49 2026] 127.0.0.1:53174 Accepted
[Fri Apr 3 15:20:00 2026] 127.0.0.1:53174 [200]: GET /monatsvorhersage_old.php?year=2026&month=10
[Fri Apr 3 15:20:00 2026] 127.0.0.1:53174 Closing
[Fri Apr 3 15:20:00 2026] 127.0.0.1:53176 Accepted
[Fri Apr 3 15:20:11 2026] 127.0.0.1:53176 [200]: GET /monatsvorhersage_old.php?year=2026&month=11
[Fri Apr 3 15:20:11 2026] 127.0.0.1:53176 Closing
[Fri Apr 3 15:21:51 2026] 127.0.0.1:59375 Accepted
[Fri Apr 3 15:22:01 2026] 127.0.0.1:59375 [200]: GET /monatsvorhersage_old.php?year=2026&month=10
[Fri Apr 3 15:22:01 2026] 127.0.0.1:59375 Closing
[Fri Apr 3 15:22:01 2026] 127.0.0.1:52790 Accepted
[Fri Apr 3 15:22:01 2026] 127.0.0.1:52790 [200]: GET /css/style.css
[Fri Apr 3 15:22:01 2026] 127.0.0.1:52790 Closing
[Fri Apr 3 15:22:01 2026] 127.0.0.1:64786 Accepted
[Fri Apr 3 15:22:01 2026] 127.0.0.1:64786 [200]: GET /js/astronomy.browser.min.js
[Fri Apr 3 15:22:01 2026] 127.0.0.1:64786 Closing
[Fri Apr 3 15:22:01 2026] 127.0.0.1:53818 Accepted
[Fri Apr 3 15:22:01 2026] 127.0.0.1:60750 Accepted
[Fri Apr 3 15:22:01 2026] 127.0.0.1:53818 [200]: GET /images/star16x16.png
[Fri Apr 3 15:22:01 2026] 127.0.0.1:60750 [200]: GET /images/star16x16_rays.png
[Fri Apr 3 15:22:01 2026] 127.0.0.1:53818 Closing
[Fri Apr 3 15:22:01 2026] 127.0.0.1:60750 Closing
[Fri Apr 3 15:22:01 2026] 127.0.0.1:60254 Accepted
[Fri Apr 3 15:22:03 2026] 127.0.0.1:60254 [200]: GET /favicon.ico
[Fri Apr 3 15:22:03 2026] 127.0.0.1:60254 Closing
[Fri Apr 3 15:23:32 2026] 127.0.0.1:54044 Accepted
[Fri Apr 3 15:23:42 2026] 127.0.0.1:54044 [200]: GET /monatsvorhersage_old.php?year=2026&month=10
[Fri Apr 3 15:23:42 2026] 127.0.0.1:54044 Closing
[Fri Apr 3 15:24:14 2026] 127.0.0.1:54048 Accepted
[Fri Apr 3 15:24:22 2026] 127.0.0.1:54048 [200]: GET /monatsvorhersage_old.php?year=2026&month=10
[Fri Apr 3 15:24:22 2026] 127.0.0.1:54048 Closing
[Fri Apr 3 15:24:51 2026] 127.0.0.1:65262 Accepted
[Fri Apr 3 15:24:59 2026] 127.0.0.1:65262 [200]: GET /monatsvorhersage_old.php?year=2026&month=10
[Fri Apr 3 15:24:59 2026] 127.0.0.1:65262 Closing
[Fri Apr 3 15:39:18 2026] 127.0.0.1:62298 Accepted
[Fri Apr 3 15:39:27 2026] 127.0.0.1:62298 [200]: GET /monatsvorhersage_old.php?year=2026&month=10
[Fri Apr 3 15:39:27 2026] 127.0.0.1:62298 Closing
[Fri Apr 3 15:39:27 2026] 127.0.0.1:50830 Accepted
[Fri Apr 3 15:39:27 2026] 127.0.0.1:50830 [200]: GET /css/style.css
[Fri Apr 3 15:39:27 2026] 127.0.0.1:50830 Closing
[Fri Apr 3 15:39:27 2026] 127.0.0.1:55999 Accepted
[Fri Apr 3 15:39:27 2026] 127.0.0.1:55999 [200]: GET /js/astronomy.browser.min.js
[Fri Apr 3 15:39:27 2026] 127.0.0.1:55999 Closing
[Fri Apr 3 15:39:27 2026] 127.0.0.1:56815 Accepted
[Fri Apr 3 15:39:27 2026] 127.0.0.1:59542 Accepted
[Fri Apr 3 15:39:27 2026] 127.0.0.1:56815 [200]: GET /images/star16x16.png
[Fri Apr 3 15:39:27 2026] 127.0.0.1:59542 [200]: GET /images/star16x16_rays.png
[Fri Apr 3 15:39:27 2026] 127.0.0.1:56815 Closing
[Fri Apr 3 15:39:27 2026] 127.0.0.1:59542 Closing
[Fri Apr 3 15:39:27 2026] 127.0.0.1:55947 Accepted
[Fri Apr 3 15:39:29 2026] 127.0.0.1:55947 [200]: GET /favicon.ico
[Fri Apr 3 15:39:29 2026] 127.0.0.1:55947 Closing
+36
View File
@@ -57,6 +57,7 @@ $messierData = [];
$planetData = [];
$moonData = null;
$moonPhaseEvents = [];
$goldenHandleEvents = [];
$moonPlanetApproaches = [];
$planetConjunctions = [];
$eclipseEvents = [];
@@ -337,6 +338,23 @@ if (is_array($moonPhaseDecoded) && ($moonPhaseDecoded['ok'] ?? false)) {
$moonPhaseEvents = is_array($moonPhaseDecoded['phases'] ?? null) ? $moonPhaseDecoded['phases'] : [];
}
$goldenHandleOutput = [];
$goldenHandleExitCode = 0;
$goldenHandleArgs = [
$pythonScriptPath,
'golden_handle_for_month',
(string) $selectedYear,
(string) $selectedMonth,
$chartTimezone,
];
$goldenHandleCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $goldenHandleArgs)) . ' 2>&1';
exec($goldenHandleCommand, $goldenHandleOutput, $goldenHandleExitCode);
$goldenHandleRaw = trim(implode("\n", $goldenHandleOutput));
$goldenHandleDecoded = json_decode($goldenHandleRaw, true);
if (is_array($goldenHandleDecoded) && ($goldenHandleDecoded['ok'] ?? false)) {
$goldenHandleEvents = is_array($goldenHandleDecoded['events'] ?? null) ? $goldenHandleDecoded['events'] : [];
}
$seasonOutput = [];
$seasonExitCode = 0;
$seasonArgs = [
@@ -597,6 +615,24 @@ foreach ($moonPhaseEvents as $phaseEvent) {
];
}
foreach ($goldenHandleEvents as $goldenHandleEvent) {
if (!is_array($goldenHandleEvent)) {
continue;
}
$peakDate = (string) ($goldenHandleEvent['peak_local_date'] ?? '');
$peakTime = (string) ($goldenHandleEvent['peak_local_time'] ?? '');
$goldenHandleLabel = 'Goldener Henkel am Mond';
$monthEventList[] = [
'event' => $goldenHandleLabel,
'date' => $peakDate !== '' ? $peakDate : (string) ($goldenHandleEvent['local_date'] ?? ''),
'time' => $peakTime !== '' ? $peakTime : (string) ($goldenHandleEvent['local_time'] ?? ''),
'local_iso' => (string) ($goldenHandleEvent['peak_local_iso'] ?? $goldenHandleEvent['local_iso'] ?? ''),
'type' => 'golden_handle',
];
}
foreach ($seasonChanges as $seasonChange) {
if (!is_array($seasonChange)) {
continue;
Binary file not shown.
+131 -2
View File
@@ -466,6 +466,130 @@ def action_moon_phases_for_month(args: list[str]) -> dict:
}
def action_golden_handle_for_month(args: list[str]) -> dict:
if len(args) != 3:
fail(
"Aktion golden_handle_for_month erwartet 3 Argumente: year month timezone",
extra={"argv": args},
)
try:
year = int(args[0])
month = int(args[1])
except ValueError as exc:
fail("Jahr oder Monat sind ungueltig.", extra={"details": str(exc), "argv": args})
if month < 1 or month > 12:
fail("Monat muss zwischen 1 und 12 liegen.", extra={"month": month})
timezone_name = args[2]
try:
tz = ZoneInfo(timezone_name)
except Exception as exc:
fail("Zeitzone ist ungueltig oder auf dem Server nicht verfuegbar.", extra={"details": str(exc)})
local_start = datetime(year, month, 1, 0, 0, 0, tzinfo=tz)
if month == 12:
local_end = datetime(year + 1, 1, 1, 0, 0, 0, tzinfo=tz)
else:
local_end = datetime(year, month + 1, 1, 0, 0, 0, tzinfo=tz)
# Approximate the Golden Handle geometrically by the effective selenographic
# colongitude over Montes Jura/Sinus Iridum (roughly 44 N, 36 W),
# corrected by libration. Empirically, the best-fit target shifts with
# libration in latitude by several tenths of a degree, i.e. by hours.
def target_colongitude(elat_deg: float) -> float:
return 33.385316705346064 + 0.11176238230626076 * elat_deg + 0.0734267792065295 * elat_deg * elat_deg
def golden_handle_metric(dt_utc: datetime) -> float:
time_value = dt_to_time(dt_utc)
phase_deg = normalize_degrees(astronomy.MoonPhase(time_value))
libration = astronomy.Libration(time_value)
effective_colongitude = normalize_degrees(phase_deg - 90.0 - float(libration.elon))
return normalize_signed_degrees(effective_colongitude - target_colongitude(float(libration.elat)))
def refine_peak_time(left_utc: datetime, right_utc: datetime) -> datetime:
left = left_utc
right = right_utc
f_left = golden_handle_metric(left)
f_right = golden_handle_metric(right)
for _ in range(40):
mid = left + (right - left) / 2
f_mid = golden_handle_metric(mid)
if abs(f_mid) < 1e-5:
return mid
if f_left == 0:
return left
if f_right == 0:
return right
if f_left * f_mid <= 0:
right = mid
f_right = f_mid
else:
left = mid
f_left = f_mid
return left + (right - left) / 2
search_start = dt_to_time(local_start.astimezone(timezone.utc) - timedelta(days=20))
events = []
probe = search_start
for _ in range(3):
first_quarter = astronomy.SearchMoonPhase(90.0, probe, 40.0)
if first_quarter is None:
break
full_moon = astronomy.SearchMoonPhase(180.0, first_quarter, 12.0)
if full_moon is None:
break
interval_start = time_to_datetime(first_quarter)
interval_end = time_to_datetime(full_moon)
step = timedelta(hours=1)
previous_time = interval_start
previous_value = golden_handle_metric(previous_time)
current = interval_start + step
peak_utc = None
while current <= interval_end:
current_value = golden_handle_metric(current)
if previous_value == 0 or current_value == 0 or previous_value * current_value < 0:
peak_utc = refine_peak_time(previous_time, current)
break
previous_time = current
previous_value = current_value
current += step
if peak_utc is not None:
peak_local = peak_utc.astimezone(tz)
if peak_local.year == year and peak_local.month == month:
events.append({
"kind": "golden_handle",
"label": "Goldener Henkel am Mond",
"utc_iso": peak_utc.isoformat().replace("+00:00", "Z"),
"local_iso": peak_local.isoformat(),
"local_date": peak_local.strftime("%d.%m.%Y"),
"local_time": peak_local.strftime("%H:%M"),
"peak_local_iso": peak_local.isoformat(),
"peak_local_date": peak_local.strftime("%d.%m.%Y"),
"peak_local_time": peak_local.strftime("%H:%M"),
})
probe = dt_to_time(interval_end + timedelta(days=10))
events.sort(key=lambda item: item["local_iso"])
return {
"ok": True,
"action": "golden_handle_for_month",
"selected": {
"year": year,
"month": month,
"timezone": timezone_name,
},
"events": events,
}
def action_season_changes_for_month(args: list[str]) -> dict:
if len(args) != 3:
fail(
@@ -2578,7 +2702,7 @@ def action_satellite_passes(args: list[str]) -> dict:
def main() -> None:
if len(sys.argv) < 2:
fail("Es wurde keine Aktion uebergeben.", extra={"available_actions": ["sun_moon_rise_set", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context", "moon_phases_for_month", "season_changes_for_month", "moon_planet_approaches", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "moon_star_occultations_for_month"]})
fail("Es wurde keine Aktion uebergeben.", extra={"available_actions": ["sun_moon_rise_set", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context", "moon_phases_for_month", "golden_handle_for_month", "season_changes_for_month", "moon_planet_approaches", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "moon_star_occultations_for_month"]})
action = sys.argv[1]
args = sys.argv[2:]
@@ -2623,6 +2747,11 @@ def main() -> None:
print(json.dumps(result, ensure_ascii=False))
return
if action == "golden_handle_for_month":
result = action_golden_handle_for_month(args)
print(json.dumps(result, ensure_ascii=False))
return
if action == "moon_planet_approaches":
result = action_moon_planet_approaches(args)
print(json.dumps(result, ensure_ascii=False))
@@ -2673,7 +2802,7 @@ def main() -> None:
print(json.dumps(result, ensure_ascii=False))
return
fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context", "moon_phases_for_month", "season_changes_for_month", "moon_planet_approaches", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "moon_star_occultations_for_month"]})
fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context", "moon_phases_for_month", "golden_handle_for_month", "season_changes_for_month", "moon_planet_approaches", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "moon_star_occultations_for_month"]})
if __name__ == "__main__":