diff --git a/homeassistant/core.py b/homeassistant/core.py index d0e80ad8bd1..7aa823dc042 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1762,6 +1762,7 @@ class State: context: Context | None = None, validate_entity_id: bool | None = True, state_info: StateInfo | None = None, + last_updated_timestamp: float | None = None, ) -> None: """Initialize a new state.""" state = str(state) @@ -1793,7 +1794,8 @@ class State: # so we will set the timestamp values here to avoid the overhead of # the function call in the property we know will always be called. last_updated = self.last_updated - last_updated_timestamp = last_updated.timestamp() + if not last_updated_timestamp: + last_updated_timestamp = last_updated.timestamp() self.last_updated_timestamp = last_updated_timestamp if self.last_changed == last_updated: self.__dict__["last_changed_timestamp"] = last_updated_timestamp @@ -2309,6 +2311,7 @@ class StateMachine: context, old_state is None, state_info, + timestamp, ) if old_state is not None: old_state.expire() diff --git a/tests/components/history/test_websocket_api.py b/tests/components/history/test_websocket_api.py index 580853fb83f..e5c33d0e7af 100644 --- a/tests/components/history/test_websocket_api.py +++ b/tests/components/history/test_websocket_api.py @@ -466,16 +466,24 @@ async def test_history_stream_historical_only( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.three", "off", attributes={"any": "changed"}) - sensor_three_last_updated = hass.states.get("sensor.three").last_updated + sensor_three_last_updated_timestamp = hass.states.get( + "sensor.three" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.four", "off", attributes={"any": "again"}) - sensor_four_last_updated = hass.states.get("sensor.four").last_updated + sensor_four_last_updated_timestamp = hass.states.get( + "sensor.four" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -506,17 +514,27 @@ async def test_history_stream_historical_only( assert response == { "event": { - "end_time": sensor_four_last_updated.timestamp(), - "start_time": now.timestamp(), + "end_time": pytest.approx(sensor_four_last_updated_timestamp), + "start_time": pytest.approx(now.timestamp()), "states": { "sensor.four": [ - {"lu": sensor_four_last_updated.timestamp(), "s": "off"} + { + "lu": pytest.approx(sensor_four_last_updated_timestamp), + "s": "off", + } + ], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"} ], - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}], "sensor.three": [ - {"lu": sensor_three_last_updated.timestamp(), "s": "off"} + { + "lu": pytest.approx(sensor_three_last_updated_timestamp), + "s": "off", + } + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"} ], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}], }, }, "id": 1, @@ -817,10 +835,14 @@ async def test_history_stream_live_no_attributes_minimal_response( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -846,15 +868,19 @@ async def test_history_stream_live_no_attributes_minimal_response( assert response["type"] == "result" response = await client.receive_json() - first_end_time = sensor_two_last_updated.timestamp() + first_end_time = sensor_two_last_updated_timestamp assert response == { "event": { - "end_time": first_end_time, - "start_time": now.timestamp(), + "end_time": pytest.approx(first_end_time), + "start_time": pytest.approx(now.timestamp()), "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"} + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"} + ], }, }, "id": 1, @@ -866,14 +892,22 @@ async def test_history_stream_live_no_attributes_minimal_response( hass.states.async_set("sensor.two", "two", attributes={"any": "attr"}) await async_recorder_block_till_done(hass) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp response = await client.receive_json() assert response == { "event": { "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "one"}], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "two"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "one"} + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "two"} + ], }, }, "id": 1, @@ -894,10 +928,14 @@ async def test_history_stream_live( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -923,24 +961,24 @@ async def test_history_stream_live( assert response["type"] == "result" response = await client.receive_json() - first_end_time = sensor_two_last_updated.timestamp() + first_end_time = sensor_two_last_updated_timestamp assert response == { "event": { - "end_time": first_end_time, - "start_time": now.timestamp(), + "end_time": pytest.approx(first_end_time), + "start_time": pytest.approx(now.timestamp()), "states": { "sensor.one": [ { "a": {"any": "attr"}, - "lu": sensor_one_last_updated.timestamp(), + "lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on", } ], "sensor.two": [ { "a": {"any": "attr"}, - "lu": sensor_two_last_updated.timestamp(), + "lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off", } ], @@ -955,24 +993,30 @@ async def test_history_stream_live( hass.states.async_set("sensor.two", "two", attributes={"any": "attr"}) await async_recorder_block_till_done(hass) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated - sensor_one_last_changed = hass.states.get("sensor.one").last_changed - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp + sensor_one_last_changed_timestamp = hass.states.get( + "sensor.one" + ).last_changed_timestamp + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp response = await client.receive_json() assert response == { "event": { "states": { "sensor.one": [ { - "lc": sensor_one_last_changed.timestamp(), - "lu": sensor_one_last_updated.timestamp(), + "lc": pytest.approx(sensor_one_last_changed_timestamp), + "lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on", "a": {"diff": "attr"}, } ], "sensor.two": [ { - "lu": sensor_two_last_updated.timestamp(), + "lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "two", "a": {"any": "attr"}, } @@ -997,10 +1041,14 @@ async def test_history_stream_live_minimal_response( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -1026,24 +1074,24 @@ async def test_history_stream_live_minimal_response( assert response["type"] == "result" response = await client.receive_json() - first_end_time = sensor_two_last_updated.timestamp() + first_end_time = sensor_two_last_updated_timestamp assert response == { "event": { - "end_time": first_end_time, + "end_time": pytest.approx(first_end_time), "start_time": now.timestamp(), "states": { "sensor.one": [ { "a": {"any": "attr"}, - "lu": sensor_one_last_updated.timestamp(), + "lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on", } ], "sensor.two": [ { "a": {"any": "attr"}, - "lu": sensor_two_last_updated.timestamp(), + "lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off", } ], @@ -1057,8 +1105,12 @@ async def test_history_stream_live_minimal_response( hass.states.async_set("sensor.one", "on", attributes={"diff": "attr"}) hass.states.async_set("sensor.two", "two", attributes={"any": "attr"}) # Only sensor.two has changed - sensor_one_last_updated = hass.states.get("sensor.one").last_updated - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp hass.states.async_remove("sensor.one") hass.states.async_remove("sensor.two") await async_recorder_block_till_done(hass) @@ -1069,7 +1121,7 @@ async def test_history_stream_live_minimal_response( "states": { "sensor.two": [ { - "lu": sensor_two_last_updated.timestamp(), + "lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "two", "a": {"any": "attr"}, } @@ -1094,10 +1146,14 @@ async def test_history_stream_live_no_attributes( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -1123,18 +1179,26 @@ async def test_history_stream_live_no_attributes( assert response["type"] == "result" response = await client.receive_json() - first_end_time = sensor_two_last_updated.timestamp() + first_end_time = sensor_two_last_updated_timestamp assert response == { "event": { - "end_time": first_end_time, - "start_time": now.timestamp(), + "end_time": pytest.approx(first_end_time), + "start_time": pytest.approx(now.timestamp()), "states": { "sensor.one": [ - {"a": {}, "lu": sensor_one_last_updated.timestamp(), "s": "on"} + { + "a": {}, + "lu": pytest.approx(sensor_one_last_updated_timestamp), + "s": "on", + } ], "sensor.two": [ - {"a": {}, "lu": sensor_two_last_updated.timestamp(), "s": "off"} + { + "a": {}, + "lu": pytest.approx(sensor_two_last_updated_timestamp), + "s": "off", + } ], }, }, @@ -1147,14 +1211,22 @@ async def test_history_stream_live_no_attributes( hass.states.async_set("sensor.two", "two", attributes={"diff": "attr"}) await async_recorder_block_till_done(hass) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp response = await client.receive_json() assert response == { "event": { "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "one"}], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "two"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "one"} + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "two"} + ], }, }, "id": 1, @@ -1176,10 +1248,14 @@ async def test_history_stream_live_no_attributes_minimal_response_specific_entit await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -1205,15 +1281,19 @@ async def test_history_stream_live_no_attributes_minimal_response_specific_entit assert response["type"] == "result" response = await client.receive_json() - first_end_time = sensor_two_last_updated.timestamp() + first_end_time = sensor_two_last_updated_timestamp assert response == { "event": { - "end_time": first_end_time, - "start_time": now.timestamp(), + "end_time": pytest.approx(first_end_time), + "start_time": pytest.approx(now.timestamp()), "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"} + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"} + ], }, }, "id": 1, @@ -1225,14 +1305,22 @@ async def test_history_stream_live_no_attributes_minimal_response_specific_entit hass.states.async_set("sensor.two", "two", attributes={"any": "attr"}) await async_recorder_block_till_done(hass) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp response = await client.receive_json() assert response == { "event": { "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "one"}], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "two"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "one"} + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "two"} + ], }, }, "id": 1, @@ -1254,10 +1342,14 @@ async def test_history_stream_live_with_future_end_time( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -1287,15 +1379,19 @@ async def test_history_stream_live_with_future_end_time( assert response["type"] == "result" response = await client.receive_json() - first_end_time = sensor_two_last_updated.timestamp() + first_end_time = sensor_two_last_updated_timestamp assert response == { "event": { - "end_time": first_end_time, - "start_time": now.timestamp(), + "end_time": pytest.approx(first_end_time), + "start_time": pytest.approx(now.timestamp()), "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"} + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"} + ], }, }, "id": 1, @@ -1307,14 +1403,22 @@ async def test_history_stream_live_with_future_end_time( hass.states.async_set("sensor.two", "two", attributes={"any": "attr"}) await async_recorder_block_till_done(hass) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp response = await client.receive_json() assert response == { "event": { "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "one"}], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "two"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "one"} + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "two"} + ], }, }, "id": 1, @@ -1450,10 +1554,14 @@ async def test_overflow_queue( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -1481,18 +1589,24 @@ async def test_overflow_queue( assert response["type"] == "result" response = await client.receive_json() - first_end_time = sensor_two_last_updated.timestamp() + first_end_time = sensor_two_last_updated_timestamp assert response == { "event": { - "end_time": first_end_time, - "start_time": now.timestamp(), + "end_time": pytest.approx(first_end_time), + "start_time": pytest.approx(now.timestamp()), "states": { "sensor.one": [ - {"lu": sensor_one_last_updated.timestamp(), "s": "on"} + { + "lu": pytest.approx(sensor_one_last_updated_timestamp), + "s": "on", + } ], "sensor.two": [ - {"lu": sensor_two_last_updated.timestamp(), "s": "off"} + { + "lu": pytest.approx(sensor_two_last_updated_timestamp), + "s": "off", + } ], }, }, @@ -1522,10 +1636,14 @@ async def test_history_during_period_for_invalid_entity_ids( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.three", "off", attributes={"any": "again"}) await async_recorder_block_till_done(hass) @@ -1550,7 +1668,11 @@ async def test_history_during_period_for_invalid_entity_ids( assert response == { "result": { "sensor.one": [ - {"a": {}, "lu": sensor_one_last_updated.timestamp(), "s": "on"} + { + "a": {}, + "lu": pytest.approx(sensor_one_last_updated_timestamp), + "s": "on", + } ], }, "id": 1, @@ -1574,10 +1696,18 @@ async def test_history_during_period_for_invalid_entity_ids( assert response == { "result": { "sensor.one": [ - {"a": {}, "lu": sensor_one_last_updated.timestamp(), "s": "on"} + { + "a": {}, + "lu": pytest.approx(sensor_one_last_updated_timestamp), + "s": "on", + } ], "sensor.two": [ - {"a": {}, "lu": sensor_two_last_updated.timestamp(), "s": "off"} + { + "a": {}, + "lu": pytest.approx(sensor_two_last_updated_timestamp), + "s": "off", + } ], }, "id": 2, @@ -1670,10 +1800,14 @@ async def test_history_stream_for_invalid_entity_ids( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) - sensor_one_last_updated = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.three", "off", attributes={"any": "again"}) await async_recorder_block_till_done(hass) @@ -1703,10 +1837,12 @@ async def test_history_stream_for_invalid_entity_ids( response = await client.receive_json() assert response == { "event": { - "end_time": sensor_one_last_updated.timestamp(), - "start_time": now.timestamp(), + "end_time": pytest.approx(sensor_one_last_updated_timestamp), + "start_time": pytest.approx(now.timestamp()), "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"} + ], }, }, "id": 1, @@ -1733,11 +1869,15 @@ async def test_history_stream_for_invalid_entity_ids( response = await client.receive_json() assert response == { "event": { - "end_time": sensor_two_last_updated.timestamp(), - "start_time": now.timestamp(), + "end_time": pytest.approx(sensor_two_last_updated_timestamp), + "start_time": pytest.approx(now.timestamp()), "states": { - "sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}], + "sensor.one": [ + {"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"} + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"} + ], }, }, "id": 2, @@ -1841,21 +1981,31 @@ async def test_history_stream_historical_only_with_start_time_state_past( now = dt_util.utcnow() await async_recorder_block_till_done(hass) hass.states.async_set("sensor.one", "second", attributes={"any": "attr"}) - sensor_one_last_updated_second = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_second_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await asyncio.sleep(0.00001) hass.states.async_set("sensor.one", "third", attributes={"any": "attr"}) - sensor_one_last_updated_third = hass.states.get("sensor.one").last_updated + sensor_one_last_updated_third_timestamp = hass.states.get( + "sensor.one" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) - sensor_two_last_updated = hass.states.get("sensor.two").last_updated + sensor_two_last_updated_timestamp = hass.states.get( + "sensor.two" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.three", "off", attributes={"any": "changed"}) - sensor_three_last_updated = hass.states.get("sensor.three").last_updated + sensor_three_last_updated_timestamp = hass.states.get( + "sensor.three" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("sensor.four", "off", attributes={"any": "again"}) - sensor_four_last_updated = hass.states.get("sensor.four").last_updated + sensor_four_last_updated_timestamp = hass.states.get( + "sensor.four" + ).last_updated_timestamp await async_recorder_block_till_done(hass) hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) await async_wait_recording_done(hass) @@ -1885,24 +2035,38 @@ async def test_history_stream_historical_only_with_start_time_state_past( assert response == { "event": { - "end_time": sensor_four_last_updated.timestamp(), - "start_time": now.timestamp(), + "end_time": pytest.approx(sensor_four_last_updated_timestamp), + "start_time": pytest.approx(now.timestamp()), "states": { "sensor.four": [ - {"lu": sensor_four_last_updated.timestamp(), "s": "off"} + { + "lu": pytest.approx(sensor_four_last_updated_timestamp), + "s": "off", + } ], "sensor.one": [ { - "lu": now.timestamp(), + "lu": pytest.approx(now.timestamp()), "s": "first", }, # should use start time state - {"lu": sensor_one_last_updated_second.timestamp(), "s": "second"}, - {"lu": sensor_one_last_updated_third.timestamp(), "s": "third"}, + { + "lu": pytest.approx(sensor_one_last_updated_second_timestamp), + "s": "second", + }, + { + "lu": pytest.approx(sensor_one_last_updated_third_timestamp), + "s": "third", + }, ], "sensor.three": [ - {"lu": sensor_three_last_updated.timestamp(), "s": "off"} + { + "lu": pytest.approx(sensor_three_last_updated_timestamp), + "s": "off", + } + ], + "sensor.two": [ + {"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"} ], - "sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}], }, }, "id": 1, diff --git a/tests/components/logbook/test_websocket_api.py b/tests/components/logbook/test_websocket_api.py index 1fb0e6eb24b..bd11c87f4df 100644 --- a/tests/components/logbook/test_websocket_api.py +++ b/tests/components/logbook/test_websocket_api.py @@ -630,7 +630,7 @@ async def test_subscribe_unsubscribe_logbook_stream_excluded_entities( { "entity_id": "binary_sensor.is_light", "state": "off", - "when": state.last_updated.timestamp(), + "when": state.last_updated_timestamp, } ] assert msg["event"]["start_time"] == now.timestamp() @@ -679,17 +679,17 @@ async def test_subscribe_unsubscribe_logbook_stream_excluded_entities( { "entity_id": "light.alpha", "state": "off", - "when": alpha_off_state.last_updated.timestamp(), + "when": alpha_off_state.last_updated_timestamp, }, { "entity_id": "light.zulu", "state": "off", - "when": zulu_off_state.last_updated.timestamp(), + "when": zulu_off_state.last_updated_timestamp, }, { "entity_id": "light.zulu", "state": "on", - "when": zulu_on_state.last_updated.timestamp(), + "when": zulu_on_state.last_updated_timestamp, }, ] @@ -1033,7 +1033,7 @@ async def test_logbook_stream_excluded_entities_inherits_filters_from_recorder( { "entity_id": "binary_sensor.is_light", "state": "off", - "when": state.last_updated.timestamp(), + "when": state.last_updated_timestamp, } ] assert msg["event"]["start_time"] == now.timestamp() @@ -1082,17 +1082,17 @@ async def test_logbook_stream_excluded_entities_inherits_filters_from_recorder( { "entity_id": "light.alpha", "state": "off", - "when": alpha_off_state.last_updated.timestamp(), + "when": alpha_off_state.last_updated_timestamp, }, { "entity_id": "light.zulu", "state": "off", - "when": zulu_off_state.last_updated.timestamp(), + "when": zulu_off_state.last_updated_timestamp, }, { "entity_id": "light.zulu", "state": "on", - "when": zulu_on_state.last_updated.timestamp(), + "when": zulu_on_state.last_updated_timestamp, }, ] @@ -1201,7 +1201,7 @@ async def test_subscribe_unsubscribe_logbook_stream( { "entity_id": "binary_sensor.is_light", "state": "off", - "when": state.last_updated.timestamp(), + "when": state.last_updated_timestamp, } ] assert msg["event"]["start_time"] == now.timestamp() @@ -1241,17 +1241,17 @@ async def test_subscribe_unsubscribe_logbook_stream( { "entity_id": "light.alpha", "state": "off", - "when": alpha_off_state.last_updated.timestamp(), + "when": alpha_off_state.last_updated_timestamp, }, { "entity_id": "light.zulu", "state": "off", - "when": zulu_off_state.last_updated.timestamp(), + "when": zulu_off_state.last_updated_timestamp, }, { "entity_id": "light.zulu", "state": "on", - "when": zulu_on_state.last_updated.timestamp(), + "when": zulu_on_state.last_updated_timestamp, }, ] @@ -1514,7 +1514,7 @@ async def test_subscribe_unsubscribe_logbook_stream_entities( { "entity_id": "binary_sensor.is_light", "state": "off", - "when": state.last_updated.timestamp(), + "when": state.last_updated_timestamp, } ] @@ -1613,7 +1613,7 @@ async def test_subscribe_unsubscribe_logbook_stream_entities_with_end_time( { "entity_id": "binary_sensor.is_light", "state": "off", - "when": state.last_updated.timestamp(), + "when": state.last_updated_timestamp, } ] @@ -1716,7 +1716,7 @@ async def test_subscribe_unsubscribe_logbook_stream_entities_past_only( { "entity_id": "binary_sensor.is_light", "state": "off", - "when": state.last_updated.timestamp(), + "when": state.last_updated_timestamp, } ] @@ -1804,7 +1804,7 @@ async def test_subscribe_unsubscribe_logbook_stream_big_query( { "entity_id": "binary_sensor.is_light", "state": "on", - "when": current_state.last_updated.timestamp(), + "when": current_state.last_updated_timestamp, } ] @@ -1817,7 +1817,7 @@ async def test_subscribe_unsubscribe_logbook_stream_big_query( { "entity_id": "binary_sensor.four_days_ago", "state": "off", - "when": four_day_old_state.last_updated.timestamp(), + "when": four_day_old_state.last_updated_timestamp, } ] @@ -2363,7 +2363,7 @@ async def test_subscribe_disconnected( { "entity_id": "binary_sensor.is_light", "state": "off", - "when": state.last_updated.timestamp(), + "when": state.last_updated_timestamp, } ] @@ -2790,7 +2790,7 @@ async def test_logbook_stream_ignores_forced_updates( { "entity_id": "binary_sensor.is_light", "state": "off", - "when": state.last_updated.timestamp(), + "when": state.last_updated_timestamp, } ] assert msg["event"]["start_time"] == now.timestamp() diff --git a/tests/components/websocket_api/test_messages.py b/tests/components/websocket_api/test_messages.py index 6294b6a2628..cb8a026fe0d 100644 --- a/tests/components/websocket_api/test_messages.py +++ b/tests/components/websocket_api/test_messages.py @@ -96,9 +96,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: message = _state_diff_event(last_state_event) assert message == { "c": { - "light.window": { - "+": {"lc": new_state.last_changed.timestamp(), "s": "off"} - } + "light.window": {"+": {"lc": new_state.last_changed_timestamp, "s": "off"}} } } @@ -117,7 +115,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: "light.window": { "+": { "c": {"parent_id": "new-parent-id"}, - "lc": new_state.last_changed.timestamp(), + "lc": new_state.last_changed_timestamp, "s": "red", } } @@ -144,7 +142,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: "parent_id": "another-new-parent-id", "user_id": "new-user-id", }, - "lc": new_state.last_changed.timestamp(), + "lc": new_state.last_changed_timestamp, "s": "green", } } @@ -168,7 +166,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: "light.window": { "+": { "c": {"user_id": "another-new-user-id"}, - "lc": new_state.last_changed.timestamp(), + "lc": new_state.last_changed_timestamp, "s": "blue", } } @@ -194,7 +192,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: "light.window": { "+": { "c": "id-new", - "lc": new_state.last_changed.timestamp(), + "lc": new_state.last_changed_timestamp, "s": "yellow", } } @@ -216,7 +214,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: "+": { "a": {"new": "attr"}, "c": {"id": new_context.id, "parent_id": None, "user_id": None}, - "lc": new_state.last_changed.timestamp(), + "lc": new_state.last_changed_timestamp, "s": "purple", } } @@ -232,7 +230,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: assert message == { "c": { "light.window": { - "+": {"lc": new_state.last_changed.timestamp(), "s": "green"}, + "+": {"lc": new_state.last_changed_timestamp, "s": "green"}, "-": {"a": ["new"]}, } } @@ -254,7 +252,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: "light.window": { "+": { "a": {"list_attr": ["a", "b", "c", "d"], "list_attr_2": ["a", "b"]}, - "lu": new_state.last_updated.timestamp(), + "lu": new_state.last_updated_timestamp, } } } @@ -275,7 +273,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None: "light.window": { "+": { "a": {"list_attr": ["a", "b", "c", "e"]}, - "lu": new_state.last_updated.timestamp(), + "lu": new_state.last_updated_timestamp, }, "-": {"a": ["list_attr_2"]}, } diff --git a/tests/test_core.py b/tests/test_core.py index fa94b4e658c..6848d209d02 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -2834,8 +2834,32 @@ async def test_state_change_events_context_id_match_state_time( assert state.last_updated == events[0].time_fired assert len(state.context.id) == 26 # ULIDs store time to 3 decimal places compared to python timestamps - assert _ulid_timestamp(state.context.id) == int( - state.last_updated.timestamp() * 1000 + assert _ulid_timestamp(state.context.id) == int(state.last_updated_timestamp * 1000) + + +async def test_state_change_events_match_time_with_limits_of_precision( + hass: HomeAssistant, +) -> None: + """Ensure last_updated matches last_updated_timestamp within limits of precision. + + The last_updated_timestamp uses the same precision as time.time() which is + a bit better than the precision of datetime.now() which is used for last_updated + on some platforms. + """ + events = async_capture_events(hass, ha.EVENT_STATE_CHANGED) + hass.states.async_set("light.bedroom", "on") + await hass.async_block_till_done() + state: State = hass.states.get("light.bedroom") + assert state.last_updated == events[0].time_fired + assert state.last_updated_timestamp == pytest.approx( + events[0].time_fired.timestamp() + ) + assert state.last_updated_timestamp == pytest.approx(state.last_updated.timestamp()) + assert state.last_updated_timestamp == state.last_changed_timestamp + assert state.last_updated_timestamp == pytest.approx(state.last_changed.timestamp()) + assert state.last_updated_timestamp == state.last_reported_timestamp + assert state.last_updated_timestamp == pytest.approx( + state.last_reported.timestamp() )