Add calendar event end trigger (#70372)

* Add calendar event end trigger

* Rename start date to last_endtime

* Rename now to last_endtime
This commit is contained in:
Allen Porter 2022-04-22 21:19:35 -07:00 committed by GitHub
parent 9008a76bd4
commit 5ffaa70bb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 81 additions and 20 deletions

View file

@ -28,13 +28,14 @@ from . import DOMAIN, CalendarEntity, CalendarEvent
_LOGGER = logging.getLogger(__name__)
EVENT_START = "start"
EVENT_END = "end"
UPDATE_INTERVAL = datetime.timedelta(minutes=15)
TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_PLATFORM): DOMAIN,
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Optional(CONF_EVENT, default=EVENT_START): vol.In({EVENT_START}),
vol.Optional(CONF_EVENT, default=EVENT_START): vol.In({EVENT_START, EVENT_END}),
}
)
@ -48,6 +49,7 @@ class CalendarEventListener:
job: HassJob,
trigger_data: dict[str, Any],
entity: CalendarEntity,
event_type: str,
) -> None:
"""Initialize CalendarEventListener."""
self._hass = hass
@ -58,6 +60,7 @@ class CalendarEventListener:
self._unsub_refresh: CALLBACK_TYPE | None = None
# Upcoming set of events with their trigger time
self._events: list[tuple[datetime.datetime, CalendarEvent]] = []
self._event_type = event_type
async def async_attach(self) -> None:
"""Attach a calendar event listener."""
@ -76,29 +79,27 @@ class CalendarEventListener:
self._unsub_refresh()
self._unsub_refresh = None
async def _fetch_events(self, now: datetime.datetime) -> None:
async def _fetch_events(self, last_endtime: datetime.datetime) -> None:
"""Update the set of eligible events."""
start_date = now
end_date = now + UPDATE_INTERVAL
_LOGGER.debug("Fetching events between %s, %s", start_date, end_date)
events = await self._entity.async_get_events(self._hass, start_date, end_date)
end_time = last_endtime + UPDATE_INTERVAL
_LOGGER.debug("Fetching events between %s, %s", last_endtime, end_time)
events = await self._entity.async_get_events(self._hass, last_endtime, end_time)
# Build list of events and the appropriate time to trigger an alarm. The
# returned events may have already started but matched the start/end time
# filtering above, so exclude any events that have already passed the
# trigger time.
event_list = [
(dt_util.as_utc(event.start_datetime_local), event) for event in events
]
event_list = []
for event in events:
event_time = (
event.start_datetime_local
if self._event_type == EVENT_START
else event.end_datetime_local
)
if event_time > last_endtime:
event_list.append((event_time, event))
event_list.sort(key=lambda x: x[0])
self._events.extend(
[
(trigger_time, event)
for (trigger_time, event) in event_list
if trigger_time > now
]
)
self._events = event_list
_LOGGER.debug("Populated event list %s", self._events)
@callback
@ -168,6 +169,8 @@ async def async_attach_trigger(
"event": event_type,
}
listener = CalendarEventListener(hass, HassJob(action), trigger_data, entity)
listener = CalendarEventListener(
hass, HassJob(action), trigger_data, entity, event_type
)
await listener.async_attach()
return listener.async_detach

View file

@ -19,7 +19,7 @@ import pytest
from homeassistant.components import calendar
import homeassistant.components.automation as automation
from homeassistant.components.calendar.trigger import EVENT_START
from homeassistant.components.calendar.trigger import EVENT_END, EVENT_START
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
@ -189,10 +189,38 @@ async def test_event_start_trigger(hass, calls, fake_schedule):
]
async def test_event_end_trigger(hass, calls, fake_schedule):
"""Test the a calendar trigger based on end time."""
event_data = fake_schedule.create_event(
start=datetime.datetime.fromisoformat("2022-04-19 11:00:00+00:00"),
end=datetime.datetime.fromisoformat("2022-04-19 12:00:00+00:00"),
)
await create_automation(hass, EVENT_END)
# Event started, nothing should fire yet
await fake_schedule.fire_until(
datetime.datetime.fromisoformat("2022-04-19 11:10:00+00:00")
)
assert len(calls()) == 0
# Event ends
await fake_schedule.fire_until(
datetime.datetime.fromisoformat("2022-04-19 12:10:00+00:00")
)
assert calls() == [
{
"platform": "calendar",
"event": EVENT_END,
"calendar_event": event_data,
}
]
async def test_calendar_trigger_with_no_events(hass, calls, fake_schedule):
"""Test a calendar trigger setup with no events."""
await create_automation(hass, EVENT_START)
await create_automation(hass, EVENT_END)
# No calls, at arbitrary times
await fake_schedule.fire_until(
@ -201,7 +229,7 @@ async def test_calendar_trigger_with_no_events(hass, calls, fake_schedule):
assert len(calls()) == 0
async def test_multiple_events(hass, calls, fake_schedule):
async def test_multiple_start_events(hass, calls, fake_schedule):
"""Test that a trigger fires for multiple events."""
event_data1 = fake_schedule.create_event(
@ -231,6 +259,36 @@ async def test_multiple_events(hass, calls, fake_schedule):
]
async def test_multiple_end_events(hass, calls, fake_schedule):
"""Test that a trigger fires for multiple events."""
event_data1 = fake_schedule.create_event(
start=datetime.datetime.fromisoformat("2022-04-19 10:45:00+00:00"),
end=datetime.datetime.fromisoformat("2022-04-19 11:00:00+00:00"),
)
event_data2 = fake_schedule.create_event(
start=datetime.datetime.fromisoformat("2022-04-19 11:00:00+00:00"),
end=datetime.datetime.fromisoformat("2022-04-19 11:15:00+00:00"),
)
await create_automation(hass, EVENT_END)
await fake_schedule.fire_until(
datetime.datetime.fromisoformat("2022-04-19 11:30:00+00:00")
)
assert calls() == [
{
"platform": "calendar",
"event": EVENT_END,
"calendar_event": event_data1,
},
{
"platform": "calendar",
"event": EVENT_END,
"calendar_event": event_data2,
},
]
async def test_multiple_events_sharing_start_time(hass, calls, fake_schedule):
"""Test that a trigger fires for every event sharing a start time."""