diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 3e2e3a55c3e..62e9f3fab6a 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1689,16 +1689,16 @@ def random_every_time(context, values): def today_at(time_str: str = "") -> datetime: """Record fetching now where the time has been replaced with value.""" - start = dt_util.start_of_local_day(datetime.now()) + today = dt_util.start_of_local_day() + if not time_str: + return today - dttime = start.time() if time_str == "" else dt_util.parse_time(time_str) + if (time_today := dt_util.parse_time(time_str)) is None: + raise ValueError( + f"could not convert {type(time_str).__name__} to datetime: '{time_str}'" + ) - if dttime: - return datetime.combine(start.date(), dttime, tzinfo=dt_util.DEFAULT_TIME_ZONE) - - raise ValueError( - f"could not convert {type(time_str).__name__} to datetime: '{time_str}'" - ) + return datetime.combine(today, time_today, today.tzinfo) def relative_time(value): diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index a50884b71c8..bd405acf597 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -5,6 +5,7 @@ import math import random from unittest.mock import patch +from freezegun import freeze_time import pytest import voluptuous as vol @@ -1149,42 +1150,68 @@ def test_utcnow(mock_is_safe, hass): assert info.has_time is True +@pytest.mark.parametrize( + "now, expected, expected_midnight, timezone_str", + [ + # Host clock in UTC + ( + "2021-11-24 03:00:00+00:00", + "2021-11-23T10:00:00-08:00", + "2021-11-23T00:00:00-08:00", + "America/Los_Angeles", + ), + # Host clock in local time + ( + "2021-11-23 19:00:00-08:00", + "2021-11-23T10:00:00-08:00", + "2021-11-23T00:00:00-08:00", + "America/Los_Angeles", + ), + ], +) @patch( "homeassistant.helpers.template.TemplateEnvironment.is_safe_callable", return_value=True, ) -def test_today_at(mock_is_safe, hass): +def test_today_at(mock_is_safe, hass, now, expected, expected_midnight, timezone_str): """Test today_at method.""" - now = dt_util.now() - with patch("homeassistant.util.dt.now", return_value=now): - now = now.replace(hour=10, minute=0, second=0, microsecond=0) - result = template.Template( - "{{ today_at('10:00').isoformat() }}", - hass, - ).async_render() - assert result == now.isoformat() + freezer = freeze_time(now) + freezer.start() - result = template.Template( - "{{ today_at('10:00:00').isoformat() }}", - hass, - ).async_render() - assert result == now.isoformat() + original_tz = dt_util.DEFAULT_TIME_ZONE - result = template.Template( - "{{ ('10:00:00' | today_at).isoformat() }}", - hass, - ).async_render() - assert result == now.isoformat() + timezone = dt_util.get_time_zone(timezone_str) + dt_util.set_default_time_zone(timezone) - now = now.replace(hour=0) - result = template.Template( - "{{ today_at().isoformat() }}", - hass, - ).async_render() - assert result == now.isoformat() + result = template.Template( + "{{ today_at('10:00').isoformat() }}", + hass, + ).async_render() + assert result == expected - with pytest.raises(TemplateError): - template.Template("{{ today_at('bad') }}", hass).async_render() + result = template.Template( + "{{ today_at('10:00:00').isoformat() }}", + hass, + ).async_render() + assert result == expected + + result = template.Template( + "{{ ('10:00:00' | today_at).isoformat() }}", + hass, + ).async_render() + assert result == expected + + result = template.Template( + "{{ today_at().isoformat() }}", + hass, + ).async_render() + assert result == expected_midnight + + with pytest.raises(TemplateError): + template.Template("{{ today_at('bad') }}", hass).async_render() + + freezer.stop() + dt_util.set_default_time_zone(original_tz) @patch(