From 5247516cde014dcdfd3c1cf1830098754a534eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Deuchnord?= Date: Wed, 14 Jan 2026 17:34:17 +0100 Subject: [PATCH] feat: add options to localize seasons --- kosmorrolib/enum.py | 55 ++++++++++++++++++++++++++++ kosmorrolib/events.py | 85 +++++++++++++++++++++++++------------------ tests.py | 2 +- 3 files changed, 105 insertions(+), 37 deletions(-) diff --git a/kosmorrolib/enum.py b/kosmorrolib/enum.py index f9debde..a9a7eb4 100644 --- a/kosmorrolib/enum.py +++ b/kosmorrolib/enum.py @@ -38,6 +38,61 @@ class SeasonType(Enum): SEPTEMBER_EQUINOX = 2 DECEMBER_SOLSTICE = 3 + def localize(self, position: Position) -> LocalizedSeasonType: + """Return the local season that corresponds to the given position's latitude. + + Args: + season (SeasonType): The season to localize. + position (Position): The position to localize the season for. + + Returns: + SeasonType: The localized season. + + Raises: + ValueError: If the latitude is 0 (equator). + + ) + + >>> from kosmorrolib import Position + >>> SeasonType.MARCH_EQUINOX.localize(Position(0, 1)) + Traceback (most recent call last): + ... + ValueError: Cannot localize seasons for this latitude. + + >>> SeasonType.MARCH_EQUINOX.localize(Position(1, 1)) + + + >>> SeasonType.MARCH_EQUINOX.localize(Position(-1, 1)) + + """ + if position.latitude == 0: # Equator + raise ValueError("Cannot localize seasons for this latitude.") + + if position.latitude < 0: # Southern hemisphere + seasons = { + self.MARCH_EQUINOX: LocalizedSeasonType.AUTUMN, + self.JUNE_SOLSTICE: LocalizedSeasonType.WINTER, + self.SEPTEMBER_EQUINOX: LocalizedSeasonType.SPRING, + self.DECEMBER_SOLSTICE: LocalizedSeasonType.SUMMER, + } + + else: # Nothern hemisphere + seasons = { + self.MARCH_EQUINOX: LocalizedSeasonType.SPRING, + self.JUNE_SOLSTICE: LocalizedSeasonType.SUMMER, + self.SEPTEMBER_EQUINOX: LocalizedSeasonType.AUTUMN, + self.DECEMBER_SOLSTICE: LocalizedSeasonType.WINTER, + } + + return seasons[self] + + +class LocalizedSeasonType(Enum): + WINTER = 0 + SPRING = 1 + SUMMER = 2 + AUTUMN = 3 + class EventType(Enum): """An enumeration for the supported event types.""" diff --git a/kosmorrolib/events.py b/kosmorrolib/events.py index 8b7fa61..e7c51f4 100644 --- a/kosmorrolib/events.py +++ b/kosmorrolib/events.py @@ -29,6 +29,7 @@ from kosmorrolib.model import ( Event, Object, + Position, Star, Planet, get_aster, @@ -360,43 +361,50 @@ def f(start_time: Time, end_time: Time, utc_offset: Union[int, float]) -> [Event return f -def _search_earth_season_change( - start_time: Time, end_time: Time, utc_offset: Union[int, float] -) -> [Event]: - """Function to find earth season change event. +def _search_earth_season_change(position: Position | None): + def f(start_time: Time, end_time: Time, utc_offset: int) -> [Event]: + """Function to find earth season change event. - **Warning:** this is an internal function, not intended for use by end-developers. + **Warning:** this is an internal function, not intended for use by end-developers. - Will return JUNE SOLSTICE on 2020/06/20: + Will return JUNE SOLSTICE on 2020/06/20: - >>> season_change = _search_earth_season_change(get_timescale().utc(2020, 6, 20), get_timescale().utc(2020, 6, 21), 0) - >>> len(season_change) - 1 - >>> season_change[0].event_type - - >>> season_change[0].details - {'season': } + >>> season_change = _search_earth_season_change(get_timescale().utc(2020, 6, 20), get_timescale().utc(2020, 6, 21), 0) + >>> len(season_change) + 1 + >>> season_change[0].event_type + + >>> season_change[0].details + {'season': } - Will return nothing if there is no season change event in the period of time being calculated: + Will return nothing if there is no season change event in the period of time being calculated: - >>> _search_earth_season_change(get_timescale().utc(2021, 6, 17), get_timescale().utc(2021, 6, 18), 0) - [] - """ - events = [] - event_time, event_id = almanac.find_discrete( - start_time, end_time, almanac.seasons(get_skf_objects()) - ) - if len(event_time) == 0: - return [] - events.append( - Event( - EventType.SEASON_CHANGE, - [], - translate_to_utc_offset(event_time.utc_datetime()[0], utc_offset), - details={"season": SeasonType(event_id[0])}, + >>> _search_earth_season_change(get_timescale().utc(2021, 6, 17), get_timescale().utc(2021, 6, 18), 0) + [] + """ + events = [] + event_time, event_id = almanac.find_discrete( + start_time, end_time, almanac.seasons(get_skf_objects()) ) - ) - return events + if len(event_time) == 0: + return [] + + season = SeasonType(event_id[0]) + + if position is not None: + season = season.localize(position) + + events.append( + Event( + EventType.SEASON_CHANGE, + [], + translate_to_timezone(event_time.utc_datetime()[0], timezone), + details={"season": season}, + ) + ) + return events + + return f def _search_lunar_eclipse( @@ -476,9 +484,12 @@ def is_in_penumbra(time: Time): def get_events( - for_date: date = date.today(), utc_offset: Union[int, float] = 0, **argv + for_date: date = date.today(), + utc_offset: Union[int, float] = 0, + position: Position | None = None, + **argv ) -> [Event]: - """Calculate and return a list of events for the given date, adjusted to the given UTC offset if any. + """Calculate and return a list of events for the given date, adjusted to the given timezone if any. Find events that happen on April 4th, 2020 (show hours in UTC): @@ -514,7 +525,8 @@ def get_events( kosmorrolib.exceptions.OutOfRangeDateError: The date must be between 1899-07-28 and 2053-10-08 :param for_date: the date for which the events must be calculated - :param utc_offset: the UTC offset to adapt the results to. If not given, defaults to 0. + :param utc_offset: the timezone to adapt the results to. If not given, defaults to 0. + :param position: the position to localize the events to. :return: a list of events found for the given date. """ @@ -542,7 +554,7 @@ def get_events( _search_perigee(ASTERS[1]), _search_apogee(EARTH, from_aster=ASTERS[0]), _search_perigee(EARTH, from_aster=ASTERS[0]), - _search_earth_season_change, + _search_earth_season_change(position), _search_lunar_eclipse, ]: found_events.append(fun(start_time, end_time, utc_offset)) @@ -565,6 +577,7 @@ def search_events( end: date, start: date = date.today(), utc_offset: Union[int, float] = 0, + position: Position | None = None, ) -> [Event]: """Search between `start` and `end` dates, and return a list of matching events for the given time range, adjusted to a given UTC offset. @@ -645,7 +658,7 @@ def _search_all_perigee_events( EventType.MAXIMAL_ELONGATION: _search_maximal_elongations, EventType.APOGEE: _search_all_apogee_events, EventType.PERIGEE: _search_all_perigee_events, - EventType.SEASON_CHANGE: _search_earth_season_change, + EventType.SEASON_CHANGE: _search_earth_season_change(position), EventType.LUNAR_ECLIPSE: _search_lunar_eclipse, } diff --git a/tests.py b/tests.py index 22ee4a3..4796ef2 100644 --- a/tests.py +++ b/tests.py @@ -9,7 +9,7 @@ failures = 0 tests = 0 - for module in [events, ephemerides, model]: + for module in [events, ephemerides, model, enum]: (f, t) = testmod(module, optionflags=NORMALIZE_WHITESPACE) failures += f tests += t