diff --git a/config/rechtesystem.json b/config/rechtesystem.json index 214d133..2607055 100644 --- a/config/rechtesystem.json +++ b/config/rechtesystem.json @@ -44,6 +44,10 @@ "label": "Planetensichtbarkeit", "menu_group": "Himmel & Planeten" }, + "sonnensternbilder.php": { + "label": "Sonne in Sternbildern", + "menu_group": "Himmel & Planeten" + }, "ephemeriden.php": { "label": "Ephemeriden", "menu_group": "Himmel & Planeten" @@ -132,6 +136,7 @@ "links": [ "moonphase.php", "planetensichtbarkeit.php", + "sonnensternbilder.php", "ephemeriden.php", "comets.php", "astronomical_conversions.php", diff --git a/public/auth_helpers.php b/public/auth_helpers.php index abdba8c..e20df23 100644 --- a/public/auth_helpers.php +++ b/public/auth_helpers.php @@ -114,6 +114,7 @@ function manageablePublicPages(): array 'my_favorites.php' => 'Favoriten', 'moonphase.php' => 'Mondphase', 'planetensichtbarkeit.php' => 'Planetensichtbarkeit', + 'sonnensternbilder.php' => 'Sonne in Sternbildern', 'ephemeriden.php' => 'Ephemeriden', 'comets.php' => 'Kometen', 'astronomical_conversions.php' => 'Astronomical Conversions', diff --git a/public/css/style.css b/public/css/style.css index aa0d417..30f8c68 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -872,6 +872,55 @@ footer a:hover { color: var(--gold); } font-style: italic; } +/* --- Sonne in Sternbildern -------------------------------- */ + +.sun-constellations-tool { + max-width: 1100px; + margin: 0 auto; +} + +.sun-constellations-form { + display: flex; + align-items: end; + gap: 0.75rem; + margin-bottom: 1rem; +} + +.sun-constellations-form label { + color: var(--text-dim); + font-size: 0.82rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.sun-constellations-form input { + width: 7rem; +} + +.sun-constellations-table-wrap { + overflow-x: auto; +} + +.sun-constellations-table td:nth-child(2), +.sun-constellations-table td:nth-child(3), +.sun-constellations-table td:nth-child(4), +.sun-constellations-table td:nth-child(5), +.sun-constellations-table td:nth-child(6), +.sun-constellations-table td:nth-child(7) { + white-space: nowrap; +} + +@media (max-width: 560px) { + .sun-constellations-form { + align-items: stretch; + flex-wrap: wrap; + } + + .sun-constellations-form label { + width: 100%; + } +} + .msg-error pre { margin-top: 0.75rem; white-space: pre-wrap; diff --git a/public/header.php b/public/header.php index 0f306a1..30d4528 100644 --- a/public/header.php +++ b/public/header.php @@ -152,6 +152,7 @@ $menuGroups = [ 'title' => 'Planeten & Sonnensystem', 'links' => [ ['href' => 'planetensichtbarkeit.php', 'label' => 'Planetensichtbarkeit'], + ['href' => 'sonnensternbilder.php', 'label' => 'Sonne in Sternbildern'], ['href' => 'ephemeriden.php', 'label' => 'Ephemeriden'], ['href' => 'solarsystem.php', 'label' => 'Sonnensystem'], ['href' => 'barycenter.php', 'label' => 'Baryzentrum'], diff --git a/public/py/api.py b/public/py/api.py index 5ce9c92..36cfa07 100644 --- a/public/py/api.py +++ b/public/py/api.py @@ -2174,6 +2174,138 @@ def action_planet_constellation_changes_for_month(args: list[str]) -> dict: } +def action_sun_constellation_changes_for_year(args: list[str]) -> dict: + if len(args) != 2: + fail( + "Aktion sun_constellation_changes_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), "argv": args}) + + if year < 1600 or year > 2200: + fail("Jahr muss zwischen 1600 und 2200 liegen.", extra={"year": year}) + + timezone_name = args[1] + 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, 1, 1, 0, 0, 0, tzinfo=tz) + local_end = datetime(year + 1, 1, 1, 0, 0, 0, tzinfo=tz) + utc_start = local_start.astimezone(timezone.utc) + utc_end = local_end.astimezone(timezone.utc) + + constellation_names = { + "Aries": "Widder", + "Taurus": "Stier", + "Gemini": "Zwillinge", + "Cancer": "Krebs", + "Leo": "Löwe", + "Virgo": "Jungfrau", + "Libra": "Waage", + "Scorpius": "Skorpion", + "Sagittarius": "Schütze", + "Capricornus": "Steinbock", + "Aquarius": "Wassermann", + "Pisces": "Fische", + "Ophiuchus": "Schlangenträger", + "Cetus": "Walfisch", + } + + def display_constellation_name(info: astronomy.ConstellationInfo) -> str: + return constellation_names.get(info.name, info.name) + + def refine_change_time(left_utc: datetime, right_utc: datetime, left_symbol: str) -> datetime: + left = left_utc + right = right_utc + for _ in range(40): + mid = left + (right - left) / 2 + if body_constellation_info(astronomy.Body.Sun, mid).symbol == left_symbol: + left = mid + else: + right = mid + return right + + events: list[dict] = [] + intervals: list[dict] = [] + interval_start_utc = utc_start + left_utc = utc_start + left_info = body_constellation_info(astronomy.Body.Sun, left_utc) + scan_step = timedelta(hours=12) + + while left_utc < utc_end: + current_utc = min(utc_end, left_utc + scan_step) + current_info = body_constellation_info(astronomy.Body.Sun, current_utc) + if current_info.symbol == left_info.symbol: + left_utc = current_utc + left_info = current_info + continue + + change_utc = refine_change_time(left_utc, current_utc, left_info.symbol) + before_info = body_constellation_info(astronomy.Body.Sun, change_utc - timedelta(seconds=1)) + after_info = body_constellation_info(astronomy.Body.Sun, change_utc) + change_local = change_utc.astimezone(tz) + interval_start_local = interval_start_utc.astimezone(tz) + + intervals.append({ + "constellation_symbol": before_info.symbol, + "constellation_name": display_constellation_name(before_info), + "start_local_iso": interval_start_local.isoformat(), + "start_local_date": interval_start_local.strftime("%d.%m.%Y"), + "start_local_time": interval_start_local.strftime("%H:%M"), + "end_local_iso": change_local.isoformat(), + "end_local_date": change_local.strftime("%d.%m.%Y"), + "end_local_time": change_local.strftime("%H:%M"), + "duration_days": round((change_utc - interval_start_utc).total_seconds() / 86400.0, 2), + }) + events.append({ + "from_constellation_symbol": before_info.symbol, + "from_constellation_name": display_constellation_name(before_info), + "to_constellation_symbol": after_info.symbol, + "to_constellation_name": display_constellation_name(after_info), + "utc_iso": change_utc.isoformat().replace("+00:00", "Z"), + "local_iso": change_local.isoformat(), + "local_date": change_local.strftime("%d.%m.%Y"), + "local_time": change_local.strftime("%H:%M"), + }) + + interval_start_utc = change_utc + left_utc = change_utc + timedelta(seconds=1) + left_info = body_constellation_info(astronomy.Body.Sun, left_utc) + + interval_start_local = interval_start_utc.astimezone(tz) + end_info = body_constellation_info(astronomy.Body.Sun, utc_end - timedelta(seconds=1)) + intervals.append({ + "constellation_symbol": end_info.symbol, + "constellation_name": display_constellation_name(end_info), + "start_local_iso": interval_start_local.isoformat(), + "start_local_date": interval_start_local.strftime("%d.%m.%Y"), + "start_local_time": interval_start_local.strftime("%H:%M"), + "end_local_iso": local_end.isoformat(), + "end_local_date": local_end.strftime("%d.%m.%Y"), + "end_local_time": local_end.strftime("%H:%M"), + "duration_days": round((utc_end - interval_start_utc).total_seconds() / 86400.0, 2), + }) + + return { + "ok": True, + "action": "sun_constellation_changes_for_year", + "selected": { + "year": year, + "timezone": timezone_name, + "local_start": local_start.isoformat(), + "local_end": local_end.isoformat(), + }, + "events": events, + "intervals": intervals, + } + + def action_month_sky_context(args: list[str]) -> dict: if len(args) != 8: fail( @@ -5925,7 +6057,7 @@ def action_barycenter(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", "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_ephemeris", "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", "golden_gate_of_ecliptic_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"]}) + fail("Es wurde keine Aktion uebergeben.", extra={"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_ephemeris", "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", "golden_gate_of_ecliptic_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", "sun_constellation_changes_for_year", "moon_star_occultations_for_month"]}) action = sys.argv[1] args = sys.argv[2:] @@ -6100,6 +6232,11 @@ def main() -> None: print(json.dumps(result, ensure_ascii=True)) return + if action == "sun_constellation_changes_for_year": + result = action_sun_constellation_changes_for_year(args) + print(json.dumps(result, ensure_ascii=True)) + return + if action == "moon_star_occultations_for_month": result = action_moon_star_occultations_for_month(args) print(json.dumps(result, ensure_ascii=True)) @@ -6115,7 +6252,7 @@ def main() -> None: 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", "twilight_chart", "comet_brightnesses", "favorite_comet_events_for_month", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "planet_ephemeris", "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", "golden_gate_of_ecliptic_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"]}) + fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "solar_longitude_to_datetime", "current_solar_longitude", "astronomical_conversions", "twilight_chart", "comet_brightnesses", "favorite_comet_events_for_month", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "planet_ephemeris", "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", "golden_gate_of_ecliptic_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", "sun_constellation_changes_for_year", "moon_star_occultations_for_month"]}) if __name__ == "__main__": diff --git a/public/sonnensternbilder.php b/public/sonnensternbilder.php new file mode 100644 index 0000000..cacaa8e --- /dev/null +++ b/public/sonnensternbilder.php @@ -0,0 +1,126 @@ +format('Y'); +$selectedYear = filter_input(INPUT_GET, 'year', FILTER_VALIDATE_INT); +if ($selectedYear === false || $selectedYear === null || $selectedYear < 1600 || $selectedYear > 2200) { + $selectedYear = $currentYear; +} + +?> + + + [$selectedYear - 1, 12, 22], + 'Wassermann' => [$selectedYear, 1, 20], + 'Fische' => [$selectedYear, 2, 19], + 'Widder' => [$selectedYear, 3, 21], + 'Stier' => [$selectedYear, 4, 20], + 'Zwillinge' => [$selectedYear, 5, 21], + 'Krebs' => [$selectedYear, 6, 21], + 'Löwe' => [$selectedYear, 7, 23], + 'Jungfrau' => [$selectedYear, 8, 23], + 'Waage' => [$selectedYear, 9, 23], + 'Skorpion' => [$selectedYear, 10, 23], + 'Schütze' => [$selectedYear, 11, 22], +]; +?> + +
+
+
+ + + +
+

+ Berechnet werden die offiziellen IAU-Sternbildgrenzen aus geozentrischer Sicht. + Der Jahreszeitraum beginnt am 01.01. um 00:00 Uhr in Europe/Berlin. + Deshalb erscheint auch der Schlangenträger; der klassische Tierkreis mit zwölf Zeichen ist nicht identisch mit den Sternbildern. +

+
+ + +
+

Berechnung nicht möglich

+

+
+ +
+

Astronomisch und astrologisch

+
+ + + + + + + + + + + + + + + getTimestamp() - $astrologicalStart->getTimestamp()) / 86400; + $differenceText = sprintf('%+.1f Tage', $differenceDays); + } + ?> + + + + + + + + + + + +
SternbildWechselAstrologischDifferenzVonBisDauer
format('d.m.Y')) ?>, Uhr, Uhr Tage
+
+
+ +
+ +