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
This commit is contained in:
Allen Porter 2022-04-13 19:04:59 -07:00 committed by GitHub
parent c00e226b2a
commit 36bb947cdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 86 additions and 5 deletions

View File

@ -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

View File

@ -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"]