Time range should be treated as open ended (#77660)
* Time range should be treated as open end * Refactored the logic of calculating the state * Improve tests * Improve tests Co-authored-by: Erik <erik@montnemery.com>
This commit is contained in:
parent
6b361b70c9
commit
32e4a2515e
2 changed files with 166 additions and 48 deletions
|
@ -291,14 +291,17 @@ class Schedule(Entity):
|
|||
todays_schedule = self._config.get(WEEKDAY_TO_CONF[now.weekday()], [])
|
||||
|
||||
# Determine current schedule state
|
||||
self._attr_state = next(
|
||||
(
|
||||
STATE_ON
|
||||
for time_range in todays_schedule
|
||||
if time_range[CONF_FROM] <= now.time() <= time_range[CONF_TO]
|
||||
),
|
||||
STATE_OFF,
|
||||
)
|
||||
for time_range in todays_schedule:
|
||||
# The current time should be greater or equal to CONF_FROM.
|
||||
if now.time() < time_range[CONF_FROM]:
|
||||
continue
|
||||
# The current time should be smaller (and not equal) to CONF_TO.
|
||||
# Note that any time in the day is treated as smaller than time.max.
|
||||
if now.time() < time_range[CONF_TO] or time_range[CONF_TO] == time.max:
|
||||
self._attr_state = STATE_ON
|
||||
break
|
||||
else:
|
||||
self._attr_state = STATE_OFF
|
||||
|
||||
# Find next event in the schedule, loop over each day (starting with
|
||||
# the current day) until the next event has been found.
|
||||
|
@ -319,11 +322,15 @@ class Schedule(Entity):
|
|||
if next_event := next(
|
||||
(
|
||||
possible_next_event
|
||||
for time in times
|
||||
for timestamp in times
|
||||
if (
|
||||
possible_next_event := (
|
||||
datetime.combine(now.date(), time, tzinfo=now.tzinfo)
|
||||
datetime.combine(now.date(), timestamp, tzinfo=now.tzinfo)
|
||||
+ timedelta(days=day)
|
||||
if not timestamp == time.max
|
||||
# Special case for midnight of the following day.
|
||||
else datetime.combine(now.date(), time(), tzinfo=now.tzinfo)
|
||||
+ timedelta(days=day + 1)
|
||||
)
|
||||
)
|
||||
> now
|
||||
|
|
|
@ -221,25 +221,14 @@ async def test_events_one_day(
|
|||
|
||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes[ATTR_NEXT_EVENT].isoformat() == "2022-09-11T07:00:00-07:00"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"sun_schedule, mon_schedule",
|
||||
(
|
||||
(
|
||||
{CONF_FROM: "23:00:00", CONF_TO: "24:00:00"},
|
||||
{CONF_FROM: "00:00:00", CONF_TO: "01:00:00"},
|
||||
),
|
||||
),
|
||||
)
|
||||
async def test_adjacent(
|
||||
async def test_adjacent_cross_midnight(
|
||||
hass: HomeAssistant,
|
||||
schedule_setup: Callable[..., Coroutine[Any, Any, bool]],
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
sun_schedule: dict[str, str],
|
||||
mon_schedule: dict[str, str],
|
||||
freezer,
|
||||
) -> None:
|
||||
"""Test adjacent events don't toggle on->off->on."""
|
||||
|
@ -251,8 +240,8 @@ async def test_adjacent(
|
|||
"from_yaml": {
|
||||
CONF_NAME: "from yaml",
|
||||
CONF_ICON: "mdi:party-popper",
|
||||
CONF_SUNDAY: sun_schedule,
|
||||
CONF_MONDAY: mon_schedule,
|
||||
CONF_SUNDAY: {CONF_FROM: "23:00:00", CONF_TO: "24:00:00"},
|
||||
CONF_MONDAY: {CONF_FROM: "00:00:00", CONF_TO: "01:00:00"},
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -269,17 +258,6 @@ async def test_adjacent(
|
|||
freezer.move_to(state.attributes[ATTR_NEXT_EVENT])
|
||||
async_fire_time_changed(hass)
|
||||
|
||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert (
|
||||
state.attributes[ATTR_NEXT_EVENT].isoformat()
|
||||
== "2022-09-04T23:59:59.999999-07:00"
|
||||
)
|
||||
|
||||
freezer.move_to(state.attributes[ATTR_NEXT_EVENT])
|
||||
async_fire_time_changed(hass)
|
||||
|
||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
|
@ -298,13 +276,149 @@ async def test_adjacent(
|
|||
|
||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes[ATTR_NEXT_EVENT].isoformat() == "2022-09-11T23:00:00-07:00"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert len(state_changes) == 4
|
||||
for event in state_changes:
|
||||
assert len(state_changes) == 3
|
||||
for event in state_changes[:-1]:
|
||||
assert event.data["new_state"].state == STATE_ON
|
||||
assert state_changes[2].data["new_state"].state == STATE_OFF
|
||||
|
||||
|
||||
async def test_adjacent_within_day(
|
||||
hass: HomeAssistant,
|
||||
schedule_setup: Callable[..., Coroutine[Any, Any, bool]],
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
freezer,
|
||||
) -> None:
|
||||
"""Test adjacent events don't toggle on->off->on."""
|
||||
freezer.move_to("2022-08-30 13:20:00-07:00")
|
||||
|
||||
assert await schedule_setup(
|
||||
config={
|
||||
DOMAIN: {
|
||||
"from_yaml": {
|
||||
CONF_NAME: "from yaml",
|
||||
CONF_ICON: "mdi:party-popper",
|
||||
CONF_SUNDAY: [
|
||||
{CONF_FROM: "22:00:00", CONF_TO: "22:30:00"},
|
||||
{CONF_FROM: "22:30:00", CONF_TO: "23:00:00"},
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
items=[],
|
||||
)
|
||||
|
||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes[ATTR_NEXT_EVENT].isoformat() == "2022-09-04T22:00:00-07:00"
|
||||
|
||||
state_changes = async_capture_events(hass, EVENT_STATE_CHANGED)
|
||||
|
||||
freezer.move_to(state.attributes[ATTR_NEXT_EVENT])
|
||||
async_fire_time_changed(hass)
|
||||
|
||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_NEXT_EVENT].isoformat() == "2022-09-04T22:30:00-07:00"
|
||||
|
||||
freezer.move_to(state.attributes[ATTR_NEXT_EVENT])
|
||||
async_fire_time_changed(hass)
|
||||
|
||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_NEXT_EVENT].isoformat() == "2022-09-04T23:00:00-07:00"
|
||||
|
||||
freezer.move_to(state.attributes[ATTR_NEXT_EVENT])
|
||||
async_fire_time_changed(hass)
|
||||
|
||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes[ATTR_NEXT_EVENT].isoformat() == "2022-09-11T22:00:00-07:00"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert len(state_changes) == 3
|
||||
for event in state_changes[:-1]:
|
||||
assert event.data["new_state"].state == STATE_ON
|
||||
assert state_changes[2].data["new_state"].state == STATE_OFF
|
||||
|
||||
|
||||
async def test_non_adjacent_within_day(
|
||||
hass: HomeAssistant,
|
||||
schedule_setup: Callable[..., Coroutine[Any, Any, bool]],
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
freezer,
|
||||
) -> None:
|
||||
"""Test adjacent events don't toggle on->off->on."""
|
||||
freezer.move_to("2022-08-30 13:20:00-07:00")
|
||||
|
||||
assert await schedule_setup(
|
||||
config={
|
||||
DOMAIN: {
|
||||
"from_yaml": {
|
||||
CONF_NAME: "from yaml",
|
||||
CONF_ICON: "mdi:party-popper",
|
||||
CONF_SUNDAY: [
|
||||
{CONF_FROM: "22:00:00", CONF_TO: "22:15:00"},
|
||||
{CONF_FROM: "22:30:00", CONF_TO: "23:00:00"},
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
items=[],
|
||||
)
|
||||
|
||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes[ATTR_NEXT_EVENT].isoformat() == "2022-09-04T22:00:00-07:00"
|
||||
|
||||
state_changes = async_capture_events(hass, EVENT_STATE_CHANGED)
|
||||
|
||||
freezer.move_to(state.attributes[ATTR_NEXT_EVENT])
|
||||
async_fire_time_changed(hass)
|
||||
|
||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_NEXT_EVENT].isoformat() == "2022-09-04T22:15:00-07:00"
|
||||
|
||||
freezer.move_to(state.attributes[ATTR_NEXT_EVENT])
|
||||
async_fire_time_changed(hass)
|
||||
|
||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes[ATTR_NEXT_EVENT].isoformat() == "2022-09-04T22:30:00-07:00"
|
||||
|
||||
freezer.move_to(state.attributes[ATTR_NEXT_EVENT])
|
||||
async_fire_time_changed(hass)
|
||||
|
||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_NEXT_EVENT].isoformat() == "2022-09-04T23:00:00-07:00"
|
||||
|
||||
freezer.move_to(state.attributes[ATTR_NEXT_EVENT])
|
||||
async_fire_time_changed(hass)
|
||||
|
||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes[ATTR_NEXT_EVENT].isoformat() == "2022-09-11T22:00:00-07:00"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert len(state_changes) == 4
|
||||
assert state_changes[0].data["new_state"].state == STATE_ON
|
||||
assert state_changes[1].data["new_state"].state == STATE_OFF
|
||||
assert state_changes[2].data["new_state"].state == STATE_ON
|
||||
assert state_changes[3].data["new_state"].state == STATE_OFF
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -348,17 +462,14 @@ async def test_to_midnight(
|
|||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert (
|
||||
state.attributes[ATTR_NEXT_EVENT].isoformat()
|
||||
== "2022-09-04T23:59:59.999999-07:00"
|
||||
)
|
||||
assert state.attributes[ATTR_NEXT_EVENT].isoformat() == "2022-09-05T00:00:00-07:00"
|
||||
|
||||
freezer.move_to(state.attributes[ATTR_NEXT_EVENT])
|
||||
async_fire_time_changed(hass)
|
||||
|
||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes[ATTR_NEXT_EVENT].isoformat() == "2022-09-11T00:00:00-07:00"
|
||||
|
||||
|
||||
|
@ -490,8 +601,8 @@ async def test_ws_delete(
|
|||
"to, next_event, saved_to",
|
||||
(
|
||||
("23:59:59", "2022-08-10T23:59:59-07:00", "23:59:59"),
|
||||
("24:00", "2022-08-10T23:59:59.999999-07:00", "24:00:00"),
|
||||
("24:00:00", "2022-08-10T23:59:59.999999-07:00", "24:00:00"),
|
||||
("24:00", "2022-08-11T00:00:00-07:00", "24:00:00"),
|
||||
("24:00:00", "2022-08-11T00:00:00-07:00", "24:00:00"),
|
||||
),
|
||||
)
|
||||
async def test_update(
|
||||
|
@ -560,8 +671,8 @@ async def test_update(
|
|||
"to, next_event, saved_to",
|
||||
(
|
||||
("14:00:00", "2022-08-15T14:00:00-07:00", "14:00:00"),
|
||||
("24:00", "2022-08-15T23:59:59.999999-07:00", "24:00:00"),
|
||||
("24:00:00", "2022-08-15T23:59:59.999999-07:00", "24:00:00"),
|
||||
("24:00", "2022-08-16T00:00:00-07:00", "24:00:00"),
|
||||
("24:00:00", "2022-08-16T00:00:00-07:00", "24:00:00"),
|
||||
),
|
||||
)
|
||||
async def test_ws_create(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue