diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 7c1113bdda8..8d837bc9bc6 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1836,14 +1836,24 @@ def forgiving_as_timestamp(value, default=_SENTINEL): return default -def as_datetime(value): +def as_datetime(value: Any, default: Any = _SENTINEL) -> Any: """Filter and to convert a time string or UNIX timestamp to datetime object.""" try: # Check for a valid UNIX timestamp string, int or float timestamp = float(value) return dt_util.utc_from_timestamp(timestamp) - except ValueError: - return dt_util.parse_datetime(value) + except (ValueError, TypeError): + # Try to parse datetime string to datetime object + try: + return dt_util.parse_datetime(value, raise_on_error=True) + except (ValueError, TypeError): + if default is _SENTINEL: + # Return None on string input + # to ensure backwards compatibility with HA Core 2024.1 and before. + if isinstance(value, str): + return None + raise_no_default("as_datetime", value) + return default def as_timedelta(value: str) -> timedelta | None: diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 90af925ddca..955cd2fd65e 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -1151,7 +1151,6 @@ def test_as_datetime(hass: HomeAssistant, input) -> None: expected = dt_util.parse_datetime(input) if expected is not None: expected = str(expected) - assert ( template.Template(f"{{{{ as_datetime('{input}') }}}}", hass).async_render() == expected @@ -1162,34 +1161,64 @@ def test_as_datetime(hass: HomeAssistant, input) -> None: ) -def test_as_datetime_from_timestamp(hass: HomeAssistant) -> None: - """Test converting a UNIX timestamp to a date object.""" - tests = [ +@pytest.mark.parametrize( + ("input", "output"), + [ (1469119144, "2016-07-21 16:39:04+00:00"), (1469119144.0, "2016-07-21 16:39:04+00:00"), (-1, "1969-12-31 23:59:59+00:00"), - ] - for input, output in tests: - # expected = dt_util.parse_datetime(input) - if output is not None: - output = str(output) + ], +) +def test_as_datetime_from_timestamp( + hass: HomeAssistant, + input: int | float, + output: str, +) -> None: + """Test converting a UNIX timestamp to a date object.""" + assert ( + template.Template(f"{{{{ as_datetime({input}) }}}}", hass).async_render() + == output + ) + assert ( + template.Template(f"{{{{ {input} | as_datetime }}}}", hass).async_render() + == output + ) + assert ( + template.Template(f"{{{{ as_datetime('{input}') }}}}", hass).async_render() + == output + ) + assert ( + template.Template(f"{{{{ '{input}' | as_datetime }}}}", hass).async_render() + == output + ) - assert ( - template.Template(f"{{{{ as_datetime({input}) }}}}", hass).async_render() - == output - ) - assert ( - template.Template(f"{{{{ {input} | as_datetime }}}}", hass).async_render() - == output - ) - assert ( - template.Template(f"{{{{ as_datetime('{input}') }}}}", hass).async_render() - == output - ) - assert ( - template.Template(f"{{{{ '{input}' | as_datetime }}}}", hass).async_render() - == output - ) + +@pytest.mark.parametrize( + ("input", "default", "output"), + [ + (1469119144, 123, "2016-07-21 16:39:04+00:00"), + ('"invalid"', ["default output"], ["default output"]), + (["a", "list"], 0, 0), + ({"a": "dict"}, None, None), + ], +) +def test_as_datetime_default( + hass: HomeAssistant, input: Any, default: Any, output: str +) -> None: + """Test invalid input and return default value.""" + + assert ( + template.Template( + f"{{{{ as_datetime({input}, default={default}) }}}}", hass + ).async_render() + == output + ) + assert ( + template.Template( + f"{{{{ {input} | as_datetime({default}) }}}}", hass + ).async_render() + == output + ) def test_as_local(hass: HomeAssistant) -> None: