planen die Geburtstagsfeierlichkeiten für drei Freunde im nächsten Jahr: Gabriel, Jacques und Camille. Alle drei wurden 1996 in Paris, Frankreich, geboren und werden nächstes Jahr im Jahr 2026 30 Jahre alt. Gabriel und Jacques werden an ihrem jeweiligen Geburtstag zufällig in Paris sein, während Camille an ihrem Geburtstag in Tokio, Japan, sein wird. Gabriel und Camille feiern ihre Geburtstage in der Regel jedes Jahr an den „offiziellen“ Tagen, die in ihren Geburtsurkunden angegeben sind – dem 18. Januar bzw. dem 5. Mai. Jacques, der am 29. Februar geboren wurde, feiert am liebsten seinen Geburtstag (bzw Bürgerjubiläum) am 1. März in Nicht-Schaltjahren.
Wir verwenden Schaltjahre um unseren Kalender mit der Umlaufbahn der Erde um die Sonne synchron zu halten. A Sonnenjahr – die Zeit, die die Erde für eine vollständige Umrundung der Sonne benötigt – beträgt etwa 365,25 Tage. Konventionell weist der Gregorianische Kalender jedem Jahr 365 Tage zu, mit Ausnahme von Schaltjahren, die 366 Tage erhalten, um die geringfügige Verschiebung im Laufe der Zeit auszugleichen. Da fragen Sie sich: Wird einer Ihrer Freunde seinen Geburtstag am „echten“ Jahrestag seines Geburtstages feiern, d. Könnte es sein, dass Ihre Freunde am Ende ihren 30. Geburtstag – einen besonderen Meilenstein – einen Tag zu früh oder einen Tag zu spät feiern?
Der folgende Artikel nutzt dieses Geburtstagsproblem, um den Lesern einige interessante und breit anwendbare Open-Supply-Knowledge-Science-Python-Pakete für astronomische Berechnungen und geospatial-zeitliche Analysen vorzustellen, darunter skyfield, timezonefinder, geopyUnd pytz. Um praktische Erfahrungen zu sammeln, werden wir diese Pakete verwenden, um unser lustiges Drawback zu lösen, den „echten Geburtstag“ (oder das Datum von) genau vorherzusagen Solarertrag) in einem bestimmten zukünftigen Jahr. Anschließend besprechen wir, wie solche Pakete in anderen realen Anwendungen genutzt werden können.
Echter Geburtstagsvorhersager
Projekt-Setup
Alle folgenden Implementierungsschritte wurden auf macOS Sequoia 15.6.1 getestet und sollten unter Linux und Home windows ungefähr ähnlich sein.
Beginnen wir mit der Einrichtung des Projektverzeichnisses. Wir werden verwenden uv um das Projekt zu verwalten (siehe Installationsanleitung). Hier). Überprüfen Sie die installierte Model im Terminal:
uv --version
Initialisieren Sie ein Projektverzeichnis mit dem Namen real-birthday-predictor an einem geeigneten Ort auf Ihrem lokalen Rechner:
uv init --bare real-birthday-predictor
Erstellen Sie im Projektverzeichnis eine necessities.txt Datei mit den folgenden Abhängigkeiten:
skyfield==1.53
timezonefinder==8.0.0
geopy==2.4.1
pytz==2025.2
Hier ist eine kurze Übersicht über jedes dieser Pakete:
skyfieldstellt Funktionen für astronomische Berechnungen bereit. Es kann verwendet werden, um genaue Positionen von Himmelskörpern (z. B. Sonne, Mond, Planeten und Satelliten) zu berechnen und so Auf-/Untergangszeiten, Finsternisse und Umlaufbahnen zu bestimmen. Es setzt auf sog Ephemeriden (über viele Jahre extrapolierte Tabellen mit Positionsdaten verschiedener Himmelskörper), die von Organisationen wie dem NASA Jet Propulsion Laboratory (JPL) gepflegt werden. Für diesen Artikel verwenden wir die kompakte Ephemeridendatei DE421, die Daten vom 29. Juli 1899 bis zum 9. Oktober 2053 abdeckt.timezonefinderverfügt über Funktionen zur Zuordnung geografischer Koordinaten (Breiten- und Längengrade) zu Zeitzonen (z. B. „Europa/Paris“). Dies ist offline möglich.geopybietet Funktionen für Geodatenanalysen, wie z. B. die Zuordnung zwischen Adressen und geografischen Koordinaten. Wir werden es zusammen mit dem verwendenNominatimGeocoder für OpenStreetMap-Daten, um die Namen von Städten und Ländern auf Koordinaten abzubilden.pytzstellt Funktionen zur zeitlichen Analyse und Zeitzonenkonvertierung bereit. Wir werden es verwenden, um mithilfe regionaler Sommerzeitregeln zwischen UTC und Ortszeit umzurechnen.
Wir werden auch einige andere integrierte Module verwenden, wie z datetime zum Analysieren und Bearbeiten von Datums-/Uhrzeitwerten, calendar zur Überprüfung von Schaltjahren und time zum Schlafen zwischen Geokodierungswiederholungsversuchen.
Erstellen Sie als Nächstes eine virtuelle Python 3.12-Umgebung im Projektverzeichnis, aktivieren Sie die Umgebung und installieren Sie die Abhängigkeiten:
uv venv --python=3.12
supply .venv/bin/activate
uv add -r necessities.txt
Überprüfen Sie, ob die Abhängigkeiten installiert wurden:
uv pip checklist
Durchführung
In diesem Abschnitt gehen wir Stück für Stück durch den Code zur Vorhersage des „echten“ Geburtstagsdatums und der „echten“ Geburtstagszeit in einem bestimmten zukünftigen Jahr und Ort der Feier. Zuerst importieren wir die notwendigen Module:
from datetime import datetime, timedelta
from skyfield.api import load, wgs84
from timezonefinder import TimezoneFinder
from geopy.geocoders import Nominatim
from geopy.exc import GeocoderTimedOut
import pytz
import calendar
import time
Dann definieren wir die Methode unter Verwendung aussagekräftiger Variablennamen und Dokumentzeichenfolgentext:
def get_real_birthday_prediction(
official_birthday: str,
official_birth_time: str,
birth_country: str,
birth_city: str,
current_country: str,
current_city: str,
target_year: str = None
):
"""
Predicts the "actual" birthday (photo voltaic return) for a given 12 months,
accounting for the time zone on the beginning location and the time zone
on the present location. Makes use of March 1 in non-leap years for the civil
anniversary if the official beginning date is February 29.
"""
Beachten Sie, dass current_country Und current_city beziehen sich gemeinsam auf den Ort, an dem der Geburtstag im Zieljahr gefeiert werden soll.
Wir validieren die Eingaben, bevor wir mit ihnen arbeiten:
# Decide goal 12 months
if target_year is None:
target_year = datetime.now().12 months
else:
attempt:
target_year = int(target_year)
besides ValueError:
increase ValueError(f"Invalid goal 12 months '{target_year}'. Please use 'yyyy' format.")
# Validate and parse beginning date
attempt:
birth_date = datetime.strptime(official_birthday, "%d-%m-%Y")
besides ValueError:
increase ValueError(
f"Invalid beginning date '{official_birthday}'. "
"Please use 'dd-mm-yyyy' format with a sound calendar date."
)
# Validate and parse beginning time
attempt:
birth_hour, birth_minute = map(int, official_birth_time.break up(":"))
besides ValueError:
increase ValueError(
f"Invalid beginning time '{official_birth_time}'. "
"Please use 'hh:mm' 24-hour format."
)
if not (0 <= birth_hour <= 23):
increase ValueError(f"Hour '{birth_hour}' is out of vary (0-23).")
if not (0 <= birth_minute <= 59):
increase ValueError(f"Minute '{birth_minute}' is out of vary (0-59).")
Als nächstes verwenden wir geopy mit dem Nominatim Geocoder zur Ermittlung des Geburtsorts und des aktuellen Standorts. Um Zeitüberschreitungsfehler zu vermeiden, legen wir einen angemessen langen Zeitüberschreitungswert von zehn Sekunden fest; So lange dauert unser safe_geocode Die Funktion wartet auf die Antwort des Geokodierungsdienstes, bevor sie a auslöst geopy.exc.GeocoderTimedOut Ausnahme. Um besonders sicher zu sein, versucht die Funktion den Suchvorgang dreimal mit einer Verzögerung von einer Sekunde, bevor sie aufgibt:
geolocator = Nominatim(user_agent="birthday_tz_lookup", timeout=10)
# Helper perform to name geocode API with retries
def safe_geocode(question, retries=3, delay=1):
for try in vary(retries):
attempt:
return geolocator.geocode(question)
besides GeocoderTimedOut:
if try < retries - 1:
time.sleep(delay)
else:
increase RuntimeError(
f"Couldn't retrieve location for '{question}' after {retries} makes an attempt. "
"The geocoding service could also be sluggish or unavailable. Please attempt once more later."
)
birth_location = safe_geocode(f"{birth_city}, {birth_country}")
current_location = safe_geocode(f"{current_city}, {current_country}")
if not birth_location or not current_location:
increase ValueError("Couldn't discover coordinates for one of many areas. Please test spelling.")
Anhand der geografischen Koordinaten der Geburt und der aktuellen Standorte ermitteln wir die jeweiligen Zeitzonen sowie das UTC-Datum und die Geburtszeit. Wir gehen auch davon aus, dass Personen wie Jacques, die am 29. Februar geboren wurden, ihren Geburtstag in Nicht-Schaltjahren lieber am 1. März feiern werden:
# Get time zones
tf = TimezoneFinder()
birth_tz_name = tf.timezone_at(lng=birth_location.longitude, lat=birth_location.latitude)
current_tz_name = tf.timezone_at(lng=current_location.longitude, lat=current_location.latitude)
if not birth_tz_name or not current_tz_name:
increase ValueError("Couldn't decide timezone for one of many areas.")
birth_tz = pytz.timezone(birth_tz_name)
current_tz = pytz.timezone(current_tz_name)
# Set civil anniversary date to March 1 for February 29 birthdays in non-leap years
birth_month, birth_day = birth_date.month, birth_date.day
if (birth_month, birth_day) == (2, 29):
if not calendar.isleap(birth_date.12 months):
increase ValueError(f"{birth_date.12 months} shouldn't be a intercalary year, so February 29 is invalid.")
civil_anniversary_month, civil_anniversary_day = (
(3, 1) if not calendar.isleap(target_year) else (2, 29)
)
else:
civil_anniversary_month, civil_anniversary_day = birth_month, birth_day
# Parse beginning datetime in beginning location's native time
birth_local_dt = birth_tz.localize(datetime(
birth_date.12 months, birth_month, birth_day,
birth_hour, birth_minute
))
birth_dt_utc = birth_local_dt.astimezone(pytz.utc)
Anhand der DE421-Ephemeridendaten berechnen wir, wo sich die Sonne befand (d. h., wo sie warfare). ekliptischer Längengrad) zum genauen Zeitpunkt und Ort, an dem die Individual geboren wurde:
# Load ephemeris knowledge and get Solar's ecliptic longitude at beginning
eph = load("de421.bsp") # Covers dates 1899-07-29 by means of 2053-10-09
ts = load.timescale()
solar = eph("solar")
earth = eph("earth")
t_birth = ts.utc(birth_dt_utc.12 months, birth_dt_utc.month, birth_dt_utc.day,
birth_dt_utc.hour, birth_dt_utc.minute, birth_dt_utc.second)
# Delivery longitude in tropical body from POV of beginning observer on Earth's floor
birth_observer = earth + wgs84.latlon(birth_location.latitude, birth_location.longitude)
ecl = birth_observer.at(t_birth).observe(solar).obvious().ecliptic_latlon(epoch='date')
birth_longitude = ecl(1).levels
Beachten Sie, dass beim ersten Mal die Zeile angezeigt wird eph = load("de421.bsp") ausgeführt wird, die de421.bsp Die Datei wird heruntergeladen und im Projektverzeichnis abgelegt. Bei allen zukünftigen Ausführungen wird die heruntergeladene Datei direkt verwendet. Es ist auch möglich, den Code zu ändern, um eine andere Ephemeridendatei zu laden (z. B. de440s.bspdas die Jahre bis zum 22. Januar 2150 abdeckt).
Jetzt kommt ein interessanter Teil der Funktion: Wir werden zunächst das „echte“ Geburtstagsdatum und die „echte“ Geburtstagszeit im Zieljahr schätzen, sichere Ober- und Untergrenzen für den wahren Datums- und Uhrzeitwert definieren (z. B. zwei Tage auf beiden Seiten der ursprünglichen Schätzung) und eine binäre Suche mit Frühstopp durchführen, um den wahren Wert effizient zu ermitteln:
# Preliminary guess for goal 12 months photo voltaic return
approx_dt_local_birth_tz = birth_tz.localize(datetime(
target_year, civil_anniversary_month, civil_anniversary_day,
birth_hour, birth_minute
))
approx_dt_utc = approx_dt_local_birth_tz.astimezone(pytz.utc)
# Compute Solar longitude from POV of present observer on Earth's floor
current_observer = earth + wgs84.latlon(current_location.latitude, current_location.longitude)
def sun_longitude_at(dt):
t = ts.utc(dt.12 months, dt.month, dt.day, dt.hour, dt.minute, dt.second)
ecl = current_observer.at
return ecl(1).levels
def angle_diff(a, b):
return (a - b + 180) % 360 - 180
# Set secure higher and decrease bounds for search area
dt1 = approx_dt_utc - timedelta(days=2)
dt2 = approx_dt_utc + timedelta(days=2)
# Use binary search with early-stopping to resolve for precise photo voltaic return in UTC
old_angle_diff = 999
for _ in vary(50):
mid = dt1 + (dt2 - dt1) / 2
curr_angle_diff = angle_diff(sun_longitude_at(mid), birth_longitude)
if old_angle_diff == curr_angle_diff: # Early-stopping situation
break
if curr_angle_diff > 0:
dt2 = mid
else:
dt1 = mid
old_angle_diff = curr_angle_diff
real_dt_utc = dt1 + (dt2 - dt1) / 2
Sehen Das In diesem Artikel finden Sie weitere Beispiele für die Verwendung der binären Suche und erfahren, warum die Beherrschung dieses Algorithmus für Datenwissenschaftler wichtig ist.
Schließlich werden Datum und Uhrzeit des „echten“ Geburtstags, der durch die binäre Suche identifiziert wurde, in die Zeitzone des aktuellen Standorts konvertiert, nach Bedarf formatiert und zurückgegeben:
# Convert to present location's native time and format output
real_dt_local_current = real_dt_utc.astimezone(current_tz)
date_str = real_dt_local_current.strftime("%d/%m")
time_str = real_dt_local_current.strftime("%H:%M")
return date_str, time_str, current_tz_name
Testen
Jetzt sind wir in der Lage, die „echten“ Geburtstage von Gabriel, Jacques und Camille im Jahr 2026 vorherzusagen.
Um die Funktionsausgabe leichter verständlich zu machen, finden Sie hier eine Hilfsfunktion, mit der wir die Ergebnisse jeder Abfrage hübsch ausdrucken:
def print_real_birthday(
official_birthday: str,
official_birth_time: str,
birth_country: str,
birth_city: str,
current_country: str,
current_city: str,
target_year: str = None):
"""Fairly-print output whereas hiding verbose error traces."""
print("Official birthday and time:", official_birthday, "at", official_birth_time)
attempt:
date_str, time_str, current_tz_name = get_real_birthday_prediction(
official_birthday,
official_birth_time,
birth_country,
birth_city,
current_country,
current_city,
target_year
)
print(f"In 12 months {target_year}, your actual birthday is on {date_str} at {time_str} ({current_tz_name})n")
besides ValueError as e:
print("Error:", e)
Hier sind die Testfälle:
# Gabriel
print_real_birthday(
official_birthday="18-01-1996",
official_birth_time="02:30",
birth_country="France",
birth_city="Paris",
current_country="France",
current_city="Paris",
target_year="2026"
)
# Jacques
print_real_birthday(
official_birthday="29-02-1996",
official_birth_time="05:45",
birth_country="France",
birth_city="Paris",
current_country="France",
current_city="Paris",
target_year="2026"
)
# Camille
print_real_birthday(
official_birthday="05-05-1996",
official_birth_time="20:30",
birth_country="Paris",
birth_city="France",
current_country="Japan",
current_city="Tokyo",
target_year="2026"
)
Und hier sind die Ergebnisse:
Official birthday and time: 18-01-1996 at 02:30
In 12 months 2026, your actual birthday is on 17/01 at 09:21 (Europe/Paris)
Official birthday and time: 29-02-1996 at 05:45
In 12 months 2026, your actual birthday is on 28/02 at 12:37 (Europe/Paris)
Official birthday and time: 05-05-1996 at 20:30
In 12 months 2026, your actual birthday is on 06/05 at 09:48 (Asia/Tokyo)
Wie wir sehen, unterscheidet sich der „echte“ Geburtstag (oder der Second der Sonnenwiederkehr) vom offiziellen Geburtstag aller drei Ihrer Freunde: Gabriel und Jacques könnten theoretisch einen Tag vor ihrem offiziellen Geburtstag in Paris mit dem Feiern beginnen, während Camille noch einen Tag warten sollte, bevor sie in Tokio ihren 30. Geburtstag feiert.
Als einfachere Different zu den oben genannten Schritten hat der Autor dieses Artikels eine Python-Bibliothek namens erstellt solarius um das gleiche Ergebnis zu erzielen (siehe Particulars Hier). Installieren Sie die Bibliothek mit pip set up solarius oder uv add solarius und verwenden Sie es wie unten gezeigt:
from solarius.mannequin import SolarReturnCalculator
calculator = SolarReturnCalculator(ephemeris_file="de421.bsp")
# Predict with out printing
date_str, time_str, tz_name = calculator.predict(
official_birthday="18-01-1996",
official_birth_time="02:30",
birth_country="France",
birth_city="Paris",
current_country="France",
current_city="Paris",
target_year="2026"
)
print(date_str, time_str, tz_name)
# Or use the comfort printer
calculator.print_real_birthday(
official_birthday="18-01-1996",
official_birth_time="02:30",
birth_country="France",
birth_city="Paris",
current_country="France",
current_city="Paris",
target_year="2026"
)
Natürlich geht es bei Geburtstagen um mehr als nur um die Vorhersage der Sonnenrendite – diese besonderen Tage haben eine jahrhundertealte Custom. Hier ist ein kurzes Video über die faszinierenden Ursprünge von Geburtstagen:
Jenseits von Geburtstagen
Die Absicht des obigen Abschnitts bestand darin, den Lesern einen unterhaltsamen und intuitiven Anwendungsfall für die Anwendung der verschiedenen Pakete für astronomische Berechnungen und georäumlich-zeitliche Analysen zu bieten. Der Nutzen solcher Pakete geht jedoch weit über die Vorhersage von Geburtstagen hinaus.
Alle diese Pakete können beispielsweise für andere Fälle der Vorhersage astronomischer Ereignisse verwendet werden (z. B. um zu bestimmen, wann ein Sonnenaufgang, ein Sonnenuntergang oder eine Sonnenfinsternis an einem zukünftigen Datum an einem bestimmten Ort stattfinden wird). Auch die Vorhersage der Bewegung von Satelliten und anderen Himmelskörpern könnte eine wichtige Rolle bei der Planung von Weltraummissionen spielen.
Die Pakete könnten auch verwendet werden, um den Einsatz von Solarmodulen an einem bestimmten Standort zu optimieren, beispielsweise in einem Wohnviertel oder einem Gewerbegebiet. Das Ziel besteht darin, vorherzusagen, wie viel Sonnenlicht zu verschiedenen Jahreszeiten voraussichtlich auf diesen Standort fallen wird, und dieses Wissen zu nutzen, um die Platzierung, Neigung und Nutzungspläne der Solarmodule für eine maximale Energiegewinnung anzupassen.
Schließlich können die Pakete für die Rekonstruktion historischer Ereignisse genutzt werden (z. B. im Kontext archäologischer oder historischer Forschung oder sogar der Rechtsforensik). Das Ziel hier wäre, die Himmelsbedingungen für ein bestimmtes Datum und einen bestimmten Ort in der Vergangenheit nachzubilden, um den Forschern zu helfen, die Licht- und Sichtverhältnisse zu diesem Zeitpunkt besser zu verstehen.
Letztendlich ist es durch die Kombination dieser Open-Supply-Pakete und integrierten Module auf verschiedene Weise möglich, interessante Probleme zu lösen, die sich über mehrere Domänen erstrecken.
