1 """Support to select a date and/or a time."""
3 from __future__
import annotations
5 import datetime
as py_datetime
7 from typing
import Any, Self
9 import voluptuous
as vol
30 _LOGGER = logging.getLogger(__name__)
32 DOMAIN =
"input_datetime"
34 CONF_HAS_DATE =
"has_date"
35 CONF_HAS_TIME =
"has_time"
36 CONF_INITIAL =
"initial"
38 DEFAULT_TIME = py_datetime.time(0, 0, 0)
40 ATTR_DATETIME =
"datetime"
41 ATTR_TIMESTAMP =
"timestamp"
45 FMT_DATETIME = f
"{FMT_DATE} {FMT_TIME}"
49 """Validate set_datetime service attributes."""
50 has_date_or_time_attr = any(key
in config
for key
in (ATTR_DATE, ATTR_TIME))
52 sum([has_date_or_time_attr, ATTR_DATETIME
in config, ATTR_TIMESTAMP
in config])
55 raise vol.Invalid(f
"Cannot use together: {', '.join(config.keys())}")
62 STORAGE_FIELDS: VolDictType = {
63 vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)),
64 vol.Optional(CONF_HAS_DATE, default=
False): cv.boolean,
65 vol.Optional(CONF_HAS_TIME, default=
False): cv.boolean,
66 vol.Optional(CONF_ICON): cv.icon,
67 vol.Optional(CONF_INITIAL): cv.string,
72 """Check at least date or time is true."""
73 if conf[CONF_HAS_DATE]
or conf[CONF_HAS_TIME]:
76 raise vol.Invalid(
"Entity needs at least a date or a time")
80 """Check the initial value is valid."""
81 if not (conf.get(CONF_INITIAL)):
90 """Check the initial value is valid."""
91 initial: str = conf[CONF_INITIAL]
93 if conf[CONF_HAS_DATE]
and conf[CONF_HAS_TIME]:
94 if (datetime := dt_util.parse_datetime(initial))
is not None:
96 raise vol.Invalid(f
"Initial value '{initial}' can't be parsed as a datetime")
98 if conf[CONF_HAS_DATE]:
99 if (date := dt_util.parse_date(initial))
is not None:
100 return py_datetime.datetime.combine(date, DEFAULT_TIME)
101 raise vol.Invalid(f
"Initial value '{initial}' can't be parsed as a date")
103 if (time := dt_util.parse_time(initial))
is not None:
104 return py_datetime.datetime.combine(py_datetime.date.today(), time)
105 raise vol.Invalid(f
"Initial value '{initial}' can't be parsed as a time")
108 CONFIG_SCHEMA = vol.Schema(
110 DOMAIN: cv.schema_with_slug_keys(
113 vol.Optional(CONF_NAME): cv.string,
114 vol.Optional(CONF_HAS_DATE, default=
False): cv.boolean,
115 vol.Optional(CONF_HAS_TIME, default=
False): cv.boolean,
116 vol.Optional(CONF_ICON): cv.icon,
117 vol.Optional(CONF_INITIAL): cv.string,
124 extra=vol.ALLOW_EXTRA,
126 RELOAD_SERVICE_SCHEMA = vol.Schema({})
129 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
130 """Set up an input datetime."""
131 component = EntityComponent[InputDatetime](_LOGGER, DOMAIN, hass)
133 id_manager = collection.IDManager()
135 yaml_collection = collection.YamlCollection(
136 logging.getLogger(f
"{__name__}.yaml_collection"), id_manager
138 collection.sync_entity_lifecycle(
139 hass, DOMAIN, DOMAIN, component, yaml_collection, InputDatetime
143 Store(hass, STORAGE_VERSION, STORAGE_KEY),
146 collection.sync_entity_lifecycle(
147 hass, DOMAIN, DOMAIN, component, storage_collection, InputDatetime
150 await yaml_collection.async_load(
151 [{CONF_ID: id_, **cfg}
for id_, cfg
in config.get(DOMAIN, {}).items()]
153 await storage_collection.async_load()
155 collection.DictStorageCollectionWebsocket(
156 storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
159 async
def reload_service_handler(service_call: ServiceCall) ->
None:
160 """Reload yaml entities."""
161 conf = await component.async_prepare_reload(skip_reset=
True)
164 await yaml_collection.async_load(
165 [{CONF_ID: id_, **cfg}
for id_, cfg
in conf.get(DOMAIN, {}).items()]
172 reload_service_handler,
173 schema=RELOAD_SERVICE_SCHEMA,
176 component.async_register_entity_service(
179 cv.make_entity_service_schema(
181 vol.Optional(ATTR_DATE): cv.date,
182 vol.Optional(ATTR_TIME): cv.time,
183 vol.Optional(ATTR_DATETIME): cv.datetime,
184 vol.Optional(ATTR_TIMESTAMP): vol.Coerce(float),
187 cv.has_at_least_one_key(
188 ATTR_DATE, ATTR_TIME, ATTR_DATETIME, ATTR_TIMESTAMP
190 validate_set_datetime_attrs,
192 "async_set_datetime",
199 """Input storage based collection."""
201 CREATE_UPDATE_SCHEMA = vol.Schema(vol.All(STORAGE_FIELDS, has_date_or_time))
204 """Validate the config is valid."""
209 """Suggest an ID based on the config."""
210 return info[CONF_NAME]
213 """Return a new updated data object."""
215 return {CONF_ID: item[CONF_ID]} | update_data
219 """Representation of a datetime input."""
221 _unrecorded_attributes = frozenset({ATTR_EDITABLE, CONF_HAS_DATE, CONF_HAS_TIME})
223 _attr_should_poll =
False
227 """Initialize a select input."""
231 if not config.get(CONF_INITIAL):
237 if current_datetime.tzinfo
is not None:
239 dt_util.get_default_time_zone()
243 tzinfo=dt_util.get_default_time_zone()
248 """Return entity instance initialized from storage."""
249 input_dt = cls(config)
250 input_dt.editable =
True
255 """Return entity instance initialized from yaml."""
256 input_dt = cls(config)
257 input_dt.entity_id = f
"{DOMAIN}.{config[CONF_ID]}"
258 input_dt.editable =
False
262 """Run when entity about to be added."""
269 default_value = py_datetime.datetime.today().strftime(f
"{FMT_DATE} 00:00:00")
277 date_time = dt_util.parse_datetime(old_state.state)
278 if date_time
is None:
279 current_datetime = dt_util.parse_datetime(default_value)
281 current_datetime = date_time
284 if (date := dt_util.parse_date(old_state.state))
is None:
285 current_datetime = dt_util.parse_datetime(default_value)
287 current_datetime = py_datetime.datetime.combine(date, DEFAULT_TIME)
289 elif (time := dt_util.parse_time(old_state.state))
is None:
290 current_datetime = dt_util.parse_datetime(default_value)
292 current_datetime = py_datetime.datetime.combine(
293 py_datetime.date.today(), time
297 tzinfo=dt_util.get_default_time_zone()
302 """Return the name of the select input."""
307 """Return True if entity has date."""
308 return self.
_config_config[CONF_HAS_DATE]
312 """Return True if entity has time."""
313 return self.
_config_config[CONF_HAS_TIME]
317 """Return the icon to be used for this entity."""
322 """Return the state of the component."""
336 """Return the capability attributes."""
338 CONF_HAS_DATE: self.
has_datehas_date,
339 CONF_HAS_TIME: self.
has_timehas_time,
344 """Return the state attributes."""
346 ATTR_EDITABLE: self.editable,
363 attrs[
"timestamp"] = (
370 extended = py_datetime.datetime.combine(
373 attrs[
"timestamp"] = extended.timestamp()
382 """Return unique id of the entity."""
383 return self.
_config_config[CONF_ID]
387 """Set a new date / time."""
389 datetime = dt_util.as_local(dt_util.utc_from_timestamp(timestamp))
392 date = datetime.date()
393 time = datetime.time()
401 if not date
and not time:
402 raise vol.Invalid(
"Nothing to set")
411 date, time, dt_util.get_default_time_zone()
416 """Handle when the config is updated."""
None async_write_ha_state(self)
State|None async_get_last_state(self)
web.Response get(self, web.Request request, str config_key)
bool time(HomeAssistant hass, dt_time|str|None before=None, dt_time|str|None after=None, str|Container[str]|None weekday=None)
None async_register_admin_service(HomeAssistant hass, str domain, str service, Callable[[ServiceCall], Awaitable[None]|None] service_func, VolSchemaType schema=vol.Schema({}, extra=vol.PREVENT_EXTRA))