Add timestamp option for input_datetime.set_datetime (#39121)

This commit is contained in:
Phil Bruckner 2020-08-26 04:28:30 -05:00 committed by GitHub
parent 2568932c1c
commit 4ff376cdd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 96 additions and 41 deletions

View File

@ -37,8 +37,34 @@ DEFAULT_DATE = datetime.date(1970, 1, 1)
DEFAULT_TIME = datetime.time(0, 0, 0)
ATTR_DATETIME = "datetime"
ATTR_TIMESTAMP = "timestamp"
def validate_set_datetime_attrs(config):
"""Validate set_datetime service attributes."""
has_date_or_time_attr = any(key in config for key in (ATTR_DATE, ATTR_TIME))
if (
sum([has_date_or_time_attr, ATTR_DATETIME in config, ATTR_TIMESTAMP in config])
> 1
):
raise vol.Invalid(f"Cannot use together: {', '.join(config.keys())}")
return config
SERVICE_SET_DATETIME = "set_datetime"
SERVICE_SET_DATETIME_SCHEMA = vol.All(
vol.Schema(
{
vol.Optional(ATTR_DATE): cv.date,
vol.Optional(ATTR_TIME): cv.time,
vol.Optional(ATTR_DATETIME): cv.datetime,
vol.Optional(ATTR_TIMESTAMP): vol.Coerce(float),
},
extra=vol.ALLOW_EXTRA,
),
cv.has_at_least_one_key(ATTR_DATE, ATTR_TIME, ATTR_DATETIME, ATTR_TIMESTAMP),
validate_set_datetime_attrs,
)
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
@ -138,37 +164,29 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
async def async_set_datetime_service(entity, call):
"""Handle a call to the input datetime 'set datetime' service."""
time = call.data.get(ATTR_TIME)
date = call.data.get(ATTR_DATE)
time = call.data.get(ATTR_TIME)
dttm = call.data.get(ATTR_DATETIME)
if (
dttm
and (date or time)
or entity.has_date
and not (date or dttm)
or entity.has_time
and not (time or dttm)
):
_LOGGER.error(
"Invalid service data for %s input_datetime.set_datetime: %s",
entity.entity_id,
str(call.data),
)
return
tmsp = call.data.get(ATTR_TIMESTAMP)
if tmsp:
dttm = dt_util.as_local(dt_util.utc_from_timestamp(tmsp)).replace(
tzinfo=None
)
if dttm:
date = dttm.date()
time = dttm.time()
if not entity.has_date:
date = None
if not entity.has_time:
time = None
if not date and not time:
raise vol.Invalid("Nothing to set")
entity.async_set_datetime(date, time)
component.async_register_entity_service(
SERVICE_SET_DATETIME,
{
vol.Optional(ATTR_DATE): cv.date,
vol.Optional(ATTR_TIME): cv.time,
vol.Optional(ATTR_DATETIME): cv.datetime,
},
async_set_datetime_service,
SERVICE_SET_DATETIME, SERVICE_SET_DATETIME_SCHEMA, async_set_datetime_service
)
return True
@ -338,17 +356,11 @@ class InputDatetime(RestoreEntity):
@callback
def async_set_datetime(self, date_val, time_val):
"""Set a new date / time."""
if self.has_date and self.has_time and date_val and time_val:
self._current_datetime = datetime.datetime.combine(date_val, time_val)
elif self.has_date and not self.has_time and date_val:
self._current_datetime = datetime.datetime.combine(
date_val, self._current_datetime.time()
)
if self.has_time and not self.has_date and time_val:
self._current_datetime = datetime.datetime.combine(
self._current_datetime.date(), time_val
)
if not date_val:
date_val = self._current_datetime.date()
if not time_val:
time_val = self._current_datetime.time()
self._current_datetime = datetime.datetime.combine(date_val, time_val)
self.async_write_ha_state()
async def async_update_config(self, config: typing.Dict) -> None:

View File

@ -1,18 +1,21 @@
set_datetime:
description: This can be used to dynamically set the date and/or time.
description: This can be used to dynamically set the date and/or time. Use date/time, datetime or timestamp.
fields:
entity_id:
description: Entity id of the input datetime to set the new value.
example: input_datetime.test_date_time
date:
description: The target date the entity should be set to. Do not use with datetime.
description: The target date the entity should be set to.
example: '"2019-04-20"'
time:
description: The target time the entity should be set to. Do not use with datetime.
description: The target time the entity should be set to.
example: '"05:04:20"'
datetime:
description: The target date & time the entity should be set to. Do not use with date or time.
description: The target date & time the entity should be set to.
example: '"2019-04-20 05:04:20"'
timestamp:
description: The target date & time the entity should be set to as expressed by a UNIX timestamp.
example: 1598027400
reload:
description: Reload the input_datetime configuration.

View File

@ -10,6 +10,7 @@ from homeassistant.components.input_datetime import (
ATTR_DATETIME,
ATTR_EDITABLE,
ATTR_TIME,
ATTR_TIMESTAMP,
CONF_HAS_DATE,
CONF_HAS_TIME,
CONF_ID,
@ -25,6 +26,7 @@ from homeassistant.core import Context, CoreState, State
from homeassistant.exceptions import Unauthorized
from homeassistant.helpers import entity_registry
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from tests.async_mock import patch
from tests.common import mock_restore_cache
@ -92,6 +94,16 @@ async def async_set_datetime(hass, entity_id, dt_value):
)
async def async_set_timestamp(hass, entity_id, timestamp):
"""Set date and / or time of input_datetime."""
await hass.services.async_call(
DOMAIN,
SERVICE_SET_DATETIME,
{ATTR_ENTITY_ID: entity_id, ATTR_TIMESTAMP: timestamp},
blocking=True,
)
async def test_invalid_configs(hass):
"""Test config."""
invalid_configs = [
@ -156,6 +168,32 @@ async def test_set_datetime_2(hass):
assert state.attributes["timestamp"] == dt_obj.timestamp()
async def test_set_datetime_3(hass):
"""Test set_datetime method using timestamp."""
await async_setup_component(
hass, DOMAIN, {DOMAIN: {"test_datetime": {"has_time": True, "has_date": True}}}
)
entity_id = "input_datetime.test_datetime"
dt_obj = datetime.datetime(2017, 9, 7, 19, 46, 30)
await async_set_timestamp(hass, entity_id, dt_util.as_utc(dt_obj).timestamp())
state = hass.states.get(entity_id)
assert state.state == str(dt_obj)
assert state.attributes["has_time"]
assert state.attributes["has_date"]
assert state.attributes["year"] == 2017
assert state.attributes["month"] == 9
assert state.attributes["day"] == 7
assert state.attributes["hour"] == 19
assert state.attributes["minute"] == 46
assert state.attributes["second"] == 30
assert state.attributes["timestamp"] == dt_obj.timestamp()
async def test_set_datetime_time(hass):
"""Test set_datetime method with only time."""
await async_setup_component(
@ -199,7 +237,8 @@ async def test_set_invalid(hass):
await hass.services.async_call(
"input_datetime",
"set_datetime",
{"entity_id": "test_date", "time": time_portion},
{"entity_id": entity_id, "time": time_portion},
blocking=True,
)
await hass.async_block_till_done()
@ -229,7 +268,8 @@ async def test_set_invalid_2(hass):
await hass.services.async_call(
"input_datetime",
"set_datetime",
{"entity_id": "test_date", "time": time_portion, "datetime": dt_obj},
{"entity_id": entity_id, "time": time_portion, "datetime": dt_obj},
blocking=True,
)
await hass.async_block_till_done()
@ -358,8 +398,8 @@ async def test_input_datetime_context(hass, hass_admin_user):
"input_datetime",
"set_datetime",
{"entity_id": state.entity_id, "date": "2018-01-02"},
True,
Context(user_id=hass_admin_user.id),
blocking=True,
context=Context(user_id=hass_admin_user.id),
)
state2 = hass.states.get("input_datetime.only_date")