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()], [])
|
todays_schedule = self._config.get(WEEKDAY_TO_CONF[now.weekday()], [])
|
||||||
|
|
||||||
# Determine current schedule state
|
# Determine current schedule state
|
||||||
self._attr_state = next(
|
for time_range in todays_schedule:
|
||||||
(
|
# The current time should be greater or equal to CONF_FROM.
|
||||||
STATE_ON
|
if now.time() < time_range[CONF_FROM]:
|
||||||
for time_range in todays_schedule
|
continue
|
||||||
if time_range[CONF_FROM] <= now.time() <= time_range[CONF_TO]
|
# 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.
|
||||||
STATE_OFF,
|
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
|
# Find next event in the schedule, loop over each day (starting with
|
||||||
# the current day) until the next event has been found.
|
# the current day) until the next event has been found.
|
||||||
|
@ -319,11 +322,15 @@ class Schedule(Entity):
|
||||||
if next_event := next(
|
if next_event := next(
|
||||||
(
|
(
|
||||||
possible_next_event
|
possible_next_event
|
||||||
for time in times
|
for timestamp in times
|
||||||
if (
|
if (
|
||||||
possible_next_event := (
|
possible_next_event := (
|
||||||
datetime.combine(now.date(), time, tzinfo=now.tzinfo)
|
datetime.combine(now.date(), timestamp, tzinfo=now.tzinfo)
|
||||||
+ timedelta(days=day)
|
+ 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
|
> now
|
||||||
|
|
|
@ -221,25 +221,14 @@ async def test_events_one_day(
|
||||||
|
|
||||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||||
assert state
|
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"
|
assert state.attributes[ATTR_NEXT_EVENT].isoformat() == "2022-09-11T07:00:00-07:00"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
async def test_adjacent_cross_midnight(
|
||||||
"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(
|
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
schedule_setup: Callable[..., Coroutine[Any, Any, bool]],
|
schedule_setup: Callable[..., Coroutine[Any, Any, bool]],
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
sun_schedule: dict[str, str],
|
|
||||||
mon_schedule: dict[str, str],
|
|
||||||
freezer,
|
freezer,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test adjacent events don't toggle on->off->on."""
|
"""Test adjacent events don't toggle on->off->on."""
|
||||||
|
@ -251,8 +240,8 @@ async def test_adjacent(
|
||||||
"from_yaml": {
|
"from_yaml": {
|
||||||
CONF_NAME: "from yaml",
|
CONF_NAME: "from yaml",
|
||||||
CONF_ICON: "mdi:party-popper",
|
CONF_ICON: "mdi:party-popper",
|
||||||
CONF_SUNDAY: sun_schedule,
|
CONF_SUNDAY: {CONF_FROM: "23:00:00", CONF_TO: "24:00:00"},
|
||||||
CONF_MONDAY: mon_schedule,
|
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])
|
freezer.move_to(state.attributes[ATTR_NEXT_EVENT])
|
||||||
async_fire_time_changed(hass)
|
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")
|
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
|
@ -298,13 +276,149 @@ async def test_adjacent(
|
||||||
|
|
||||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||||
assert state
|
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"
|
assert state.attributes[ATTR_NEXT_EVENT].isoformat() == "2022-09-11T23:00:00-07:00"
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(state_changes) == 4
|
assert len(state_changes) == 3
|
||||||
for event in state_changes:
|
for event in state_changes[:-1]:
|
||||||
assert event.data["new_state"].state == STATE_ON
|
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(
|
@pytest.mark.parametrize(
|
||||||
|
@ -348,17 +462,14 @@ async def test_to_midnight(
|
||||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
assert (
|
assert state.attributes[ATTR_NEXT_EVENT].isoformat() == "2022-09-05T00:00:00-07:00"
|
||||||
state.attributes[ATTR_NEXT_EVENT].isoformat()
|
|
||||||
== "2022-09-04T23:59:59.999999-07:00"
|
|
||||||
)
|
|
||||||
|
|
||||||
freezer.move_to(state.attributes[ATTR_NEXT_EVENT])
|
freezer.move_to(state.attributes[ATTR_NEXT_EVENT])
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
|
|
||||||
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
state = hass.states.get(f"{DOMAIN}.from_yaml")
|
||||||
assert state
|
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"
|
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",
|
"to, next_event, saved_to",
|
||||||
(
|
(
|
||||||
("23:59:59", "2022-08-10T23:59:59-07:00", "23:59:59"),
|
("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", "2022-08-11T00:00:00-07:00", "24:00:00"),
|
||||||
("24:00:00", "2022-08-10T23:59:59.999999-07:00", "24:00:00"),
|
("24:00:00", "2022-08-11T00:00:00-07:00", "24:00:00"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def test_update(
|
async def test_update(
|
||||||
|
@ -560,8 +671,8 @@ async def test_update(
|
||||||
"to, next_event, saved_to",
|
"to, next_event, saved_to",
|
||||||
(
|
(
|
||||||
("14:00:00", "2022-08-15T14:00:00-07:00", "14:00:00"),
|
("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", "2022-08-16T00:00:00-07:00", "24:00:00"),
|
||||||
("24:00:00", "2022-08-15T23:59:59.999999-07:00", "24:00:00"),
|
("24:00:00", "2022-08-16T00:00:00-07:00", "24:00:00"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def test_ws_create(
|
async def test_ws_create(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue