Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions homeassistant/util/dt.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import bisect
from contextlib import suppress
import datetime as dt
from functools import partial
import platform
import re
import time
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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.

Expand All @@ -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()
6 changes: 3 additions & 3 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
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
Expand Down Expand Up @@ -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_)

Expand All @@ -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_)

Expand Down
50 changes: 25 additions & 25 deletions tests/components/recorder/test_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import json
from unittest.mock import patch, sentinel

from freezegun import freeze_time
import pytest
from sqlalchemy import text

Expand Down Expand Up @@ -973,36 +972,37 @@ 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,
):
"""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
9 changes: 9 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import asyncio
from collections.abc import AsyncGenerator, Callable, Generator
from contextlib import asynccontextmanager
import datetime
import functools
import gc
import itertools
Expand Down Expand Up @@ -78,6 +79,14 @@
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
Comment thread
bdraco marked this conversation as resolved.


def pytest_addoption(parser):
"""Register custom pytest options."""
parser.addoption("--dburl", action="store", default="sqlite://")
Expand Down