Add weekdays to time trigger (#147505)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Franck Nijhof 2025-07-04 22:31:11 +02:00 committed by GitHub
parent 6a7f4953cd
commit c0368f2448
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 218 additions and 2 deletions

View File

@ -16,6 +16,7 @@ from homeassistant.const import (
CONF_PLATFORM, CONF_PLATFORM,
STATE_UNAVAILABLE, STATE_UNAVAILABLE,
STATE_UNKNOWN, STATE_UNKNOWN,
WEEKDAYS,
) )
from homeassistant.core import ( from homeassistant.core import (
CALLBACK_TYPE, CALLBACK_TYPE,
@ -37,6 +38,8 @@ from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
CONF_WEEKDAY = "weekday"
_TIME_TRIGGER_ENTITY = vol.All(str, cv.entity_domain(["input_datetime", "sensor"])) _TIME_TRIGGER_ENTITY = vol.All(str, cv.entity_domain(["input_datetime", "sensor"]))
_TIME_AT_SCHEMA = vol.Any(cv.time, _TIME_TRIGGER_ENTITY) _TIME_AT_SCHEMA = vol.Any(cv.time, _TIME_TRIGGER_ENTITY)
@ -74,6 +77,10 @@ TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend(
{ {
vol.Required(CONF_PLATFORM): "time", vol.Required(CONF_PLATFORM): "time",
vol.Required(CONF_AT): vol.All(cv.ensure_list, [_TIME_TRIGGER_SCHEMA]), vol.Required(CONF_AT): vol.All(cv.ensure_list, [_TIME_TRIGGER_SCHEMA]),
vol.Optional(CONF_WEEKDAY): vol.Any(
vol.In(WEEKDAYS),
vol.All(cv.ensure_list, [vol.In(WEEKDAYS)]),
),
} }
) )
@ -85,7 +92,7 @@ class TrackEntity(NamedTuple):
callback: Callable callback: Callable
async def async_attach_trigger( async def async_attach_trigger( # noqa: C901
hass: HomeAssistant, hass: HomeAssistant,
config: ConfigType, config: ConfigType,
action: TriggerActionType, action: TriggerActionType,
@ -103,6 +110,18 @@ async def async_attach_trigger(
description: str, now: datetime, *, entity_id: str | None = None description: str, now: datetime, *, entity_id: str | None = None
) -> None: ) -> None:
"""Listen for time changes and calls action.""" """Listen for time changes and calls action."""
# Check weekday filter if configured
if CONF_WEEKDAY in config:
weekday_config = config[CONF_WEEKDAY]
current_weekday = WEEKDAYS[now.weekday()]
# Check if current weekday matches the configuration
if isinstance(weekday_config, str):
if current_weekday != weekday_config:
return
elif current_weekday not in weekday_config:
return
hass.async_run_hass_job( hass.async_run_hass_job(
job, job,
{ {

View File

@ -1,6 +1,6 @@
"""The tests for the time automation.""" """The tests for the time automation."""
from datetime import timedelta from datetime import datetime, timedelta
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
@ -877,3 +877,200 @@ async def test_if_at_template_limited_template(
await hass.async_block_till_done() await hass.async_block_till_done()
assert "is not supported in limited templates" in caplog.text assert "is not supported in limited templates" in caplog.text
async def test_if_fires_using_weekday_single(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
service_calls: list[ServiceCall],
) -> None:
"""Test for firing on a specific weekday."""
# Freeze time to Monday, January 2, 2023 at 5:00:00
monday_trigger = dt_util.as_utc(datetime(2023, 1, 2, 5, 0, 0, 0))
freezer.move_to(monday_trigger)
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"trigger": {"platform": "time", "at": "5:00:00", "weekday": "mon"},
"action": {
"service": "test.automation",
"data_template": {
"some": "{{ trigger.platform }} - {{ trigger.now.strftime('%A') }}",
},
},
}
},
)
await hass.async_block_till_done()
# Fire the trigger on Monday
async_fire_time_changed(hass, monday_trigger + timedelta(seconds=1))
await hass.async_block_till_done()
assert len(service_calls) == 1
assert service_calls[0].data["some"] == "time - Monday"
# Fire on Tuesday at the same time - should not trigger
tuesday_trigger = dt_util.as_utc(datetime(2023, 1, 3, 5, 0, 0, 0))
async_fire_time_changed(hass, tuesday_trigger)
await hass.async_block_till_done()
# Should still be only 1 call
assert len(service_calls) == 1
async def test_if_fires_using_weekday_multiple(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
service_calls: list[ServiceCall],
) -> None:
"""Test for firing on multiple weekdays."""
# Freeze time to Monday, January 2, 2023 at 5:00:00
monday_trigger = dt_util.as_utc(datetime(2023, 1, 2, 5, 0, 0, 0))
freezer.move_to(monday_trigger)
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"trigger": {
"platform": "time",
"at": "5:00:00",
"weekday": ["mon", "wed", "fri"],
},
"action": {
"service": "test.automation",
"data_template": {
"some": "{{ trigger.platform }} - {{ trigger.now.strftime('%A') }}",
},
},
}
},
)
await hass.async_block_till_done()
# Fire on Monday - should trigger
async_fire_time_changed(hass, monday_trigger + timedelta(seconds=1))
await hass.async_block_till_done()
assert len(service_calls) == 1
assert "Monday" in service_calls[0].data["some"]
# Fire on Tuesday - should not trigger
tuesday_trigger = dt_util.as_utc(datetime(2023, 1, 3, 5, 0, 0, 0))
async_fire_time_changed(hass, tuesday_trigger)
await hass.async_block_till_done()
assert len(service_calls) == 1
# Fire on Wednesday - should trigger
wednesday_trigger = dt_util.as_utc(datetime(2023, 1, 4, 5, 0, 0, 0))
async_fire_time_changed(hass, wednesday_trigger)
await hass.async_block_till_done()
assert len(service_calls) == 2
assert "Wednesday" in service_calls[1].data["some"]
# Fire on Friday - should trigger
friday_trigger = dt_util.as_utc(datetime(2023, 1, 6, 5, 0, 0, 0))
async_fire_time_changed(hass, friday_trigger)
await hass.async_block_till_done()
assert len(service_calls) == 3
assert "Friday" in service_calls[2].data["some"]
async def test_if_fires_using_weekday_with_entity(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
service_calls: list[ServiceCall],
) -> None:
"""Test for firing on weekday with input_datetime entity."""
await async_setup_component(
hass,
"input_datetime",
{"input_datetime": {"trigger": {"has_date": False, "has_time": True}}},
)
# Freeze time to Monday, January 2, 2023 at 5:00:00
monday_trigger = dt_util.as_utc(datetime(2023, 1, 2, 5, 0, 0, 0))
await hass.services.async_call(
"input_datetime",
"set_datetime",
{
ATTR_ENTITY_ID: "input_datetime.trigger",
"time": "05:00:00",
},
blocking=True,
)
freezer.move_to(monday_trigger)
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"trigger": {
"platform": "time",
"at": "input_datetime.trigger",
"weekday": "mon",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "{{ trigger.platform }} - {{ trigger.now.strftime('%A') }}",
"entity": "{{ trigger.entity_id }}",
},
},
}
},
)
await hass.async_block_till_done()
# Fire on Monday - should trigger
async_fire_time_changed(hass, monday_trigger + timedelta(seconds=1))
await hass.async_block_till_done()
automation_calls = [call for call in service_calls if call.domain == "test"]
assert len(automation_calls) == 1
assert "Monday" in automation_calls[0].data["some"]
assert automation_calls[0].data["entity"] == "input_datetime.trigger"
# Fire on Tuesday - should not trigger
tuesday_trigger = dt_util.as_utc(datetime(2023, 1, 3, 5, 0, 0, 0))
async_fire_time_changed(hass, tuesday_trigger)
await hass.async_block_till_done()
automation_calls = [call for call in service_calls if call.domain == "test"]
assert len(automation_calls) == 1
def test_weekday_validation() -> None:
"""Test weekday validation in trigger schema."""
# Valid single weekday
valid_config = {"platform": "time", "at": "5:00:00", "weekday": "mon"}
time.TRIGGER_SCHEMA(valid_config)
# Valid multiple weekdays
valid_config = {
"platform": "time",
"at": "5:00:00",
"weekday": ["mon", "wed", "fri"],
}
time.TRIGGER_SCHEMA(valid_config)
# Invalid weekday
invalid_config = {"platform": "time", "at": "5:00:00", "weekday": "invalid"}
with pytest.raises(vol.Invalid):
time.TRIGGER_SCHEMA(invalid_config)
# Invalid weekday in list
invalid_config = {
"platform": "time",
"at": "5:00:00",
"weekday": ["mon", "invalid"],
}
with pytest.raises(vol.Invalid):
time.TRIGGER_SCHEMA(invalid_config)