From 36bb947cdf24cb74c4d4288ca61825226e1de5ff Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Wed, 13 Apr 2022 19:04:59 -0700 Subject: [PATCH] Fix bug in google calendar offset calculation (#70024) Move the offset reached computation outside of the update method so that it is computed when state updates occur rather than when data refreshes happen (which are throttled and happen at most every 15 minutes). Issue #69892 --- homeassistant/components/google/calendar.py | 17 +++-- tests/components/google/test_calendar.py | 74 +++++++++++++++++++++ 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/google/calendar.py b/homeassistant/components/google/calendar.py index b52f120e65a..73071e8a13d 100644 --- a/homeassistant/components/google/calendar.py +++ b/homeassistant/components/google/calendar.py @@ -120,13 +120,22 @@ class GoogleCalendarEntity(CalendarEntity): self._event: CalendarEvent | None = None self._name: str = data[CONF_NAME] self._offset = data.get(CONF_OFFSET, DEFAULT_CONF_OFFSET) - self._offset_reached = False + self._offset_value: timedelta | None = None self.entity_id = entity_id @property def extra_state_attributes(self) -> dict[str, bool]: """Return the device state attributes.""" - return {"offset_reached": self._offset_reached} + return {"offset_reached": self.offset_reached} + + @property + def offset_reached(self) -> bool: + """Return whether or not the event offset was reached.""" + if self._event and self._offset_value: + return is_offset_reached( + self._event.start_datetime_local, self._offset_value + ) + return False @property def event(self) -> CalendarEvent | None: @@ -187,9 +196,7 @@ class GoogleCalendarEntity(CalendarEntity): (summary, offset) = extract_offset(event.get("summary", ""), self._offset) event["summary"] = summary self._event = _get_calendar_event(event) - self._offset_reached = is_offset_reached( - self._event.start_datetime_local, offset - ) + self._offset_value = offset else: self._event = None diff --git a/tests/components/google/test_calendar.py b/tests/components/google/test_calendar.py index a5aca8a27d4..dda4cddc962 100644 --- a/tests/components/google/test_calendar.py +++ b/tests/components/google/test_calendar.py @@ -505,3 +505,77 @@ async def test_scan_calendar_error( assert await component_setup() assert not hass.states.get(TEST_ENTITY) + + +async def test_future_event_update_behavior( + hass, mock_events_list_items, component_setup +): + """Test an future event that becomes active.""" + now = dt_util.now() + now_utc = dt_util.utcnow() + one_hour_from_now = now + datetime.timedelta(minutes=60) + end_event = one_hour_from_now + datetime.timedelta(minutes=90) + event = { + **TEST_EVENT, + "start": {"dateTime": one_hour_from_now.isoformat()}, + "end": {"dateTime": end_event.isoformat()}, + } + mock_events_list_items([event]) + assert await component_setup() + + # Event has not started yet + state = hass.states.get(TEST_ENTITY) + assert state.name == TEST_ENTITY_NAME + assert state.state == STATE_OFF + + # Advance time until event has started + now += datetime.timedelta(minutes=60) + now_utc += datetime.timedelta(minutes=30) + with patch("homeassistant.util.dt.utcnow", return_value=now_utc), patch( + "homeassistant.util.dt.now", return_value=now + ): + async_fire_time_changed(hass, now) + await hass.async_block_till_done() + + # Event has started + state = hass.states.get(TEST_ENTITY) + assert state.state == STATE_ON + + +async def test_future_event_offset_update_behavior( + hass, mock_events_list_items, component_setup +): + """Test an future event that becomes active.""" + now = dt_util.now() + now_utc = dt_util.utcnow() + one_hour_from_now = now + datetime.timedelta(minutes=60) + end_event = one_hour_from_now + datetime.timedelta(minutes=90) + event_summary = "Test Event in Progress" + event = { + **TEST_EVENT, + "start": {"dateTime": one_hour_from_now.isoformat()}, + "end": {"dateTime": end_event.isoformat()}, + "summary": f"{event_summary} !!-15", + } + mock_events_list_items([event]) + assert await component_setup() + + # Event has not started yet + state = hass.states.get(TEST_ENTITY) + assert state.name == TEST_ENTITY_NAME + assert state.state == STATE_OFF + assert not state.attributes["offset_reached"] + + # Advance time until event has started + now += datetime.timedelta(minutes=45) + now_utc += datetime.timedelta(minutes=45) + with patch("homeassistant.util.dt.utcnow", return_value=now_utc), patch( + "homeassistant.util.dt.now", return_value=now + ): + async_fire_time_changed(hass, now) + await hass.async_block_till_done() + + # Event has not started, but the offset was reached + state = hass.states.get(TEST_ENTITY) + assert state.state == STATE_OFF + assert state.attributes["offset_reached"]