From 91f92b814af720e7f9f4463ea434dd959f7fc9ea Mon Sep 17 00:00:00 2001 From: Eskimue Date: Fri, 3 Apr 2026 16:25:37 +0200 Subject: [PATCH] Sommer Winterzeit drin --- .codex-temp/php-server.err.log | 120 ++++++++++++++++++++++++++++++++ public/monatsvorhersage_old.php | 42 +++++++++++ public/py/api.py | 94 ++++++++++++++++++++++++- 3 files changed, 254 insertions(+), 2 deletions(-) diff --git a/.codex-temp/php-server.err.log b/.codex-temp/php-server.err.log index 585c3e0..e35c176 100644 --- a/.codex-temp/php-server.err.log +++ b/.codex-temp/php-server.err.log @@ -782,3 +782,123 @@ Stack trace: [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 +[Fri Apr 3 15:45:36 2026] 127.0.0.1:59896 Accepted +[Fri Apr 3 15:45:44 2026] 127.0.0.1:59896 [200]: GET /monatsvorhersage_old.php?year=2026&month=9 +[Fri Apr 3 15:45:44 2026] 127.0.0.1:59896 Closing +[Fri Apr 3 15:49:51 2026] 127.0.0.1:53418 Accepted +[Fri Apr 3 15:50:00 2026] 127.0.0.1:53418 [200]: GET /monatsvorhersage_old.php?year=2026&month=9 +[Fri Apr 3 15:50:00 2026] 127.0.0.1:53418 Closing +[Fri Apr 3 15:50:00 2026] 127.0.0.1:57283 Accepted +[Fri Apr 3 15:50:00 2026] 127.0.0.1:60076 Accepted +[Fri Apr 3 15:50:00 2026] 127.0.0.1:57283 [200]: GET /css/style.css +[Fri Apr 3 15:50:00 2026] 127.0.0.1:60076 [200]: GET /js/astronomy.browser.min.js +[Fri Apr 3 15:50:00 2026] 127.0.0.1:57283 Closing +[Fri Apr 3 15:50:00 2026] 127.0.0.1:60076 Closing +[Fri Apr 3 15:50:00 2026] 127.0.0.1:62073 Accepted +[Fri Apr 3 15:50:00 2026] 127.0.0.1:62073 [200]: GET /images/star16x16.png +[Fri Apr 3 15:50:00 2026] 127.0.0.1:51356 Accepted +[Fri Apr 3 15:50:00 2026] 127.0.0.1:51356 [200]: GET /images/star16x16_rays.png +[Fri Apr 3 15:50:00 2026] 127.0.0.1:62073 Closing +[Fri Apr 3 15:50:00 2026] 127.0.0.1:51356 Closing +[Fri Apr 3 15:50:00 2026] 127.0.0.1:56915 Accepted +[Fri Apr 3 15:50:01 2026] 127.0.0.1:56915 [200]: GET /favicon.ico +[Fri Apr 3 15:50:01 2026] 127.0.0.1:56915 Closing +[Fri Apr 3 15:52:41 2026] 127.0.0.1:54040 Accepted +[Fri Apr 3 15:52:49 2026] 127.0.0.1:54040 [200]: GET /monatsvorhersage_old.php?year=2026&month=9 +[Fri Apr 3 15:52:49 2026] 127.0.0.1:54040 Closing +[Fri Apr 3 15:53:10 2026] 127.0.0.1:65142 Accepted +[Fri Apr 3 15:53:19 2026] 127.0.0.1:65142 [200]: GET /monatsvorhersage_old.php?year=2026&month=9 +[Fri Apr 3 15:53:19 2026] 127.0.0.1:65142 Closing +[Fri Apr 3 15:53:19 2026] 127.0.0.1:50833 Accepted +[Fri Apr 3 15:53:19 2026] 127.0.0.1:50833 [200]: GET /css/style.css +[Fri Apr 3 15:53:19 2026] 127.0.0.1:50833 Closing +[Fri Apr 3 15:53:19 2026] 127.0.0.1:52437 Accepted +[Fri Apr 3 15:53:19 2026] 127.0.0.1:52437 [200]: GET /js/astronomy.browser.min.js +[Fri Apr 3 15:53:19 2026] 127.0.0.1:52437 Closing +[Fri Apr 3 15:53:19 2026] 127.0.0.1:62874 Accepted +[Fri Apr 3 15:53:19 2026] 127.0.0.1:62874 [200]: GET /images/star16x16.png +[Fri Apr 3 15:53:19 2026] 127.0.0.1:49611 Accepted +[Fri Apr 3 15:53:19 2026] 127.0.0.1:49611 [200]: GET /images/star16x16_rays.png +[Fri Apr 3 15:53:19 2026] 127.0.0.1:62874 Closing +[Fri Apr 3 15:53:19 2026] 127.0.0.1:49611 Closing +[Fri Apr 3 15:53:19 2026] 127.0.0.1:63993 Accepted +[Fri Apr 3 15:53:20 2026] 127.0.0.1:63993 [200]: GET /favicon.ico +[Fri Apr 3 15:53:20 2026] 127.0.0.1:63993 Closing +[Fri Apr 3 15:53:36 2026] 127.0.0.1:52820 Accepted +[Fri Apr 3 15:53:45 2026] 127.0.0.1:52820 [200]: GET /monatsvorhersage_old.php?year=2026&month=7 +[Fri Apr 3 15:53:45 2026] 127.0.0.1:52820 Closing +[Fri Apr 3 15:53:45 2026] 127.0.0.1:50899 Accepted +[Fri Apr 3 15:53:45 2026] 127.0.0.1:50899 [200]: GET /css/style.css +[Fri Apr 3 15:53:45 2026] 127.0.0.1:50899 Closing +[Fri Apr 3 15:53:45 2026] 127.0.0.1:61600 Accepted +[Fri Apr 3 15:53:45 2026] 127.0.0.1:61600 [200]: GET /js/astronomy.browser.min.js +[Fri Apr 3 15:53:45 2026] 127.0.0.1:61600 Closing +[Fri Apr 3 15:53:45 2026] 127.0.0.1:59909 Accepted +[Fri Apr 3 15:53:45 2026] 127.0.0.1:62643 Accepted +[Fri Apr 3 15:53:45 2026] 127.0.0.1:59909 [200]: GET /images/star16x16.png +[Fri Apr 3 15:53:45 2026] 127.0.0.1:62643 [200]: GET /images/star16x16_rays.png +[Fri Apr 3 15:53:45 2026] 127.0.0.1:59909 Closing +[Fri Apr 3 15:53:45 2026] 127.0.0.1:62643 Closing +[Fri Apr 3 15:53:45 2026] 127.0.0.1:58844 Accepted +[Fri Apr 3 15:53:46 2026] 127.0.0.1:58844 [200]: GET /favicon.ico +[Fri Apr 3 15:53:46 2026] 127.0.0.1:58844 Closing +[Fri Apr 3 15:55:14 2026] 127.0.0.1:56372 Accepted +[Fri Apr 3 15:55:23 2026] 127.0.0.1:56372 [200]: GET /monatsvorhersage_old.php?year=2026&month=9 +[Fri Apr 3 15:55:23 2026] 127.0.0.1:56372 Closing +[Fri Apr 3 15:56:00 2026] 127.0.0.1:64711 Accepted +[Fri Apr 3 15:56:09 2026] 127.0.0.1:64711 [200]: GET /monatsvorhersage_old.php?year=2026&month=9 +[Fri Apr 3 15:56:09 2026] 127.0.0.1:64711 Closing +[Fri Apr 3 15:56:09 2026] 127.0.0.1:63571 Accepted +[Fri Apr 3 15:56:10 2026] 127.0.0.1:63571 [200]: GET /favicon.ico +[Fri Apr 3 15:56:10 2026] 127.0.0.1:63571 Closing +[Fri Apr 3 16:01:09 2026] 127.0.0.1:58142 Accepted +[Fri Apr 3 16:01:18 2026] 127.0.0.1:58142 [200]: GET /monatsvorhersage_old.php?year=2026&month=9 +[Fri Apr 3 16:01:18 2026] 127.0.0.1:58142 Closing +[Fri Apr 3 16:03:02 2026] 127.0.0.1:62323 Accepted +[Fri Apr 3 16:03:16 2026] 127.0.0.1:62323 [200]: GET /monatsvorhersage_old.php?year=2026&month=3 +[Fri Apr 3 16:03:16 2026] 127.0.0.1:62323 Closing +[Fri Apr 3 16:03:16 2026] 127.0.0.1:62462 Accepted +[Fri Apr 3 16:03:16 2026] 127.0.0.1:61754 Accepted +[Fri Apr 3 16:03:16 2026] 127.0.0.1:62462 [200]: GET /css/style.css +[Fri Apr 3 16:03:16 2026] 127.0.0.1:61754 [200]: GET /js/astronomy.browser.min.js +[Fri Apr 3 16:03:16 2026] 127.0.0.1:62462 Closing +[Fri Apr 3 16:03:16 2026] 127.0.0.1:61754 Closing +[Fri Apr 3 16:03:16 2026] 127.0.0.1:57741 Accepted +[Fri Apr 3 16:03:16 2026] 127.0.0.1:51820 Accepted +[Fri Apr 3 16:03:16 2026] 127.0.0.1:57741 [200]: GET /images/star16x16.png +[Fri Apr 3 16:03:16 2026] 127.0.0.1:51820 [200]: GET /images/star16x16_rays.png +[Fri Apr 3 16:03:16 2026] 127.0.0.1:57741 Closing +[Fri Apr 3 16:03:16 2026] 127.0.0.1:51820 Closing +[Fri Apr 3 16:03:16 2026] 127.0.0.1:60434 Accepted +[Fri Apr 3 16:03:18 2026] 127.0.0.1:60434 [200]: GET /favicon.ico +[Fri Apr 3 16:03:18 2026] 127.0.0.1:60434 Closing +[Fri Apr 3 16:07:00 2026] 127.0.0.1:54937 Accepted +[Fri Apr 3 16:07:15 2026] 127.0.0.1:54937 [200]: GET /monatsvorhersage_old.php?year=2026&month=3 +[Fri Apr 3 16:07:15 2026] 127.0.0.1:54937 Closing +[Fri Apr 3 16:07:15 2026] 127.0.0.1:54939 Accepted +[Fri Apr 3 16:07:24 2026] 127.0.0.1:54939 [200]: GET /monatsvorhersage_old.php?year=2026&month=9 +[Fri Apr 3 16:07:24 2026] 127.0.0.1:54939 Closing +[Fri Apr 3 16:08:54 2026] 127.0.0.1:50294 Accepted +[Fri Apr 3 16:09:03 2026] 127.0.0.1:50294 [200]: GET /monatsvorhersage_old.php?year=2026&month=12 +[Fri Apr 3 16:09:03 2026] 127.0.0.1:50294 Closing +[Fri Apr 3 16:09:03 2026] 127.0.0.1:53963 Accepted +[Fri Apr 3 16:09:03 2026] 127.0.0.1:53963 [200]: GET /css/style.css +[Fri Apr 3 16:09:03 2026] 127.0.0.1:64822 Accepted +[Fri Apr 3 16:09:03 2026] 127.0.0.1:53963 Closing +[Fri Apr 3 16:09:03 2026] 127.0.0.1:64822 [200]: GET /js/astronomy.browser.min.js +[Fri Apr 3 16:09:03 2026] 127.0.0.1:64822 Closing +[Fri Apr 3 16:09:03 2026] 127.0.0.1:57513 Accepted +[Fri Apr 3 16:09:03 2026] 127.0.0.1:57513 [200]: GET /images/star16x16.png +[Fri Apr 3 16:09:03 2026] 127.0.0.1:50161 Accepted +[Fri Apr 3 16:09:03 2026] 127.0.0.1:50161 [200]: GET /images/star16x16_rays.png +[Fri Apr 3 16:09:03 2026] 127.0.0.1:57513 Closing +[Fri Apr 3 16:09:03 2026] 127.0.0.1:50161 Closing +[Fri Apr 3 16:09:03 2026] 127.0.0.1:53090 Accepted +[Fri Apr 3 16:09:05 2026] 127.0.0.1:53090 [200]: GET /favicon.ico +[Fri Apr 3 16:09:05 2026] 127.0.0.1:53090 Closing +[Fri Apr 3 16:10:03 2026] 127.0.0.1:58129 Accepted +[Fri Apr 3 16:10:12 2026] 127.0.0.1:58129 [200]: GET /monatsvorhersage_old.php?year=2026&month=12 +[Fri Apr 3 16:10:12 2026] 127.0.0.1:58129 Closing +[Fri Apr 3 16:13:22 2026] 127.0.0.1:62960 Accepted +[Fri Apr 3 16:13:31 2026] 127.0.0.1:62960 [200]: GET /monatsvorhersage_old.php?year=2026&month=12 +[Fri Apr 3 16:13:31 2026] 127.0.0.1:62960 Closing diff --git a/public/monatsvorhersage_old.php b/public/monatsvorhersage_old.php index a8822aa..568e2ef 100644 --- a/public/monatsvorhersage_old.php +++ b/public/monatsvorhersage_old.php @@ -69,6 +69,7 @@ $outerPlanetEvents = []; $outerPlanetStationEvents = []; $moonOccultations = []; $seasonChanges = []; +$timeChanges = []; $monthEventList = []; $starDataError = null; @@ -372,6 +373,23 @@ if (is_array($seasonDecoded) && ($seasonDecoded['ok'] ?? false)) { $seasonChanges = is_array($seasonDecoded['changes'] ?? null) ? $seasonDecoded['changes'] : []; } +$timeChangeOutput = []; +$timeChangeExitCode = 0; +$timeChangeArgs = [ + $pythonScriptPath, + 'time_changes_for_month', + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, +]; +$timeChangeCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $timeChangeArgs)) . ' 2>&1'; +exec($timeChangeCommand, $timeChangeOutput, $timeChangeExitCode); +$timeChangeRaw = trim(implode("\n", $timeChangeOutput)); +$timeChangeDecoded = json_decode($timeChangeRaw, true); +if (is_array($timeChangeDecoded) && ($timeChangeDecoded['ok'] ?? false)) { + $timeChanges = is_array($timeChangeDecoded['events'] ?? null) ? $timeChangeDecoded['events'] : []; +} + $eclipseOutput = []; $eclipseExitCode = 0; $eclipseArgs = [ @@ -647,6 +665,30 @@ foreach ($seasonChanges as $seasonChange) { ]; } +foreach ($timeChanges as $timeChange) { + if (!is_array($timeChange)) { + continue; + } + + $timeChangeKind = (string) ($timeChange['kind'] ?? ''); + $timeChangeLabel = $timeChangeKind === 'dst_start' + ? 'Beginn der Sommerzeit (Uhren von 02:00 auf 03:00 Uhr vorstellen)' + : ($timeChangeKind === 'dst_end' + ? 'Beginn der Normalzeit (Uhren von 03:00 auf 02:00 Uhr zurueckstellen)' + : 'Zeitumstellung'); + $timeChangeTime = $timeChangeKind === 'dst_start' + ? '02:00' + : ($timeChangeKind === 'dst_end' ? '03:00' : (string) ($timeChange['local_time'] ?? '')); + + $monthEventList[] = [ + 'event' => $timeChangeLabel, + 'date' => (string) ($timeChange['local_date'] ?? ''), + 'time' => $timeChangeTime, + 'local_iso' => (string) ($timeChange['local_iso'] ?? ''), + 'type' => 'time_change', + ]; +} + foreach ($eclipseEvents as $eclipseEvent) { if (!is_array($eclipseEvent)) { continue; diff --git a/public/py/api.py b/public/py/api.py index 87f2f82..c0584d4 100644 --- a/public/py/api.py +++ b/public/py/api.py @@ -650,6 +650,91 @@ def action_season_changes_for_month(args: list[str]) -> dict: } +def action_time_changes_for_month(args: list[str]) -> dict: + if len(args) != 3: + fail( + "Aktion time_changes_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) - timedelta(days=2) + utc_end = local_end.astimezone(timezone.utc) + timedelta(days=2) + + def utc_offset_seconds(dt_utc: datetime) -> int: + return int(dt_utc.astimezone(tz).utcoffset().total_seconds()) + + def refine_transition(left_utc: datetime, right_utc: datetime) -> datetime: + left = left_utc + right = right_utc + left_offset = utc_offset_seconds(left) + while (right - left) > timedelta(seconds=1): + mid = left + (right - left) / 2 + if utc_offset_seconds(mid) == left_offset: + left = mid + else: + right = mid + return right + + events = [] + previous_utc = utc_start + previous_offset = utc_offset_seconds(previous_utc) + current_utc = previous_utc + timedelta(hours=1) + while current_utc <= utc_end: + current_offset = utc_offset_seconds(current_utc) + if current_offset != previous_offset: + transition_utc = refine_transition(previous_utc, current_utc) + local_dt = transition_utc.astimezone(tz) + if local_dt.year == year and local_dt.month == month: + delta_seconds = current_offset - previous_offset + kind = "dst_start" if delta_seconds > 0 else "dst_end" + events.append({ + "kind": kind, + "offset_before_hours": previous_offset / 3600.0, + "offset_after_hours": current_offset / 3600.0, + "utc_iso": transition_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"), + }) + previous_utc = current_utc + previous_offset = current_offset + current_utc += timedelta(hours=1) + + events.sort(key=lambda item: item["local_iso"]) + + return { + "ok": True, + "action": "time_changes_for_month", + "selected": { + "year": year, + "month": month, + "timezone": timezone_name, + }, + "events": events, + } + + def moon_planet_separation_deg( body: astronomy.Body, observer: astronomy.Observer, @@ -2702,7 +2787,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", "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"]}) + 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", "time_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:] @@ -2722,6 +2807,11 @@ def main() -> None: print(json.dumps(result, ensure_ascii=False)) return + if action == "time_changes_for_month": + result = action_time_changes_for_month(args) + print(json.dumps(result, ensure_ascii=False)) + return + if action == "moon_phase_details": result = action_moon_phase_details(args) print(json.dumps(result, ensure_ascii=False)) @@ -2802,7 +2892,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", "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"]}) + 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", "time_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__":