diff --git a/public/images/moons/moon4calendar.png b/public/images/moons/moon4calendar.png new file mode 100644 index 0000000..af65e06 Binary files /dev/null and b/public/images/moons/moon4calendar.png differ diff --git a/public/moonyear.php b/public/moonyear.php index c0f8be7..5715a4f 100644 --- a/public/moonyear.php +++ b/public/moonyear.php @@ -3,10 +3,14 @@ declare(strict_types=1); session_start(); -$currentYear = (int) date('Y'); -$selectedYear = isset($_GET['year']) ? max(1900, min(2100, (int) $_GET['year'])) : $currentYear; +$currentYear = (int) date('Y'); +$currentMonth = (int) date('n'); +$selectedYear = isset($_GET['year']) ? max(1900, min(2100, (int) $_GET['year'])) : $currentYear; +$selectedMonth = isset($_GET['month']) ? max(1, min(12, (int) $_GET['month'])) : $currentMonth; +$activeTab = (isset($_GET['tab']) && $_GET['tab'] === 'month') ? 'month' : 'year'; +$doCalc = isset($_GET['calc']); -$pageTitle = 'Mondphasen-Jahresübersicht ' . $selectedYear . ' - AstroTools'; +$pageTitle = 'Mondphasen - AstroTools'; $bodyClass = 'moonyear-page'; $publicBasePath = './'; @@ -20,39 +24,83 @@ if (!empty($currentLocation['timezone'])) { $chartTimezone = (string) $currentLocation['timezone']; } -// ------------------------------------------------------------------------- -// Python: Mondphasen für alle 12 Monate abrufen -// ------------------------------------------------------------------------- $pythonExecutable = PHP_OS_FAMILY === 'Windows' ? 'python' : '/usr/bin/python'; $pythonScriptPath = __DIR__ . '/py/api.py'; -$allPhaseEvents = []; +$monthNamesDE = [ + 1 => 'Januar', 2 => 'Februar', 3 => 'März', 4 => 'April', + 5 => 'Mai', 6 => 'Juni', 7 => 'Juli', 8 => 'August', + 9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Dezember', +]; -for ($m = 1; $m <= 12; $m++) { +// ------------------------------------------------------------------------- +// Python: Jahresübersicht — nur wenn Tab=Jahr und Berechnen geklickt +// ------------------------------------------------------------------------- +$allPhaseEvents = []; +$lunarEclipses = []; // [{kind, month, day, local_time}, ...] +if ($doCalc && $activeTab === 'year') { + for ($m = 1; $m <= 12; $m++) { + $output = []; + $exitCode = 0; + $cmd = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', [ + $pythonScriptPath, 'moon_phases_for_month', + (string) $selectedYear, (string) $m, $chartTimezone, + ])) . ' 2>&1'; + exec($cmd, $output, $exitCode); + $decoded = json_decode(trim(implode("\n", $output)), true); + if (is_array($decoded) && ($decoded['ok'] ?? false) && is_array($decoded['phases'] ?? null)) { + foreach ($decoded['phases'] as $phase) { + $parts = explode('.', (string) ($phase['local_date'] ?? '')); + if (count($parts) === 3) { + $day = (int) $parts[0]; + $month = (int) $parts[1]; + if ($month === $m && $day >= 1 && $day <= 31) { + $allPhaseEvents[] = [ + 'month' => $month, + 'day' => $day, + 'label' => (string) ($phase['label'] ?? ''), + 'time' => (string) ($phase['local_time'] ?? ''), + ]; + } + } + } + } + } + + // Mondfinsternisse für das Jahr + $ecOutput = []; + $ecExitCode = 0; + $ecCmd = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', [ + $pythonScriptPath, 'lunar_eclipses_for_year', + (string) $selectedYear, $chartTimezone, + ])) . ' 2>&1'; + exec($ecCmd, $ecOutput, $ecExitCode); + $ecDecoded = json_decode(trim(implode("\n", $ecOutput)), true); + if (is_array($ecDecoded) && ($ecDecoded['ok'] ?? false) && is_array($ecDecoded['eclipses'] ?? null)) { + $lunarEclipses = $ecDecoded['eclipses']; + } +} + +// ------------------------------------------------------------------------- +// Python: Monatskalender — nur wenn Tab=Monat und Berechnen geklickt +// ------------------------------------------------------------------------- +$calPhaseEvents = []; +if ($doCalc && $activeTab === 'month') { $output = []; $exitCode = 0; - $args = [ - $pythonScriptPath, - 'moon_phases_for_month', - (string) $selectedYear, - (string) $m, - $chartTimezone, - ]; - $cmd = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $args)) . ' 2>&1'; + $cmd = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', [ + $pythonScriptPath, 'moon_phases_for_month', + (string) $selectedYear, (string) $selectedMonth, $chartTimezone, + ])) . ' 2>&1'; exec($cmd, $output, $exitCode); - $raw = trim(implode("\n", $output)); - $decoded = json_decode($raw, true); + $decoded = json_decode(trim(implode("\n", $output)), true); if (is_array($decoded) && ($decoded['ok'] ?? false) && is_array($decoded['phases'] ?? null)) { foreach ($decoded['phases'] as $phase) { - // local_date format: "DD.MM.YYYY" $parts = explode('.', (string) ($phase['local_date'] ?? '')); if (count($parts) === 3) { - $day = (int) $parts[0]; - $month = (int) $parts[1]; - if ($month === $m && $day >= 1 && $day <= 31) { - $allPhaseEvents[] = [ - 'month' => $month, - 'day' => $day, + $day = (int) $parts[0]; + if ($day >= 1 && $day <= 31) { + $calPhaseEvents[$day] = [ 'label' => (string) ($phase['label'] ?? ''), 'time' => (string) ($phase['local_time'] ?? ''), ]; @@ -60,371 +108,588 @@ for ($m = 1; $m <= 12; $m++) { } } } + + // Mondfinsternisse für das Jahr (für den Monats-Tab benötigt) + $ecOutput = []; + $ecExitCode = 0; + $ecCmd = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', [ + $pythonScriptPath, 'lunar_eclipses_for_year', + (string) $selectedYear, $chartTimezone, + ])) . ' 2>&1'; + exec($ecCmd, $ecOutput, $ecExitCode); + $ecDecoded = json_decode(trim(implode("\n", $ecOutput)), true); + if (is_array($ecDecoded) && ($ecDecoded['ok'] ?? false) && is_array($ecDecoded['eclipses'] ?? null)) { + $lunarEclipses = $ecDecoded['eclipses']; + } } ?>
-

Mondphasen

+

Mondphasen

-
- - - - - Aktuelles Jahr - - -
- -
- Mondphasen <?= htmlspecialchars((string) $selectedYear, ENT_QUOTES, 'UTF-8') ?> +
+ +
-

Zeichne Mondphasen-Jahresübersicht…

-
- NM = Neumond  ·  EV = Erstes Viertel  ·  VM = Vollmond  ·  LV = Letztes Viertel -  ·  Zeitzone: + +
+
+ + +
+ + +
+
+ + + + +
+
+ + +
+ Mondphasen <?= htmlspecialchars((string) $selectedYear, ENT_QUOTES, 'UTF-8') ?> +
+

Zeichne Jahresübersicht…

+ +
Jahr wählen und auf  Berechnen  klicken.
+ +
+ + +
+
+ + +
+ + +
+
+ + +
+
+ + + + +
+
+ + +
+ Mondkalender <?= htmlspecialchars($monthNamesDE[$selectedMonth] . ' ' . $selectedYear, ENT_QUOTES, 'UTF-8') ?> +
+

Zeichne Monatskalender…

+ +
Monat wählen und auf  Berechnen  klicken.
+
- + - // Phase events from Python: [{month, day, label, time}, ...] - // label = "Neumond" | "Erstes Viertel" | "Vollmond" | "Letztes Viertel" - const PHASE_EVENTS = ; + + + - if (!imageEl || !statusEl) return; + + + + + + + diff --git a/public/py/api.py b/public/py/api.py index 9e5fc70..8052352 100644 --- a/public/py/api.py +++ b/public/py/api.py @@ -2834,6 +2834,46 @@ def serialize_time_only(time_value: astronomy.Time, tz: ZoneInfo) -> dict: } +def action_lunar_eclipses_for_year(args: list[str]) -> dict: + if len(args) != 2: + fail("Aktion lunar_eclipses_for_year erwartet 2 Argumente: year timezone", extra={"argv": args}) + + try: + year = int(args[0]) + except ValueError as exc: + fail("Jahr ist ungueltig.", extra={"details": str(exc)}) + + timezone_name = args[1] + try: + tz = ZoneInfo(timezone_name) + except Exception as exc: + fail("Zeitzone ist ungueltig.", extra={"details": str(exc)}) + + year_start = datetime(year, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + year_end = datetime(year + 1, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + scan_start = dt_to_time(year_start - timedelta(days=20)) + + eclipses = [] + lunar = astronomy.SearchLunarEclipse(scan_start) + while True: + peak_utc = time_to_datetime(lunar.peak) + if peak_utc >= year_end: + break + if peak_utc >= year_start: + local_peak = peak_utc.astimezone(tz) + eclipses.append({ + "kind": lunar.kind.name.lower(), + "month": local_peak.month, + "day": local_peak.day, + "local_date": local_peak.strftime("%d.%m.%Y"), + "local_time": local_peak.strftime("%H:%M"), + "utc_iso": peak_utc.isoformat().replace("+00:00", "Z"), + }) + lunar = astronomy.NextLunarEclipse(lunar.peak) + + return {"ok": True, "action": "lunar_eclipses_for_year", "year": year, "eclipses": eclipses} + + def action_eclipses_for_month(args: list[str]) -> dict: if len(args) != 6: fail( @@ -5057,6 +5097,11 @@ def main() -> None: print(json.dumps(result, ensure_ascii=True)) return + if action == "lunar_eclipses_for_year": + result = action_lunar_eclipses_for_year(args) + print(json.dumps(result, ensure_ascii=True)) + return + fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "solar_longitude_to_datetime", "current_solar_longitude", "astronomical_conversions", "comet_brightnesses", "favorite_comet_events_for_month", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "planet_visibility_chart", "month_sky_context", "moon_phases_for_month", "golden_handle_for_month", "season_changes_for_month", "time_changes_for_month", "moon_planet_approaches", "moon_deep_sky_approaches_for_month", "planet_bright_star_approaches_for_month", "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", "jupiter_moons_one_side_for_month", "mercury_good_visibility_for_month", "planet_parades_for_month", "planet_constellation_changes_for_month", "moon_star_occultations_for_month"]})