Small speed up to frequently called datetime functions (#85399)
This commit is contained in:
parent
45eb1efc6f
commit
d81febd3f4
4 changed files with 49 additions and 35 deletions
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
||||||
import bisect
|
import bisect
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
|
from functools import partial
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
@ -98,9 +99,10 @@ def get_time_zone(time_zone_str: str) -> dt.tzinfo | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def utcnow() -> dt.datetime:
|
# We use a partial here since it is implemented in native code
|
||||||
"""Get now in UTC time."""
|
# and avoids the global lookup of UTC
|
||||||
return dt.datetime.now(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:
|
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()
|
return _datetime_exists(dattim) and dattim.utcoffset() != opposite_fold.utcoffset()
|
||||||
|
|
||||||
|
|
||||||
def __monotonic_time_coarse() -> float:
|
def __gen_monotonic_time_coarse() -> partial[float]:
|
||||||
"""Return a monotonic time in seconds.
|
"""Return a function that provides monotonic time in seconds.
|
||||||
|
|
||||||
This is the coarse version of time_monotonic, which is faster but less accurate.
|
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/
|
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
|
monotonic_time_coarse = time.monotonic
|
||||||
with suppress(Exception):
|
with suppress(Exception):
|
||||||
if (
|
if (
|
||||||
platform.system() == "Linux"
|
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()
|
||||||
|
|
|
@ -5,7 +5,7 @@ import asyncio
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from collections.abc import Awaitable, Callable, Collection
|
from collections.abc import Awaitable, Callable, Collection
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
import functools as ft
|
import functools as ft
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
import json
|
import json
|
||||||
|
@ -396,7 +396,7 @@ def async_fire_time_changed_exact(
|
||||||
approach, as this is only for testing.
|
approach, as this is only for testing.
|
||||||
"""
|
"""
|
||||||
if datetime_ is None:
|
if datetime_ is None:
|
||||||
utc_datetime = date_util.utcnow()
|
utc_datetime = datetime.now(timezone.utc)
|
||||||
else:
|
else:
|
||||||
utc_datetime = date_util.as_utc(datetime_)
|
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.
|
for an exact microsecond, use async_fire_time_changed_exact.
|
||||||
"""
|
"""
|
||||||
if datetime_ is None:
|
if datetime_ is None:
|
||||||
utc_datetime = date_util.utcnow()
|
utc_datetime = datetime.now(timezone.utc)
|
||||||
else:
|
else:
|
||||||
utc_datetime = date_util.as_utc(datetime_)
|
utc_datetime = date_util.as_utc(datetime_)
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ from datetime import datetime, timedelta
|
||||||
import json
|
import json
|
||||||
from unittest.mock import patch, sentinel
|
from unittest.mock import patch, sentinel
|
||||||
|
|
||||||
from freezegun import freeze_time
|
|
||||||
import pytest
|
import pytest
|
||||||
from sqlalchemy import text
|
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
|
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 def test_get_full_significant_states_past_year_2038(
|
||||||
async_setup_recorder_instance: SetupRecorderInstanceT,
|
async_setup_recorder_instance: SetupRecorderInstanceT,
|
||||||
hass: ha.HomeAssistant,
|
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."""
|
"""Test we can store times past year 2038."""
|
||||||
await async_setup_recorder_instance(hass, {})
|
await async_setup_recorder_instance(hass, {})
|
||||||
past_2038_time = dt_util.parse_datetime("2039-01-19 03:14:07.555555-00:00")
|
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": "new"})
|
||||||
hass.states.async_set("sensor.one", "on", {"attr": "original"})
|
state1 = hass.states.get("sensor.one")
|
||||||
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)
|
|
||||||
|
|
||||||
def _get_entries():
|
await async_wait_recording_done(hass)
|
||||||
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)
|
def _get_entries():
|
||||||
sensor_one_states: list[State] = states["sensor.one"]
|
with session_scope(hass=hass) as session:
|
||||||
assert sensor_one_states[0] == state0
|
return history.get_full_significant_states_with_session(
|
||||||
assert sensor_one_states[1] == state1
|
hass,
|
||||||
assert sensor_one_states[0].last_changed == past_2038_time
|
session,
|
||||||
assert sensor_one_states[0].last_updated == past_2038_time
|
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
|
||||||
|
|
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import AsyncGenerator, Callable, Generator
|
from collections.abc import AsyncGenerator, Callable, Generator
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
import datetime
|
||||||
import functools
|
import functools
|
||||||
import gc
|
import gc
|
||||||
import itertools
|
import itertools
|
||||||
|
@ -78,6 +79,14 @@ asyncio.set_event_loop_policy(runner.HassEventLoopPolicy(False))
|
||||||
asyncio.set_event_loop_policy = lambda policy: None
|
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):
|
def pytest_addoption(parser):
|
||||||
"""Register custom pytest options."""
|
"""Register custom pytest options."""
|
||||||
parser.addoption("--dburl", action="store", default="sqlite://")
|
parser.addoption("--dburl", action="store", default="sqlite://")
|
||||||
|
|
Loading…
Add table
Reference in a new issue