From d81febd3f4e8bfe6da0caa12403cf04bffe0abb9 Mon Sep 17 00:00:00 2001
From: "J. Nick Koston" <nick@koston.org>
Date: Sun, 8 Jan 2023 09:42:29 -1000
Subject: [PATCH] Small speed up to frequently called datetime functions
 (#85399)

---
 homeassistant/util/dt.py                  | 21 ++++++----
 tests/common.py                           |  6 +--
 tests/components/recorder/test_history.py | 50 +++++++++++------------
 tests/conftest.py                         |  9 ++++
 4 files changed, 50 insertions(+), 36 deletions(-)

diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py
index 44e4403d689..3e9ae088296 100644
--- a/homeassistant/util/dt.py
+++ b/homeassistant/util/dt.py
@@ -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()
diff --git a/tests/common.py b/tests/common.py
index eb7c7ba63ab..eaa31851f0c 100644
--- a/tests/common.py
+++ b/tests/common.py
@@ -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_)
 
diff --git a/tests/components/recorder/test_history.py b/tests/components/recorder/test_history.py
index 913ae3d8bf6..0465c10a8d2 100644
--- a/tests/components/recorder/test_history.py
+++ b/tests/components/recorder/test_history.py
@@ -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)
-
-        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,
-                )
+    hass.states.async_set("sensor.one", "on", {"attr": "new"})
+    state1 = hass.states.get("sensor.one")
+
+    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
diff --git a/tests/conftest.py b/tests/conftest.py
index 307f6626ba8..75655cf2d86 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -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://")
-- 
GitLab