1
mirror of https://github.com/home-assistant/core synced 2024-10-01 05:30:36 +02:00

Small speed up to frequently called datetime functions (#85399)

This commit is contained in:
J. Nick Koston 2023-01-08 09:42:29 -10:00 committed by GitHub
parent 45eb1efc6f
commit d81febd3f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 49 additions and 35 deletions

View File

@ -4,6 +4,7 @@ from __future__ import annotations
import bisect
from contextlib import suppress
import datetime as dt
from functools import partial
import platform
import re
import time
@ -98,9 +99,10 @@ def get_time_zone(time_zone_str: str) -> dt.tzinfo | None:
return None
def utcnow() -> dt.datetime:
"""Get now in UTC time."""
return dt.datetime.now(UTC)
# We use a partial here since it is implemented in native code
# and avoids the global lookup of UTC
utcnow: partial[dt.datetime] = partial(dt.datetime.now, UTC)
utcnow.__doc__ = "Get now in UTC time."
def now(time_zone: dt.tzinfo | None = None) -> dt.datetime:
@ -466,8 +468,8 @@ def _datetime_ambiguous(dattim: dt.datetime) -> bool:
return _datetime_exists(dattim) and dattim.utcoffset() != opposite_fold.utcoffset()
def __monotonic_time_coarse() -> float:
"""Return a monotonic time in seconds.
def __gen_monotonic_time_coarse() -> partial[float]:
"""Return a function that provides monotonic time in seconds.
This is the coarse version of time_monotonic, which is faster but less accurate.
@ -477,13 +479,16 @@ def __monotonic_time_coarse() -> float:
https://lore.kernel.org/lkml/20170404171826.25030-1-marc.zyngier@arm.com/
"""
return time.clock_gettime(CLOCK_MONOTONIC_COARSE)
# We use a partial here since its implementation is in native code
# which allows us to avoid the overhead of the global lookup
# of CLOCK_MONOTONIC_COARSE.
return partial(time.clock_gettime, CLOCK_MONOTONIC_COARSE)
monotonic_time_coarse = time.monotonic
with suppress(Exception):
if (
platform.system() == "Linux"
and abs(time.monotonic() - __monotonic_time_coarse()) < 1
and abs(time.monotonic() - __gen_monotonic_time_coarse()()) < 1
):
monotonic_time_coarse = __monotonic_time_coarse
monotonic_time_coarse = __gen_monotonic_time_coarse()

View File

@ -5,7 +5,7 @@ import asyncio
from collections import OrderedDict
from collections.abc import Awaitable, Callable, Collection
from contextlib import contextmanager
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
import functools as ft
from io import StringIO
import json
@ -396,7 +396,7 @@ def async_fire_time_changed_exact(
approach, as this is only for testing.
"""
if datetime_ is None:
utc_datetime = date_util.utcnow()
utc_datetime = datetime.now(timezone.utc)
else:
utc_datetime = date_util.as_utc(datetime_)
@ -418,7 +418,7 @@ def async_fire_time_changed(
for an exact microsecond, use async_fire_time_changed_exact.
"""
if datetime_ is None:
utc_datetime = date_util.utcnow()
utc_datetime = datetime.now(timezone.utc)
else:
utc_datetime = date_util.as_utc(datetime_)

View File

@ -7,7 +7,6 @@ from datetime import datetime, timedelta
import json
from unittest.mock import patch, sentinel
from freezegun import freeze_time
import pytest
from sqlalchemy import text
@ -973,6 +972,7 @@ def test_state_changes_during_period_multiple_entities_single_test(hass_recorder
hist[entity_id][0].state == value
@pytest.mark.freeze_time("2039-01-19 03:14:07.555555-00:00")
async def test_get_full_significant_states_past_year_2038(
async_setup_recorder_instance: SetupRecorderInstanceT,
hass: ha.HomeAssistant,
@ -980,29 +980,29 @@ async def test_get_full_significant_states_past_year_2038(
"""Test we can store times past year 2038."""
await async_setup_recorder_instance(hass, {})
past_2038_time = dt_util.parse_datetime("2039-01-19 03:14:07.555555-00:00")
hass.states.async_set("sensor.one", "on", {"attr": "original"})
state0 = hass.states.get("sensor.one")
await hass.async_block_till_done()
with freeze_time(past_2038_time):
hass.states.async_set("sensor.one", "on", {"attr": "original"})
state0 = hass.states.get("sensor.one")
await hass.async_block_till_done()
hass.states.async_set("sensor.one", "on", {"attr": "new"})
state1 = hass.states.get("sensor.one")
await async_wait_recording_done(hass)
hass.states.async_set("sensor.one", "on", {"attr": "new"})
state1 = hass.states.get("sensor.one")
def _get_entries():
with session_scope(hass=hass) as session:
return history.get_full_significant_states_with_session(
hass,
session,
past_2038_time - timedelta(days=365),
past_2038_time + timedelta(days=365),
entity_ids=["sensor.one"],
significant_changes_only=False,
)
await async_wait_recording_done(hass)
states = await recorder.get_instance(hass).async_add_executor_job(_get_entries)
sensor_one_states: list[State] = states["sensor.one"]
assert sensor_one_states[0] == state0
assert sensor_one_states[1] == state1
assert sensor_one_states[0].last_changed == past_2038_time
assert sensor_one_states[0].last_updated == past_2038_time
def _get_entries():
with session_scope(hass=hass) as session:
return history.get_full_significant_states_with_session(
hass,
session,
past_2038_time - timedelta(days=365),
past_2038_time + timedelta(days=365),
entity_ids=["sensor.one"],
significant_changes_only=False,
)
states = await recorder.get_instance(hass).async_add_executor_job(_get_entries)
sensor_one_states: list[State] = states["sensor.one"]
assert sensor_one_states[0] == state0
assert sensor_one_states[1] == state1
assert sensor_one_states[0].last_changed == past_2038_time
assert sensor_one_states[0].last_updated == past_2038_time

View File

@ -4,6 +4,7 @@ from __future__ import annotations
import asyncio
from collections.abc import AsyncGenerator, Callable, Generator
from contextlib import asynccontextmanager
import datetime
import functools
import gc
import itertools
@ -78,6 +79,14 @@ asyncio.set_event_loop_policy(runner.HassEventLoopPolicy(False))
asyncio.set_event_loop_policy = lambda policy: None
def _utcnow():
"""Make utcnow patchable by freezegun."""
return datetime.datetime.now(datetime.timezone.utc)
dt_util.utcnow = _utcnow
def pytest_addoption(parser):
"""Register custom pytest options."""
parser.addoption("--dburl", action="store", default="sqlite://")