diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index a467d952683..d3020b8d6d8 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -16,7 +16,9 @@ from homeassistant.components import zone as zone_cmp from homeassistant.components.device_automation import ( async_get_device_automation_platform, ) +from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP from homeassistant.const import ( + ATTR_DEVICE_CLASS, ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, @@ -736,11 +738,24 @@ def time( after_entity = hass.states.get(after) if not after_entity: raise ConditionErrorMessage("time", f"unknown 'after' entity {after}") - after = dt_util.dt.time( - after_entity.attributes.get("hour", 23), - after_entity.attributes.get("minute", 59), - after_entity.attributes.get("second", 59), - ) + if after_entity.domain == "input_datetime": + after = dt_util.dt.time( + after_entity.attributes.get("hour", 23), + after_entity.attributes.get("minute", 59), + after_entity.attributes.get("second", 59), + ) + elif after_entity.attributes.get( + ATTR_DEVICE_CLASS + ) == DEVICE_CLASS_TIMESTAMP and after_entity.state not in ( + STATE_UNAVAILABLE, + STATE_UNKNOWN, + ): + after_datetime = dt_util.parse_datetime(after_entity.state) + if after_datetime is None: + return False + after = dt_util.as_local(after_datetime).time() + else: + return False if before is None: before = dt_util.dt.time(23, 59, 59, 999999) @@ -748,11 +763,24 @@ def time( before_entity = hass.states.get(before) if not before_entity: raise ConditionErrorMessage("time", f"unknown 'before' entity {before}") - before = dt_util.dt.time( - before_entity.attributes.get("hour", 23), - before_entity.attributes.get("minute", 59), - before_entity.attributes.get("second", 59), - ) + if before_entity.domain == "input_datetime": + before = dt_util.dt.time( + before_entity.attributes.get("hour", 23), + before_entity.attributes.get("minute", 59), + before_entity.attributes.get("second", 59), + ) + elif before_entity.attributes.get( + ATTR_DEVICE_CLASS + ) == DEVICE_CLASS_TIMESTAMP and before_entity.state not in ( + STATE_UNAVAILABLE, + STATE_UNKNOWN, + ): + before_timedatime = dt_util.parse_datetime(before_entity.state) + if before_timedatime is None: + return False + before = dt_util.as_local(before_timedatime).time() + else: + return False if after < before: condition_trace_update_result(after=after, now_time=now_time, before=before) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 88f38f0a688..324173ed2e8 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1014,8 +1014,12 @@ TIME_CONDITION_SCHEMA = vol.All( { **CONDITION_BASE_SCHEMA, vol.Required(CONF_CONDITION): "time", - "before": vol.Any(time, vol.All(str, entity_domain("input_datetime"))), - "after": vol.Any(time, vol.All(str, entity_domain("input_datetime"))), + "before": vol.Any( + time, vol.All(str, entity_domain(["input_datetime", "sensor"])) + ), + "after": vol.Any( + time, vol.All(str, entity_domain(["input_datetime", "sensor"])) + ), "weekday": weekdays, } ), diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 973196d29f8..d75dd53bbf2 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -6,7 +6,8 @@ import pytest from homeassistant.components import sun import homeassistant.components.automation as automation -from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET +from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP +from homeassistant.const import ATTR_DEVICE_CLASS, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET from homeassistant.exceptions import ConditionError, HomeAssistantError from homeassistant.helpers import condition, trace from homeassistant.helpers.template import Template @@ -826,6 +827,90 @@ async def test_time_using_input_datetime(hass): condition.time(hass, before="input_datetime.not_existing") +async def test_time_using_sensor(hass): + """Test time conditions using sensor entities.""" + hass.states.async_set( + "sensor.am", + "2021-06-03 13:00:00.000000+00:00", # 6 am local time + {ATTR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP}, + ) + hass.states.async_set( + "sensor.pm", + "2020-06-01 01:00:00.000000+00:00", # 6 pm local time + {ATTR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP}, + ) + hass.states.async_set( + "sensor.no_device_class", + "2020-06-01 01:00:00.000000+00:00", + ) + hass.states.async_set( + "sensor.invalid_timestamp", + "This is not a timestamp", + {ATTR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP}, + ) + + with patch( + "homeassistant.helpers.condition.dt_util.now", + return_value=dt_util.now().replace(hour=3), + ): + assert not condition.time(hass, after="sensor.am", before="sensor.pm") + assert condition.time(hass, after="sensor.pm", before="sensor.am") + + with patch( + "homeassistant.helpers.condition.dt_util.now", + return_value=dt_util.now().replace(hour=9), + ): + assert condition.time(hass, after="sensor.am", before="sensor.pm") + assert not condition.time(hass, after="sensor.pm", before="sensor.am") + + with patch( + "homeassistant.helpers.condition.dt_util.now", + return_value=dt_util.now().replace(hour=15), + ): + assert condition.time(hass, after="sensor.am", before="sensor.pm") + assert not condition.time(hass, after="sensor.pm", before="sensor.am") + + with patch( + "homeassistant.helpers.condition.dt_util.now", + return_value=dt_util.now().replace(hour=21), + ): + assert not condition.time(hass, after="sensor.am", before="sensor.pm") + assert condition.time(hass, after="sensor.pm", before="sensor.am") + + # Trigger on PM time + with patch( + "homeassistant.helpers.condition.dt_util.now", + return_value=dt_util.now().replace(hour=18, minute=0, second=0), + ): + assert condition.time(hass, after="sensor.pm", before="sensor.am") + assert not condition.time(hass, after="sensor.am", before="sensor.pm") + assert condition.time(hass, after="sensor.pm") + assert not condition.time(hass, before="sensor.pm") + + # Even though valid, the device class is missing + assert not condition.time(hass, after="sensor.no_device_class") + assert not condition.time(hass, before="sensor.no_device_class") + + # Trigger on AM time + with patch( + "homeassistant.helpers.condition.dt_util.now", + return_value=dt_util.now().replace(hour=6, minute=0, second=0), + ): + assert not condition.time(hass, after="sensor.pm", before="sensor.am") + assert condition.time(hass, after="sensor.am", before="sensor.pm") + assert condition.time(hass, after="sensor.am") + assert not condition.time(hass, before="sensor.am") + + assert not condition.time(hass, after="sensor.invalid_timestamp") + assert not condition.time(hass, before="sensor.invalid_timestamp") + + with pytest.raises(ConditionError): + condition.time(hass, after="sensor.not_existing") + + with pytest.raises(ConditionError): + condition.time(hass, before="sensor.not_existing") + + async def test_state_raises(hass): """Test that state raises ConditionError on errors.""" # No entity