1 """Support for representing current time of the day as binary sensors."""
3 from __future__
import annotations
5 from collections.abc
import Callable
6 from datetime
import datetime, time, timedelta
8 from typing
import Any, Literal, TypeGuard
10 import voluptuous
as vol
13 PLATFORM_SCHEMA
as BINARY_SENSOR_PLATFORM_SCHEMA,
39 type SunEventType = Literal[
"sunrise",
"sunset"]
41 _LOGGER = logging.getLogger(__name__)
44 ATTR_BEFORE =
"before"
45 ATTR_NEXT_UPDATE =
"next_update"
47 PLATFORM_SCHEMA = BINARY_SENSOR_PLATFORM_SCHEMA.extend(
49 vol.Required(CONF_AFTER): vol.Any(cv.time, vol.All(vol.Lower, cv.sun_event)),
50 vol.Required(CONF_BEFORE): vol.Any(cv.time, vol.All(vol.Lower, cv.sun_event)),
51 vol.Required(CONF_NAME): cv.string,
52 vol.Optional(CONF_AFTER_OFFSET, default=
timedelta(0)): cv.time_period,
53 vol.Optional(CONF_BEFORE_OFFSET, default=
timedelta(0)): cv.time_period,
54 vol.Optional(CONF_UNIQUE_ID): cv.string,
61 config_entry: ConfigEntry,
62 async_add_entities: AddEntitiesCallback,
64 """Initialize Times of the Day config entry."""
65 if hass.config.time_zone
is None:
66 _LOGGER.error(
"Timezone is not set in Home Assistant configuration")
69 after = cv.time(config_entry.options[CONF_AFTER_TIME])
71 before = cv.time(config_entry.options[CONF_BEFORE_TIME])
73 name = config_entry.title
74 unique_id = config_entry.entry_id
77 [
TodSensor(name, after, after_offset, before, before_offset, unique_id)]
84 async_add_entities: AddEntitiesCallback,
85 discovery_info: DiscoveryInfoType |
None =
None,
87 """Set up the ToD sensors."""
88 if hass.config.time_zone
is None:
89 _LOGGER.error(
"Timezone is not set in Home Assistant configuration")
92 after = config[CONF_AFTER]
93 after_offset = config[CONF_AFTER_OFFSET]
94 before = config[CONF_BEFORE]
95 before_offset = config[CONF_BEFORE_OFFSET]
96 name = config[CONF_NAME]
97 unique_id = config.get(CONF_UNIQUE_ID)
98 sensor =
TodSensor(name, after, after_offset, before, before_offset, unique_id)
103 def _is_sun_event(sun_event: time | SunEventType) -> TypeGuard[SunEventType]:
104 """Return true if event is sun event not time."""
105 return sun_event
in (SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET)
109 """Time of the Day Sensor."""
111 _attr_should_poll =
False
112 _time_before: datetime
113 _time_after: datetime
114 _next_update: datetime
120 after_offset: timedelta,
122 before_offset: timedelta,
123 unique_id: str |
None,
125 """Init the ToD Sensor..."""
132 self.
_unsub_update_unsub_update: Callable[[],
None] |
None =
None
136 """Return True is sensor is on."""
143 """Return the state attributes of the sensor."""
144 if time_zone := dt_util.get_default_time_zone():
146 ATTR_AFTER: self.
_time_after_time_after.astimezone(time_zone).isoformat(),
147 ATTR_BEFORE: self.
_time_before_time_before.astimezone(time_zone).isoformat(),
148 ATTR_NEXT_UPDATE: self.
_next_update_next_update.astimezone(time_zone).isoformat(),
153 """Convert naive time from config to utc_datetime with current day."""
155 current_local_date = (
156 dt_util.utcnow().astimezone(dt_util.get_default_time_zone()).
date()
159 return dt_util.as_utc(datetime.combine(current_local_date, naive_time))
162 """Calculate internal absolute time boundaries."""
163 nowutc = dt_util.utcnow()
188 if before_event_date < after_event_date:
191 self.
hasshass, self.
_before_before, after_event_date
198 if before_event_date < after_event_date + self.
_after_offset_after_offset:
224 """Add 24 hours (1 day) but account for DST."""
225 tentative_new_date = a_date +
timedelta(days=1)
226 tentative_new_date = dt_util.as_local(tentative_new_date)
227 tentative_new_date = tentative_new_date.replace(
228 hour=target_time.hour, minute=target_time.minute
231 return dt_util.find_next_time_expression_time(
233 dt_util.parse_time_expression(
"*", 0, 59),
234 dt_util.parse_time_expression(
"*", 0, 59),
235 dt_util.parse_time_expression(
"*", 0, 23),
239 """Turn to to the next day."""
263 """Call when entity about to be added to Home Assistant."""
268 def _clean_up_listener() -> None:
275 self.
_unsub_update_unsub_update = event.async_track_point_in_utc_time(
280 """Datetime when the next update to the state."""
281 now = dt_util.utcnow()
293 """Run when the state of the sensor should be updated."""
297 self.
_unsub_update_unsub_update = event.async_track_point_in_utc_time(
datetime _add_one_dst_aware_day(self, datetime a_date, time target_time)
None _turn_to_next_day(self)
None _calculate_next_update(self)
None _calculate_boundary_time(self)
None async_added_to_hass(self)
None __init__(self, str name, time after, timedelta after_offset, time before, timedelta before_offset, str|None unique_id)
dict[str, Any]|None extra_state_attributes(self)
datetime _naive_time_to_utc_datetime(self, time naive_time)
None _point_in_time_listener(self, datetime now)
None async_write_ha_state(self)
None async_on_remove(self, CALLBACK_TYPE func)
TypeGuard[SunEventType] _is_sun_event(time|SunEventType sun_event)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
datetime.datetime|None get_astral_event_date(HomeAssistant hass, str event, datetime.date|datetime.datetime|None date=None)
datetime.datetime get_astral_event_next(HomeAssistant hass, str event, datetime.datetime|None utc_point_in_time=None, datetime.timedelta|None offset=None)