diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index a628cdefff4..387d2ad09b0 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -586,31 +586,46 @@ def sun( before_offset = before_offset or timedelta(0) after_offset = after_offset or timedelta(0) - sunrise_today = get_astral_event_date(hass, SUN_EVENT_SUNRISE, today) - sunset_today = get_astral_event_date(hass, SUN_EVENT_SUNSET, today) + sunrise = get_astral_event_date(hass, SUN_EVENT_SUNRISE, today) + sunset = get_astral_event_date(hass, SUN_EVENT_SUNSET, today) - sunrise = sunrise_today - sunset = sunset_today - if today > dt_util.as_local( - cast(datetime, sunrise_today) - ).date() and SUN_EVENT_SUNRISE in (before, after): - tomorrow = dt_util.as_local(utcnow + timedelta(days=1)).date() - sunrise_tomorrow = get_astral_event_date(hass, SUN_EVENT_SUNRISE, tomorrow) - sunrise = sunrise_tomorrow + has_sunrise_condition = SUN_EVENT_SUNRISE in (before, after) + has_sunset_condition = SUN_EVENT_SUNSET in (before, after) - if today > dt_util.as_local( - cast(datetime, sunset_today) - ).date() and SUN_EVENT_SUNSET in (before, after): - tomorrow = dt_util.as_local(utcnow + timedelta(days=1)).date() - sunset_tomorrow = get_astral_event_date(hass, SUN_EVENT_SUNSET, tomorrow) - sunset = sunset_tomorrow + after_sunrise = today > dt_util.as_local(cast(datetime, sunrise)).date() + if after_sunrise and has_sunrise_condition: + tomorrow = today + timedelta(days=1) + sunrise = get_astral_event_date(hass, SUN_EVENT_SUNRISE, tomorrow) - if sunrise is None and SUN_EVENT_SUNRISE in (before, after): + after_sunset = today > dt_util.as_local(cast(datetime, 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 + + if sunrise is None and has_sunrise_condition: # There is no sunrise today condition_trace_set_result(False, message="no sunrise today") return False - if sunset is None and SUN_EVENT_SUNSET in (before, after): + if sunset is None and has_sunset_condition: # There is no sunset today condition_trace_set_result(False, message="no sunset today") return False diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index b7e4caf68c7..e3189955912 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -2735,9 +2735,9 @@ async def test_if_action_after_sunset_with_offset(hass, hass_ws_client, calls): ) -async def test_if_action_before_and_after_during(hass, hass_ws_client, calls): +async def test_if_action_after_and_before_during(hass, hass_ws_client, calls): """ - Test if action was after sunset and before sunrise. + Test if action was after sunrise and before sunset. This is true from sunrise until sunset. """ @@ -2837,6 +2837,128 @@ async def test_if_action_before_and_after_during(hass, hass_ws_client, calls): ) +async def test_if_action_before_or_after_during(hass, hass_ws_client, calls): + """ + Test if action was before sunrise or after sunset. + + This is true from midnight until sunrise and from sunset until midnight + """ + await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "condition": { + "condition": "sun", + "before": SUN_EVENT_SUNRISE, + "after": SUN_EVENT_SUNSET, + }, + "action": {"service": "test.automation"}, + } + }, + ) + + # sunrise: 2015-09-16 06:33:18 local, sunset: 2015-09-16 18:53:45 local + # sunrise: 2015-09-16 13:33:18 UTC, sunset: 2015-09-17 01:53:45 UTC + # now = sunrise - 1s -> 'before sunrise' | 'after sunset' true + now = datetime(2015, 9, 16, 13, 33, 17, tzinfo=dt_util.UTC) + with patch("homeassistant.util.dt.utcnow", return_value=now): + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + assert len(calls) == 1 + await assert_automation_condition_trace( + hass_ws_client, + "sun", + { + "result": True, + "wanted_time_after": "2015-09-17T01:53:44.723614+00:00", + "wanted_time_before": "2015-09-16T13:33:18.342542+00:00", + }, + ) + + # now = sunset + 1s -> 'before sunrise' | 'after sunset' true + now = datetime(2015, 9, 17, 1, 53, 46, tzinfo=dt_util.UTC) + with patch("homeassistant.util.dt.utcnow", return_value=now): + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + assert len(calls) == 2 + await assert_automation_condition_trace( + hass_ws_client, + "sun", + { + "result": True, + "wanted_time_after": "2015-09-17T01:53:44.723614+00:00", + "wanted_time_before": "2015-09-16T13:33:18.342542+00:00", + }, + ) + + # now = sunrise + 1s -> 'before sunrise' | 'after sunset' false + now = datetime(2015, 9, 16, 13, 33, 19, tzinfo=dt_util.UTC) + with patch("homeassistant.util.dt.utcnow", return_value=now): + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + assert len(calls) == 2 + await assert_automation_condition_trace( + hass_ws_client, + "sun", + { + "result": False, + "wanted_time_after": "2015-09-17T01:53:44.723614+00:00", + "wanted_time_before": "2015-09-16T13:33:18.342542+00:00", + }, + ) + + # now = sunset - 1s -> 'before sunrise' | 'after sunset' false + now = datetime(2015, 9, 17, 1, 53, 44, tzinfo=dt_util.UTC) + with patch("homeassistant.util.dt.utcnow", return_value=now): + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + assert len(calls) == 2 + await assert_automation_condition_trace( + hass_ws_client, + "sun", + { + "result": False, + "wanted_time_after": "2015-09-17T01:53:44.723614+00:00", + "wanted_time_before": "2015-09-16T13:33:18.342542+00:00", + }, + ) + + # now = midnight + 1s local -> 'before sunrise' | 'after sunset' true + now = datetime(2015, 9, 16, 7, 0, 1, tzinfo=dt_util.UTC) + with patch("homeassistant.util.dt.utcnow", return_value=now): + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + assert len(calls) == 3 + await assert_automation_condition_trace( + hass_ws_client, + "sun", + { + "result": True, + "wanted_time_after": "2015-09-17T01:53:44.723614+00:00", + "wanted_time_before": "2015-09-16T13:33:18.342542+00:00", + }, + ) + + # now = midnight - 1s local -> 'before sunrise' | 'after sunset' true + now = datetime(2015, 9, 17, 6, 59, 59, tzinfo=dt_util.UTC) + with patch("homeassistant.util.dt.utcnow", return_value=now): + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + assert len(calls) == 4 + await assert_automation_condition_trace( + hass_ws_client, + "sun", + { + "result": True, + "wanted_time_after": "2015-09-17T01:53:44.723614+00:00", + "wanted_time_before": "2015-09-16T13:33:18.342542+00:00", + }, + ) + + async def test_if_action_before_sunrise_no_offset_kotzebue(hass, hass_ws_client, calls): """ Test if action was before sunrise.