diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 9be584403bd..e5b113f8a4d 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -86,7 +86,7 @@ import homeassistant.util.dt as dt_util # pylint: disable=invalid-name -TIME_PERIOD_ERROR = "offset {} should be format 'HH:MM' or 'HH:MM:SS'" +TIME_PERIOD_ERROR = "offset {} should be format 'HH:MM', 'HH:MM:SS' or 'HH:MM:SS.F'" # Home Assistant types byte = vol.All(vol.Coerce(int), vol.Range(min=0, max=255)) @@ -299,11 +299,11 @@ time_period_dict = vol.All( dict, vol.Schema( { - "days": vol.Coerce(int), - "hours": vol.Coerce(int), - "minutes": vol.Coerce(int), - "seconds": vol.Coerce(int), - "milliseconds": vol.Coerce(int), + "days": vol.Coerce(float), + "hours": vol.Coerce(float), + "minutes": vol.Coerce(float), + "seconds": vol.Coerce(float), + "milliseconds": vol.Coerce(float), } ), has_at_least_one_key("days", "hours", "minutes", "seconds", "milliseconds"), @@ -357,17 +357,17 @@ def time_period_str(value: str) -> timedelta: elif value.startswith("+"): value = value[1:] - try: - parsed = [int(x) for x in value.split(":")] - except ValueError: + parsed = value.split(":") + if len(parsed) not in (2, 3): raise vol.Invalid(TIME_PERIOD_ERROR.format(value)) - - if len(parsed) == 2: - hour, minute = parsed - second = 0 - elif len(parsed) == 3: - hour, minute, second = parsed - else: + try: + hour = int(parsed[0]) + minute = int(parsed[1]) + try: + second = float(parsed[2]) + except IndexError: + second = 0 + except ValueError: raise vol.Invalid(TIME_PERIOD_ERROR.format(value)) offset = timedelta(hours=hour, minutes=minute, seconds=second) @@ -378,10 +378,10 @@ def time_period_str(value: str) -> timedelta: return offset -def time_period_seconds(value: Union[int, str]) -> timedelta: +def time_period_seconds(value: Union[float, str]) -> timedelta: """Validate and transform seconds to a time offset.""" try: - return timedelta(seconds=int(value)) + return timedelta(seconds=float(value)) except (ValueError, TypeError): raise vol.Invalid(f"Expected seconds, got {value}") diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index e6c3757ec55..7da0557c9eb 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -260,18 +260,49 @@ def test_time_period(): """Test time_period validation.""" schema = vol.Schema(cv.time_period) - options = (None, "", "hello:world", "12:", "12:34:56:78", {}, {"wrong_key": -10}) + options = ( + None, + "", + "hello:world", + "12:", + "12:34:56:78", + {}, + {"wrong_key": -10}, + "12.5:30", + "12:30.5", + "12.5:30:30", + "12:30.5:30", + ) for value in options: with pytest.raises(vol.MultipleInvalid): schema(value) - options = ("8:20", "23:59", "-8:20", "-23:59:59", "-48:00", {"minutes": 5}, 1, "5") - for value in options: - schema(value) - - assert timedelta(seconds=180) == schema("180") - assert timedelta(hours=23, minutes=59) == schema("23:59") - assert -1 * timedelta(hours=1, minutes=15) == schema("-1:15") + options = ( + ("8:20", timedelta(hours=8, minutes=20)), + ("23:59", timedelta(hours=23, minutes=59)), + ("-8:20", -1 * timedelta(hours=8, minutes=20)), + ("-1:15", -1 * timedelta(hours=1, minutes=15)), + ("-23:59:59", -1 * timedelta(hours=23, minutes=59, seconds=59)), + ("-48:00", -1 * timedelta(days=2)), + ({"minutes": 5}, timedelta(minutes=5)), + (1, timedelta(seconds=1)), + ("5", timedelta(seconds=5)), + ("180", timedelta(seconds=180)), + ("00:08:20.5", timedelta(minutes=8, seconds=20, milliseconds=500)), + ("00:23:59.999", timedelta(minutes=23, seconds=59, milliseconds=999)), + ("-00:08:20.5", -1 * timedelta(minutes=8, seconds=20, milliseconds=500)), + ( + "-12:59:59.999", + -1 * timedelta(hours=12, minutes=59, seconds=59, milliseconds=999), + ), + ({"milliseconds": 1.5}, timedelta(milliseconds=1, microseconds=500)), + ({"seconds": "1.5"}, timedelta(seconds=1, milliseconds=500)), + ({"minutes": "1.5"}, timedelta(minutes=1, seconds=30)), + ({"hours": -1.5}, -1 * timedelta(hours=1, minutes=30)), + ({"days": "-1.5"}, -1 * timedelta(days=1, hours=12)), + ) + for value, result in options: + assert schema(value) == result def test_remove_falsy():