diff --git a/homeassistant/components/sun/condition.py b/homeassistant/components/sun/condition.py index 1c5378139857..681b6be82530 100644 --- a/homeassistant/components/sun/condition.py +++ b/homeassistant/components/sun/condition.py @@ -85,34 +85,18 @@ def sun( has_sunrise_condition = SUN_EVENT_SUNRISE in (before, after) has_sunset_condition = SUN_EVENT_SUNSET in (before, after) - after_sunrise = today > dt_util.as_local(cast(datetime, sunrise)).date() + after_sunrise = sunrise is not None and today > dt_util.as_local(sunrise).date() if after_sunrise and has_sunrise_condition: tomorrow = today + timedelta(days=1) sunrise = get_astral_event_date(hass, SUN_EVENT_SUNRISE, tomorrow) - after_sunset = today > dt_util.as_local(cast(datetime, sunset)).date() + after_sunset = sunset is not None and today > dt_util.as_local(sunset).date() if after_sunset and has_sunset_condition: tomorrow = today + timedelta(days=1) sunset = get_astral_event_date(hass, SUN_EVENT_SUNSET, tomorrow) - # Special case: before sunrise OR after sunset - # This will handle the very rare case in the polar region when the sun rises/sets - # but does not set/rise. - # However this entire condition does not handle those full days of darkness - # or light, the following should be used instead: - # - # condition: - # condition: state - # entity_id: sun.sun - # state: 'above_horizon' (or 'below_horizon') - # - if before == SUN_EVENT_SUNRISE and after == SUN_EVENT_SUNSET: - wanted_time_before = cast(datetime, sunrise) + before_offset - condition_trace_update_result(wanted_time_before=wanted_time_before) - wanted_time_after = cast(datetime, sunset) + after_offset - condition_trace_update_result(wanted_time_after=wanted_time_after) - return utcnow < wanted_time_before or utcnow > wanted_time_after - + # A missing sunrise/sunset means the sun doesn't rise/set on this day, which + # happens in polar regions. if sunrise is None and has_sunrise_condition: # There is no sunrise today condition_trace_set_result(False, message="no sunrise today") @@ -123,6 +107,16 @@ def sun( condition_trace_set_result(False, message="no sunset today") return False + # "before: sunrise" combined with "after: sunset" describes the dark period + # around midnight, so it is evaluated as an OR (true before sunrise or after + # sunset) rather than the usual AND of the two bounds. + if before == SUN_EVENT_SUNRISE and after == SUN_EVENT_SUNSET: + wanted_time_before = cast(datetime, sunrise) + before_offset + condition_trace_update_result(wanted_time_before=wanted_time_before) + wanted_time_after = cast(datetime, sunset) + after_offset + condition_trace_update_result(wanted_time_after=wanted_time_after) + return utcnow < wanted_time_before or utcnow > wanted_time_after + if before == SUN_EVENT_SUNRISE: wanted_time_before = cast(datetime, sunrise) + before_offset condition_trace_update_result(wanted_time_before=wanted_time_before) diff --git a/tests/components/sun/test_condition.py b/tests/components/sun/test_condition.py index b7e4bea7f680..95504e040d42 100644 --- a/tests/components/sun/test_condition.py +++ b/tests/components/sun/test_condition.py @@ -85,16 +85,6 @@ async def assert_automation_condition_trace(hass_ws_client, automation_id, expec assert condition_trace["result"] == expected -async def assert_automation_condition_trace_error( - hass_ws_client, automation_id, expected -): - """Test the error of automation condition.""" - condition_trace = await _get_automation_condition_trace( - hass_ws_client, automation_id - ) - assert condition_trace["error"] == expected - - async def test_if_action_before_sunrise_no_offset( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, @@ -1299,10 +1289,9 @@ async def test_if_action_no_sun_event_in_polar_regions( """Test a sun condition where the requested event never occurs. During midnight sun and polar night the sun neither rises nor sets, so - ``get_astral_event_date`` returns None for both events. This documents the - legacy condition crashing on the missing event (it passes None to - ``dt_util.as_local``); the crash fix and the matching "no sunrise/sunset - today" results are a follow-up. + ``get_astral_event_date`` returns None for the requested event. The + condition cannot be satisfied and reports "no sunrise today" / "no sunset + today" instead of raising. """ latitude, longitude, time_zone = location await hass.config.async_set_time_zone(time_zone) @@ -1328,10 +1317,10 @@ async def test_if_action_no_sun_event_in_polar_regions( hass.bus.async_fire("test_event") await hass.async_block_till_done() assert len(service_calls) == 0 - await assert_automation_condition_trace_error( + await assert_automation_condition_trace( hass_ws_client, "sun", - "'NoneType' object has no attribute 'tzinfo'", + {"result": False, "message": f"no {event} today"}, )