From 0059b2f78f21d31be80ab976a0a71063b5e1ce40 Mon Sep 17 00:00:00 2001 From: Jules Dejaeghere Date: Sun, 9 Jun 2024 17:53:27 +0200 Subject: [PATCH] Fetch sunrise and sunset time for daily forecast --- custom_components/irm_kmi/coordinator.py | 38 +++++++++++++++++---- custom_components/irm_kmi/data.py | 2 ++ tests/test_coordinator.py | 42 +++++++++++++++++++++++- 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/custom_components/irm_kmi/coordinator.py b/custom_components/irm_kmi/coordinator.py index ff46ed2..22d14fc 100644 --- a/custom_components/irm_kmi/coordinator.py +++ b/custom_components/irm_kmi/coordinator.py @@ -19,16 +19,18 @@ from homeassistant.util import dt from homeassistant.util.dt import utcnow from .api import IrmKmiApiClient, IrmKmiApiError -from .const import CONF_DARK_MODE, CONF_STYLE, DOMAIN, IRM_KMI_NAME, WEEKDAYS +from .const import CONF_DARK_MODE, CONF_STYLE, DOMAIN, IRM_KMI_NAME from .const import IRM_KMI_TO_HA_CONDITION_MAP as CDT_MAP from .const import MAP_WARNING_ID_TO_SLUG as SLUG_MAP -from .const import OPTION_STYLE_SATELLITE, OUT_OF_BENELUX, STYLE_TO_PARAM_MAP +from .const import (OPTION_STYLE_SATELLITE, OUT_OF_BENELUX, STYLE_TO_PARAM_MAP, + WEEKDAYS) from .data import (AnimationFrameData, CurrentWeatherData, IrmKmiForecast, IrmKmiRadarForecast, ProcessedCoordinatorData, RadarAnimationData, WarningData) from .pollen import PollenParser from .rain_graph import RainGraph -from .utils import disable_from_config, get_config_value, preferred_language, next_weekday +from .utils import (disable_from_config, get_config_value, next_weekday, + preferred_language) _LOGGER = logging.getLogger(__name__) @@ -351,14 +353,14 @@ class IrmKmiCoordinator(TimestampDataUpdateCoordinator): IrmKmiRadarForecast( datetime=f.get("time"), native_precipitation=f.get('value'), - rain_forecast_max=round(f.get('positionHigher')*ratio, 2), - rain_forecast_min=round(f.get('positionLower')*ratio, 2), + rain_forecast_max=round(f.get('positionHigher') * ratio, 2), + rain_forecast_min=round(f.get('positionLower') * ratio, 2), might_rain=f.get('positionHigher') > 0 ) ) return forecast - async def daily_list_to_forecast(self, data: List[dict] | None) -> List[Forecast] | None: + async def daily_list_to_forecast(self, data: List[dict] | None) -> List[IrmKmiForecast] | None: """Parse data from the API to create a list of daily forecasts""" if data is None or not isinstance(data, list) or len(data) == 0: return None @@ -403,6 +405,28 @@ class IrmKmiCoordinator(TimestampDataUpdateCoordinator): elif day_name == 'Tomorrow': forecast_day = dt.now(tz) + timedelta(days=1) + sunrise_sec = f.get('dawnRiseSeconds', None) + if sunrise_sec is None: + sunrise_sec = f.get('sunRise', None) + sunrise = None + if sunrise_sec is not None: + try: + sunrise = (forecast_day.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=tz) + + timedelta(seconds=float(sunrise_sec))) + except (TypeError, ValueError): + pass + + sunset_sec = f.get('dawnSetSeconds', None) + if sunset_sec is None: + sunset_sec = f.get('sunSet', None) + sunset = None + if sunset_sec is not None: + try: + sunset = (forecast_day.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=tz) + + timedelta(seconds=float(sunset_sec))) + except (TypeError, ValueError): + pass + forecast = IrmKmiForecast( datetime=(forecast_day.strftime('%Y-%m-%d')), condition=CDT_MAP.get((f.get('ww1', None), f.get('dayNight', None)), None), @@ -415,6 +439,8 @@ class IrmKmiCoordinator(TimestampDataUpdateCoordinator): wind_bearing=wind_bearing, is_daytime=is_daytime, text=f.get('text', {}).get(lang, ""), + sunrise=sunrise, + sunset=sunset ) # Swap temperature and templow if needed if (forecast['native_templow'] is not None diff --git a/custom_components/irm_kmi/data.py b/custom_components/irm_kmi/data.py index 510d2f1..b64f59e 100644 --- a/custom_components/irm_kmi/data.py +++ b/custom_components/irm_kmi/data.py @@ -10,6 +10,8 @@ class IrmKmiForecast(Forecast): # TODO: add condition_2 as well and evolution to match data from the API? text: str | None + sunrise: datetime | None + sunset: datetime | None class IrmKmiRadarForecast(Forecast): diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index 65a05d2..dd6704b 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -1,3 +1,4 @@ +import zoneinfo from datetime import datetime, timedelta from freezegun import freeze_time @@ -101,7 +102,7 @@ async def test_daily_forecast( assert len(result) == 8 assert result[0]['datetime'] == '2023-12-26' assert not result[0]['is_daytime'] - + tz = zoneinfo.ZoneInfo(key='Europe/Brussels') expected = IrmKmiForecast( datetime='2023-12-27', condition=ATTR_CONDITION_PARTLYCLOUDY, @@ -114,6 +115,8 @@ async def test_daily_forecast( wind_bearing=180, is_daytime=True, text='Bar', + sunrise=datetime.fromisoformat("2023-12-27T08:44:00+01:00").astimezone(tz), + sunset=datetime.fromisoformat("2023-12-27T16:43:00+01:00").astimezone(tz) ) assert result[1] == expected @@ -350,3 +353,40 @@ async def test_current_condition_forecast_nl() -> None: uv_index=6 ) assert result == expected + + +@freeze_time("2024-06-09T13:40:00+00:00") +async def test_sunrise_sunset_nl( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry +) -> None: + api_data = get_api_data("forecast_ams_no_ww.json").get('for', {}).get('daily') + + coordinator = IrmKmiCoordinator(hass, mock_config_entry) + result = await coordinator.daily_list_to_forecast(api_data) + + assert result[0]['sunrise'].isoformat() == '2024-06-09T05:19:28+02:00' + assert result[0]['sunset'].isoformat() == '2024-06-09T22:01:09+02:00' + + assert result[1]['sunrise'] is None + assert result[1]['sunset'] is None + + assert result[2]['sunrise'].isoformat() == '2024-06-10T05:19:08+02:00' + assert result[2]['sunset'].isoformat() == '2024-06-10T22:01:53+02:00' + + +@freeze_time("2023-12-26T18:30:00+01:00") +async def test_sunrise_sunset_be( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry +) -> None: + api_data = get_api_data("forecast.json").get('for', {}).get('daily') + + coordinator = IrmKmiCoordinator(hass, mock_config_entry) + result = await coordinator.daily_list_to_forecast(api_data) + + assert result[1]['sunrise'].isoformat() == '2023-12-27T08:44:00+01:00' + assert result[1]['sunset'].isoformat() == '2023-12-27T16:43:00+01:00' + + assert result[2]['sunrise'].isoformat() == '2023-12-28T08:45:00+01:00' + assert result[2]['sunset'].isoformat() == '2023-12-28T16:43:00+01:00'