From a3146ad150084ee10ba1c3d62f77e5f1a509ca2b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 7 Jun 2021 08:21:10 -1000 Subject: [PATCH] Fix loop in tod binary sensor (#51491) Co-authored-by: Paulus Schoutsen --- homeassistant/components/tod/binary_sensor.py | 22 +- tests/components/tod/test_binary_sensor.py | 219 +++++++++++++++++- 2 files changed, 237 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/tod/binary_sensor.py b/homeassistant/components/tod/binary_sensor.py index 4fd9a3b8bf9..8264468e2e7 100644 --- a/homeassistant/components/tod/binary_sensor.py +++ b/homeassistant/components/tod/binary_sensor.py @@ -156,6 +156,26 @@ class TodSensor(BinarySensorEntity): self._time_after += self._after_offset self._time_before += self._before_offset + def _turn_to_next_day(self): + """Turn to to the next day.""" + if is_sun_event(self._after): + self._time_after = get_astral_event_next( + self.hass, self._after, self._time_after - self._after_offset + ) + self._time_after += self._after_offset + else: + # Offset is already there + self._time_after += timedelta(days=1) + + if is_sun_event(self._before): + self._time_before = get_astral_event_next( + self.hass, self._before, self._time_before - self._before_offset + ) + self._time_before += self._before_offset + else: + # Offset is already there + self._time_before += timedelta(days=1) + async def async_added_to_hass(self): """Call when entity about to be added to Home Assistant.""" self._calculate_boudary_time() @@ -182,7 +202,7 @@ class TodSensor(BinarySensorEntity): if now < self._time_before: self._next_update = self._time_before return - self._calculate_boudary_time() + self._turn_to_next_day() self._next_update = self._time_after @callback diff --git a/tests/components/tod/test_binary_sensor.py b/tests/components/tod/test_binary_sensor.py index 8b63082c36c..ef8088d6aab 100644 --- a/tests/components/tod/test_binary_sensor.py +++ b/tests/components/tod/test_binary_sensor.py @@ -12,6 +12,8 @@ import homeassistant.util.dt as dt_util from tests.common import assert_setup_component +ORIG_TIMEZONE = dt_util.DEFAULT_TIME_ZONE + @pytest.fixture(autouse=True) def mock_legacy_time(legacy_patchable_time): @@ -26,6 +28,13 @@ def setup_fixture(hass): hass.config.longitude = 18.98583 +@pytest.fixture(autouse=True) +def restore_timezone(hass): + """Make sure we change timezone.""" + yield + dt_util.set_default_time_zone(ORIG_TIMEZONE) + + async def test_setup(hass): """Test the setup.""" config = { @@ -863,6 +872,7 @@ async def test_sun_offset(hass): async def test_dst(hass): """Test sun event with offset.""" hass.config.time_zone = "CET" + dt_util.set_default_time_zone(dt_util.get_time_zone("CET")) test_time = datetime(2019, 3, 30, 3, 0, 0, tzinfo=dt_util.UTC) config = { "binary_sensor": [ @@ -882,7 +892,210 @@ async def test_dst(hass): await hass.async_block_till_done() state = hass.states.get(entity_id) - assert state.attributes["after"] == "2019-03-30T03:30:00+01:00" - assert state.attributes["before"] == "2019-03-30T03:40:00+01:00" - assert state.attributes["next_update"] == "2019-03-30T03:30:00+01:00" + assert state.attributes["after"] == "2019-03-31T03:30:00+02:00" + assert state.attributes["before"] == "2019-03-31T03:40:00+02:00" + assert state.attributes["next_update"] == "2019-03-31T03:30:00+02:00" assert state.state == STATE_OFF + + +async def test_simple_before_after_does_not_loop_utc_not_in_range(hass): + """Test simple before after.""" + hass.config.time_zone = "UTC" + dt_util.set_default_time_zone(dt_util.UTC) + test_time = datetime(2019, 1, 10, 18, 43, 0, tzinfo=dt_util.UTC) + config = { + "binary_sensor": [ + { + "platform": "tod", + "name": "Night", + "before": "06:00", + "after": "22:00", + } + ] + } + with patch( + "homeassistant.components.tod.binary_sensor.dt_util.utcnow", + return_value=test_time, + ): + await async_setup_component(hass, "binary_sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.night") + assert state.state == STATE_OFF + assert state.attributes["after"] == "2019-01-10T22:00:00+00:00" + assert state.attributes["before"] == "2019-01-11T06:00:00+00:00" + assert state.attributes["next_update"] == "2019-01-10T22:00:00+00:00" + + +async def test_simple_before_after_does_not_loop_utc_in_range(hass): + """Test simple before after.""" + hass.config.time_zone = "UTC" + dt_util.set_default_time_zone(dt_util.UTC) + test_time = datetime(2019, 1, 10, 22, 43, 0, tzinfo=dt_util.UTC) + config = { + "binary_sensor": [ + { + "platform": "tod", + "name": "Night", + "before": "06:00", + "after": "22:00", + } + ] + } + with patch( + "homeassistant.components.tod.binary_sensor.dt_util.utcnow", + return_value=test_time, + ): + await async_setup_component(hass, "binary_sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.night") + assert state.state == STATE_ON + assert state.attributes["after"] == "2019-01-10T22:00:00+00:00" + assert state.attributes["before"] == "2019-01-11T06:00:00+00:00" + assert state.attributes["next_update"] == "2019-01-11T06:00:00+00:00" + + +async def test_simple_before_after_does_not_loop_utc_fire_at_before(hass): + """Test simple before after.""" + hass.config.time_zone = "UTC" + dt_util.set_default_time_zone(dt_util.UTC) + test_time = datetime(2019, 1, 11, 6, 0, 0, tzinfo=dt_util.UTC) + config = { + "binary_sensor": [ + { + "platform": "tod", + "name": "Night", + "before": "06:00", + "after": "22:00", + } + ] + } + with patch( + "homeassistant.components.tod.binary_sensor.dt_util.utcnow", + return_value=test_time, + ): + await async_setup_component(hass, "binary_sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.night") + assert state.state == STATE_OFF + assert state.attributes["after"] == "2019-01-11T22:00:00+00:00" + assert state.attributes["before"] == "2019-01-12T06:00:00+00:00" + assert state.attributes["next_update"] == "2019-01-11T22:00:00+00:00" + + +async def test_simple_before_after_does_not_loop_utc_fire_at_after(hass): + """Test simple before after.""" + hass.config.time_zone = "UTC" + dt_util.set_default_time_zone(dt_util.UTC) + test_time = datetime(2019, 1, 10, 22, 0, 0, tzinfo=dt_util.UTC) + config = { + "binary_sensor": [ + { + "platform": "tod", + "name": "Night", + "before": "06:00", + "after": "22:00", + } + ] + } + with patch( + "homeassistant.components.tod.binary_sensor.dt_util.utcnow", + return_value=test_time, + ): + await async_setup_component(hass, "binary_sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.night") + assert state.state == STATE_ON + assert state.attributes["after"] == "2019-01-10T22:00:00+00:00" + assert state.attributes["before"] == "2019-01-11T06:00:00+00:00" + assert state.attributes["next_update"] == "2019-01-11T06:00:00+00:00" + + +async def test_simple_before_after_does_not_loop_utc_both_before_now(hass): + """Test simple before after.""" + hass.config.time_zone = "UTC" + dt_util.set_default_time_zone(dt_util.UTC) + test_time = datetime(2019, 1, 10, 22, 0, 0, tzinfo=dt_util.UTC) + config = { + "binary_sensor": [ + { + "platform": "tod", + "name": "Morning", + "before": "08:00", + "after": "00:00", + } + ] + } + with patch( + "homeassistant.components.tod.binary_sensor.dt_util.utcnow", + return_value=test_time, + ): + await async_setup_component(hass, "binary_sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.morning") + assert state.state == STATE_OFF + assert state.attributes["after"] == "2019-01-11T00:00:00+00:00" + assert state.attributes["before"] == "2019-01-11T08:00:00+00:00" + assert state.attributes["next_update"] == "2019-01-11T00:00:00+00:00" + + +async def test_simple_before_after_does_not_loop_berlin_not_in_range(hass): + """Test simple before after.""" + hass.config.time_zone = "Europe/Berlin" + dt_util.set_default_time_zone(dt_util.get_time_zone("Europe/Berlin")) + test_time = datetime(2019, 1, 10, 18, 43, 0, tzinfo=dt_util.UTC) + config = { + "binary_sensor": [ + { + "platform": "tod", + "name": "Dark", + "before": "06:00", + "after": "00:00", + } + ] + } + with patch( + "homeassistant.components.tod.binary_sensor.dt_util.utcnow", + return_value=test_time, + ): + await async_setup_component(hass, "binary_sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.dark") + assert state.state == STATE_OFF + assert state.attributes["after"] == "2019-01-11T00:00:00+01:00" + assert state.attributes["before"] == "2019-01-11T06:00:00+01:00" + assert state.attributes["next_update"] == "2019-01-11T00:00:00+01:00" + + +async def test_simple_before_after_does_not_loop_berlin_in_range(hass): + """Test simple before after.""" + hass.config.time_zone = "Europe/Berlin" + dt_util.set_default_time_zone(dt_util.get_time_zone("Europe/Berlin")) + test_time = datetime(2019, 1, 10, 23, 43, 0, tzinfo=dt_util.UTC) + config = { + "binary_sensor": [ + { + "platform": "tod", + "name": "Dark", + "before": "06:00", + "after": "00:00", + } + ] + } + with patch( + "homeassistant.components.tod.binary_sensor.dt_util.utcnow", + return_value=test_time, + ): + await async_setup_component(hass, "binary_sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.dark") + assert state.state == STATE_ON + assert state.attributes["after"] == "2019-01-11T00:00:00+01:00" + assert state.attributes["before"] == "2019-01-11T06:00:00+01:00" + assert state.attributes["next_update"] == "2019-01-11T06:00:00+01:00"