From ccc818266b1e9c0731e636c95a1a5ea1b2555d98 Mon Sep 17 00:00:00 2001 From: Mikael Svensson Date: Mon, 20 Apr 2020 19:29:12 +0200 Subject: [PATCH] Fix relative_time datetime object without timezone (#34273) --- homeassistant/helpers/template.py | 23 ++++++++++++++++- tests/helpers/test_template.py | 42 +++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 06bbf10a8ea..3f9924d00d5 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -918,6 +918,27 @@ def random_every_time(context, values): return random.choice(values) +def relative_time(value): + """ + Take a datetime and return its "age" as a string. + + The age can be in second, minute, hour, day, month or year. Only the + biggest unit is considered, e.g. if it's 2 days and 3 hours, "2 days" will + be returned. + Make sure date is not in the future, or else it will return None. + + If the input are not a datetime object the input will be returned unmodified. + """ + + if not isinstance(value, datetime): + return value + if not value.tzinfo: + value = dt_util.as_local(value) + if dt_util.now() < value: + return value + return dt_util.get_age(value) + + class TemplateEnvironment(ImmutableSandboxedEnvironment): """The Home Assistant template environment.""" @@ -972,7 +993,7 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): self.globals["now"] = dt_util.now self.globals["utcnow"] = dt_util.utcnow self.globals["as_timestamp"] = forgiving_as_timestamp - self.globals["relative_time"] = dt_util.get_age + self.globals["relative_time"] = relative_time self.globals["strptime"] = strptime if hass is None: return diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 6c5f2b1d371..6b3e0774bd8 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -829,6 +829,48 @@ def test_now(mock_is_safe, hass): ) +@patch( + "homeassistant.helpers.template.TemplateEnvironment.is_safe_callable", + return_value=True, +) +def test_relative_time(mock_is_safe, hass): + """Test relative_time method.""" + now = datetime.strptime("2000-01-01 10:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z") + with patch("homeassistant.util.dt.now", return_value=now): + assert ( + "1 hour" + == template.Template( + '{{relative_time(strptime("2000-01-01 09:00:00", "%Y-%m-%d %H:%M:%S"))}}', + hass, + ).async_render() + ) + assert ( + "2 hours" + == template.Template( + '{{relative_time(strptime("2000-01-01 09:00:00 +01:00", "%Y-%m-%d %H:%M:%S %z"))}}', + hass, + ).async_render() + ) + assert ( + "1 hour" + == template.Template( + '{{relative_time(strptime("2000-01-01 03:00:00 -06:00", "%Y-%m-%d %H:%M:%S %z"))}}', + hass, + ).async_render() + ) + assert ( + str(template.strptime("2000-01-01 11:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z")) + == template.Template( + '{{relative_time(strptime("2000-01-01 11:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z"))}}', + hass, + ).async_render() + ) + assert ( + "string" + == template.Template('{{relative_time("string")}}', hass,).async_render() + ) + + @patch( "homeassistant.helpers.template.TemplateEnvironment.is_safe_callable", return_value=True,