Fix history stats query using incorrect microseconds (#91250)

pull/91575/head
J. Nick Koston 2023-04-17 11:37:30 -10:00 committed by GitHub
parent 81f018b7e5
commit da4c144a5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 68 additions and 8 deletions

View File

@ -78,7 +78,7 @@ class HistoryStats:
utc_now = dt_util.utcnow()
now_timestamp = floored_timestamp(utc_now)
if current_period_start > utc_now:
if current_period_start_timestamp > now_timestamp:
# History cannot tell the future
self._history_current_period = []
self._previous_run_before_start = True
@ -122,7 +122,9 @@ class HistoryStats:
# Don't compute anything as the value cannot have changed
return self._state
else:
await self._async_history_from_db(current_period_start, current_period_end)
await self._async_history_from_db(
current_period_start_timestamp, current_period_end_timestamp
)
self._previous_run_before_start = False
seconds_matched, match_count = self._async_compute_seconds_and_changes(
@ -135,15 +137,15 @@ class HistoryStats:
async def _async_history_from_db(
self,
current_period_start: datetime.datetime,
current_period_end: datetime.datetime,
current_period_start_timestamp: float,
current_period_end_timestamp: float,
) -> None:
"""Update history data for the current period from the database."""
instance = get_instance(self.hass)
states = await instance.async_add_executor_job(
self._state_changes_during_period,
current_period_start,
current_period_end,
current_period_start_timestamp,
current_period_end_timestamp,
)
self._history_current_period = [
HistoryState(state.state, state.last_changed.timestamp())
@ -151,8 +153,11 @@ class HistoryStats:
]
def _state_changes_during_period(
self, start: datetime.datetime, end: datetime.datetime
self, start_ts: float, end_ts: float
) -> list[State]:
"""Return state changes during a period."""
start = dt_util.utc_from_timestamp(start_ts)
end = dt_util.utc_from_timestamp(end_ts)
return history.state_changes_during_period(
self.hass,
start,

View File

@ -1,5 +1,5 @@
"""The test for the History Statistics sensor platform."""
from datetime import timedelta
from datetime import datetime, timedelta
from unittest.mock import patch
from freezegun import freeze_time
@ -1534,3 +1534,58 @@ async def test_device_classes(recorder_mock: Recorder, hass: HomeAssistant) -> N
assert hass.states.get("sensor.time").attributes[ATTR_DEVICE_CLASS] == "duration"
assert ATTR_DEVICE_CLASS not in hass.states.get("sensor.ratio").attributes
assert ATTR_DEVICE_CLASS not in hass.states.get("sensor.count").attributes
async def test_history_stats_handles_floored_timestamps(
recorder_mock: Recorder,
hass: HomeAssistant,
) -> None:
"""Test we account for microseconds when doing the data calculation."""
hass.config.set_time_zone("UTC")
utcnow = dt_util.utcnow()
start_time = utcnow.replace(hour=0, minute=0, second=0, microsecond=0)
last_times = None
def _fake_states(
hass: HomeAssistant, start: datetime, end: datetime | None, *args, **kwargs
) -> dict[str, list[ha.State]]:
"""Fake state changes."""
nonlocal last_times
last_times = (start, end)
return {
"binary_sensor.state": [
ha.State(
"binary_sensor.state",
"on",
last_changed=start_time,
last_updated=start_time,
),
]
}
with patch(
"homeassistant.components.recorder.history.state_changes_during_period",
_fake_states,
), freeze_time(start_time):
await async_setup_component(
hass,
"sensor",
{
"sensor": [
{
"platform": "history_stats",
"entity_id": "binary_sensor.state",
"name": "sensor1",
"state": "on",
"start": "{{ utcnow().replace(hour=0, minute=0, second=0, microsecond=100) }}",
"duration": {"hours": 2},
"type": "time",
}
]
},
)
await hass.async_block_till_done()
await async_update_entity(hass, "sensor.sensor1")
await hass.async_block_till_done()
assert last_times == (start_time, start_time + timedelta(hours=2))