diff --git a/.codex-temp/php-server.err.log b/.codex-temp/php-server.err.log index 7494f4e..cc1a430 100644 --- a/.codex-temp/php-server.err.log +++ b/.codex-temp/php-server.err.log @@ -242,3 +242,42 @@ Stack trace: [Fri Apr 3 13:34:19 2026] 127.0.0.1:63048 Accepted [Fri Apr 3 13:34:20 2026] 127.0.0.1:63048 [200]: GET /favicon.ico [Fri Apr 3 13:34:20 2026] 127.0.0.1:63048 Closing +[Fri Apr 3 13:37:55 2026] 127.0.0.1:52978 Accepted +[Fri Apr 3 13:38:05 2026] 127.0.0.1:52978 [200]: GET /monatsvorhersage_old.php?year=2026&month=8 +[Fri Apr 3 13:38:05 2026] 127.0.0.1:52978 Closing +[Fri Apr 3 13:39:22 2026] 127.0.0.1:58743 Accepted +[Fri Apr 3 13:39:36 2026] 127.0.0.1:58743 [200]: GET /monatsvorhersage_old.php?year=2026&month=3 +[Fri Apr 3 13:39:36 2026] 127.0.0.1:58743 Closing +[Fri Apr 3 13:39:36 2026] 127.0.0.1:54463 Accepted +[Fri Apr 3 13:39:36 2026] 127.0.0.1:54463 [200]: GET /css/style.css +[Fri Apr 3 13:39:36 2026] 127.0.0.1:54463 Closing +[Fri Apr 3 13:39:36 2026] 127.0.0.1:49628 Accepted +[Fri Apr 3 13:39:36 2026] 127.0.0.1:49628 [200]: GET /js/astronomy.browser.min.js +[Fri Apr 3 13:39:36 2026] 127.0.0.1:49628 Closing +[Fri Apr 3 13:39:36 2026] 127.0.0.1:60178 Accepted +[Fri Apr 3 13:39:36 2026] 127.0.0.1:63240 Accepted +[Fri Apr 3 13:39:36 2026] 127.0.0.1:60178 [200]: GET /images/star16x16.png +[Fri Apr 3 13:39:36 2026] 127.0.0.1:63240 [200]: GET /images/star16x16_rays.png +[Fri Apr 3 13:39:36 2026] 127.0.0.1:60178 Closing +[Fri Apr 3 13:39:36 2026] 127.0.0.1:63240 Closing +[Fri Apr 3 13:39:36 2026] 127.0.0.1:53540 Accepted +[Fri Apr 3 13:39:38 2026] 127.0.0.1:53540 [200]: GET /favicon.ico +[Fri Apr 3 13:39:38 2026] 127.0.0.1:53540 Closing +[Fri Apr 3 13:40:20 2026] 127.0.0.1:64903 Accepted +[Fri Apr 3 13:40:28 2026] 127.0.0.1:64903 [200]: GET /monatsvorhersage_old.php?year=2026&month=8 +[Fri Apr 3 13:40:28 2026] 127.0.0.1:64903 Closing +[Fri Apr 3 13:40:28 2026] 127.0.0.1:58859 Accepted +[Fri Apr 3 13:40:28 2026] 127.0.0.1:58859 [200]: GET /css/style.css +[Fri Apr 3 13:40:28 2026] 127.0.0.1:58859 Closing +[Fri Apr 3 13:40:28 2026] 127.0.0.1:63926 Accepted +[Fri Apr 3 13:40:28 2026] 127.0.0.1:63926 [200]: GET /js/astronomy.browser.min.js +[Fri Apr 3 13:40:28 2026] 127.0.0.1:63926 Closing +[Fri Apr 3 13:40:28 2026] 127.0.0.1:58621 Accepted +[Fri Apr 3 13:40:28 2026] 127.0.0.1:58621 [200]: GET /images/star16x16.png +[Fri Apr 3 13:40:28 2026] 127.0.0.1:56978 Accepted +[Fri Apr 3 13:40:28 2026] 127.0.0.1:56978 [200]: GET /images/star16x16_rays.png +[Fri Apr 3 13:40:28 2026] 127.0.0.1:58621 Closing +[Fri Apr 3 13:40:28 2026] 127.0.0.1:56978 Closing +[Fri Apr 3 13:40:28 2026] 127.0.0.1:63117 Accepted +[Fri Apr 3 13:40:30 2026] 127.0.0.1:63117 [200]: GET /favicon.ico +[Fri Apr 3 13:40:30 2026] 127.0.0.1:63117 Closing diff --git a/public/monatsvorhersage_old.php b/public/monatsvorhersage_old.php index b3598e3..e571afd 100644 --- a/public/monatsvorhersage_old.php +++ b/public/monatsvorhersage_old.php @@ -60,6 +60,8 @@ $moonPhaseEvents = []; $moonPlanetApproaches = []; $planetConjunctions = []; $eclipseEvents = []; +$moonApsisEvents = []; +$sunApsisEvents = []; $moonOccultations = []; $seasonChanges = []; $monthEventList = []; @@ -368,6 +370,40 @@ if (is_array($eclipseDecoded) && ($eclipseDecoded['ok'] ?? false)) { $eclipseEvents = is_array($eclipseDecoded['events'] ?? null) ? $eclipseDecoded['events'] : []; } +$moonApsisOutput = []; +$moonApsisExitCode = 0; +$moonApsisArgs = [ + $pythonScriptPath, + 'moon_apsides_for_month', + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, +]; +$moonApsisCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $moonApsisArgs)) . ' 2>&1'; +exec($moonApsisCommand, $moonApsisOutput, $moonApsisExitCode); +$moonApsisRaw = trim(implode("\n", $moonApsisOutput)); +$moonApsisDecoded = json_decode($moonApsisRaw, true); +if (is_array($moonApsisDecoded) && ($moonApsisDecoded['ok'] ?? false)) { + $moonApsisEvents = is_array($moonApsisDecoded['events'] ?? null) ? $moonApsisDecoded['events'] : []; +} + +$sunApsisOutput = []; +$sunApsisExitCode = 0; +$sunApsisArgs = [ + $pythonScriptPath, + 'sun_apsides_for_month', + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, +]; +$sunApsisCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $sunApsisArgs)) . ' 2>&1'; +exec($sunApsisCommand, $sunApsisOutput, $sunApsisExitCode); +$sunApsisRaw = trim(implode("\n", $sunApsisOutput)); +$sunApsisDecoded = json_decode($sunApsisRaw, true); +if (is_array($sunApsisDecoded) && ($sunApsisDecoded['ok'] ?? false)) { + $sunApsisEvents = is_array($sunApsisDecoded['events'] ?? null) ? $sunApsisDecoded['events'] : []; +} + $moonPlanetOutput = []; $moonPlanetExitCode = 0; $moonPlanetArgs = [ @@ -517,6 +553,50 @@ foreach ($eclipseEvents as $eclipseEvent) { ]; } +foreach ($moonApsisEvents as $moonApsisEvent) { + if (!is_array($moonApsisEvent)) { + continue; + } + + $moonApsisKind = (string) ($moonApsisEvent['kind'] ?? ''); + $distanceText = isset($moonApsisEvent['distance_km']) + ? number_format((float) $moonApsisEvent['distance_km'], 0, ',', '.') . ' km' + : ''; + $moonApsisLabel = $moonApsisKind === 'perigee' + ? 'Mond in Erdnähe (Perigäum)' + : ($moonApsisKind === 'apogee' ? 'Mond in Erdferne (Apogäum)' : (string) ($moonApsisEvent['label'] ?? 'Mondabstand')); + + $monthEventList[] = [ + 'event' => trim($moonApsisLabel . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), + 'date' => (string) ($moonApsisEvent['local_date'] ?? ''), + 'time' => (string) ($moonApsisEvent['local_time'] ?? ''), + 'local_iso' => (string) ($moonApsisEvent['local_iso'] ?? ''), + 'type' => 'moon_apsis', + ]; +} + +foreach ($sunApsisEvents as $sunApsisEvent) { + if (!is_array($sunApsisEvent)) { + continue; + } + + $sunApsisKind = (string) ($sunApsisEvent['kind'] ?? ''); + $distanceText = isset($sunApsisEvent['distance_km']) + ? number_format((float) $sunApsisEvent['distance_km'], 0, ',', '.') . ' km' + : ''; + $sunApsisLabel = $sunApsisKind === 'perihelion' + ? 'Erde erreicht sonnennächsten Punkt (Perihel)' + : ($sunApsisKind === 'aphelion' ? 'Erde erreicht sonnenfernsten Punkt (Aphel)' : (string) ($sunApsisEvent['label'] ?? 'Sonnenabstand')); + + $monthEventList[] = [ + 'event' => trim($sunApsisLabel . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), + 'date' => (string) ($sunApsisEvent['local_date'] ?? ''), + 'time' => (string) ($sunApsisEvent['local_time'] ?? ''), + 'local_iso' => (string) ($sunApsisEvent['local_iso'] ?? ''), + 'type' => 'sun_apsis', + ]; +} + foreach ($moonPlanetApproaches as $approachEvent) { if (!is_array($approachEvent)) { continue; diff --git a/public/py/__pycache__/api.cpython-314.pyc b/public/py/__pycache__/api.cpython-314.pyc index 2a0d7ec..b421731 100644 Binary files a/public/py/__pycache__/api.cpython-314.pyc and b/public/py/__pycache__/api.cpython-314.pyc differ diff --git a/public/py/api.py b/public/py/api.py index d957fbb..aa99651 100644 --- a/public/py/api.py +++ b/public/py/api.py @@ -1014,6 +1014,146 @@ def action_eclipses_for_month(args: list[str]) -> dict: } +def action_moon_apsides_for_month(args: list[str]) -> dict: + if len(args) != 3: + fail( + "Aktion moon_apsides_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) + + utc_start = local_start.astimezone(timezone.utc) + scan_start = dt_to_time(utc_start - timedelta(days=10)) + + events: list[dict] = [] + apsis = astronomy.SearchLunarApsis(scan_start) + while True: + dt_utc = time_to_datetime(apsis.time) + local_dt = dt_utc.astimezone(tz) + if local_dt >= local_end: + break + + if local_dt >= local_start: + is_perigee = apsis.kind == astronomy.ApsisKind.Pericenter + label = "Mondnaehe (Perigaeum)" if is_perigee else "Mondferne (Apogaeum)" + events.append({ + "kind": "perigee" if is_perigee else "apogee", + "label": label, + "distance_km": float(apsis.dist_au * astronomy.KM_PER_AU), + "utc_iso": dt_utc.isoformat().replace("+00:00", "Z"), + "local_iso": local_dt.isoformat(), + "local_date": local_dt.strftime("%d.%m.%Y"), + "local_time": local_dt.strftime("%H:%M"), + }) + + apsis = astronomy.NextLunarApsis(apsis) + + events.sort(key=lambda item: item["local_iso"]) + + return { + "ok": True, + "action": "moon_apsides_for_month", + "selected": { + "year": year, + "month": month, + "timezone": timezone_name, + }, + "events": events, + } + + +def action_sun_apsides_for_month(args: list[str]) -> dict: + if len(args) != 3: + fail( + "Aktion sun_apsides_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) + + utc_start = local_start.astimezone(timezone.utc) + scan_start = dt_to_time(utc_start - timedelta(days=40)) + + events: list[dict] = [] + apsis = astronomy.SearchPlanetApsis(astronomy.Body.Earth, scan_start) + while True: + dt_utc = time_to_datetime(apsis.time) + local_dt = dt_utc.astimezone(tz) + if local_dt >= local_end: + break + + if local_dt >= local_start: + is_perihelion = apsis.kind == astronomy.ApsisKind.Pericenter + label = ( + "Erde erreicht sonnennachsten Punkt (Perihel)" + if is_perihelion + else "Erde erreicht sonnenfernsten Punkt (Aphel)" + ) + events.append({ + "kind": "perihelion" if is_perihelion else "aphelion", + "label": label, + "distance_km": float(apsis.dist_au * astronomy.KM_PER_AU), + "utc_iso": dt_utc.isoformat().replace("+00:00", "Z"), + "local_iso": local_dt.isoformat(), + "local_date": local_dt.strftime("%d.%m.%Y"), + "local_time": local_dt.strftime("%H:%M"), + }) + + apsis = astronomy.NextPlanetApsis(astronomy.Body.Earth, apsis) + + events.sort(key=lambda item: item["local_iso"]) + + return { + "ok": True, + "action": "sun_apsides_for_month", + "selected": { + "year": year, + "month": month, + "timezone": timezone_name, + }, + "events": events, + } + + def action_moon_star_occultations_for_month(args: list[str]) -> dict: if len(args) != 7: fail( @@ -2070,7 +2210,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_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", "season_changes_for_month", "moon_planet_approaches", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "moon_star_occultations_for_month"]}) action = sys.argv[1] args = sys.argv[2:] @@ -2130,12 +2270,22 @@ def main() -> None: print(json.dumps(result, ensure_ascii=False)) return + if action == "moon_apsides_for_month": + result = action_moon_apsides_for_month(args) + print(json.dumps(result, ensure_ascii=False)) + return + + if action == "sun_apsides_for_month": + result = action_sun_apsides_for_month(args) + print(json.dumps(result, ensure_ascii=False)) + return + if action == "moon_star_occultations_for_month": result = action_moon_star_occultations_for_month(args) 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_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", "season_changes_for_month", "moon_planet_approaches", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "moon_star_occultations_for_month"]}) if __name__ == "__main__":