1 """Sensor to indicate whether the current day is a workday."""
3 from __future__
import annotations
5 from datetime
import date, datetime, timedelta
6 from typing
import Final
11 __version__
as python_holidays_version,
14 import voluptuous
as vol
30 async_get_current_platform,
49 SERVICE_CHECK_DATE: Final =
"check_date"
50 CHECK_DATE: Final =
"check_date"
54 """Validate and adds to list of dates to add or remove."""
55 calc_holidays: list[str] = []
56 for add_date
in holiday_list:
57 if add_date.find(
",") > 0:
58 dates = add_date.split(
",", maxsplit=1)
59 d1 = dt_util.parse_date(dates[0])
60 d2 = dt_util.parse_date(dates[1])
61 if d1
is None or d2
is None:
62 LOGGER.error(
"Incorrect dates in date range: %s", add_date)
64 _range: timedelta = d2 - d1
65 for i
in range(_range.days + 1):
67 calc_holidays.append(day.strftime(
"%Y-%m-%d"))
69 calc_holidays.append(add_date)
78 categories: list[str] |
None,
80 """Get the object for the requested country and year."""
86 category_list = [PUBLIC]
87 category_list.extend(categories)
88 set_categories =
tuple(category_list)
90 obj_holidays: HolidayBase = country_holidays(
93 years=[year, year + 1],
95 categories=set_categories,
97 if (supported_languages := obj_holidays.supported_languages)
and language ==
"en":
98 for lang
in supported_languages:
99 if lang.startswith(
"en"):
100 obj_holidays = country_holidays(
105 categories=set_categories,
107 LOGGER.debug(
"Changing language from %s to %s", language, lang)
112 hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
114 """Set up the Workday sensor."""
115 add_holidays: list[str] = entry.options[CONF_ADD_HOLIDAYS]
116 remove_holidays: list[str] = entry.options[CONF_REMOVE_HOLIDAYS]
117 country: str |
None = entry.options.get(CONF_COUNTRY)
118 days_offset: int =
int(entry.options[CONF_OFFSET])
119 excludes: list[str] = entry.options[CONF_EXCLUDES]
120 province: str |
None = entry.options.get(CONF_PROVINCE)
121 sensor_name: str = entry.options[CONF_NAME]
122 workdays: list[str] = entry.options[CONF_WORKDAYS]
123 language: str |
None = entry.options.get(CONF_LANGUAGE)
124 categories: list[str] |
None = entry.options.get(CONF_CATEGORY)
126 year: int = (dt_util.now() +
timedelta(days=days_offset)).year
127 obj_holidays: HolidayBase = await hass.async_add_executor_job(
128 _get_obj_holidays, country, province, year, language, categories
132 next_year = dt_util.now().year + 1
136 obj_holidays.append(calc_add_holidays)
137 except ValueError
as error:
138 LOGGER.error(
"Could not add custom holidays: %s", error)
141 for remove_holiday
in calc_remove_holidays:
144 if dt_util.parse_date(remove_holiday):
146 removed = obj_holidays.pop(remove_holiday)
147 LOGGER.debug(
"Removed %s", remove_holiday)
150 LOGGER.debug(
"Treating '%s' as named holiday", remove_holiday)
151 removed = obj_holidays.pop_named(remove_holiday)
152 for holiday
in removed:
153 LOGGER.debug(
"Removed %s by name '%s'", holiday, remove_holiday)
154 except KeyError
as unmatched:
155 LOGGER.warning(
"No holiday found matching %s", unmatched)
156 if _date := dt_util.parse_date(remove_holiday):
157 if _date.year <= next_year:
162 f
"bad_date_holiday-{entry.entry_id}-{slugify(remove_holiday)}",
165 severity=IssueSeverity.WARNING,
166 translation_key=
"bad_date_holiday",
167 translation_placeholders={
168 CONF_COUNTRY: country
if country
else "-",
169 "title": entry.title,
170 CONF_REMOVE_HOLIDAYS: remove_holiday,
173 "entry_id": entry.entry_id,
175 "named_holiday": remove_holiday,
182 f
"bad_named_holiday-{entry.entry_id}-{slugify(remove_holiday)}",
185 severity=IssueSeverity.WARNING,
186 translation_key=
"bad_named_holiday",
187 translation_placeholders={
188 CONF_COUNTRY: country
if country
else "-",
189 "title": entry.title,
190 CONF_REMOVE_HOLIDAYS: remove_holiday,
193 "entry_id": entry.entry_id,
195 "named_holiday": remove_holiday,
199 LOGGER.debug(
"Found the following holidays for your configuration:")
200 for holiday_date, name
in sorted(obj_holidays.items()):
202 _holiday_string = holiday_date.strftime(
"%Y-%m-%d")
203 LOGGER.debug(
"%s %s", _holiday_string, name)
206 platform.async_register_entity_service(
208 {vol.Required(CHECK_DATE): cv.date},
211 SupportsResponse.ONLY,
229 """Implementation of a Workday sensor."""
231 _attr_has_entity_name =
True
233 _attr_translation_key = DOMAIN
234 _attr_should_poll =
False
235 unsub: CALLBACK_TYPE |
None =
None
239 obj_holidays: HolidayBase,
246 """Initialize the Workday sensor."""
252 CONF_WORKDAYS: workdays,
253 CONF_EXCLUDES: excludes,
254 CONF_OFFSET: days_offset,
258 entry_type=DeviceEntryType.SERVICE,
259 identifiers={(DOMAIN, entry_id)},
260 manufacturer=
"python-holidays",
261 model=python_holidays_version,
266 """Check if given day is in the includes list."""
275 """Check if given day is in the excludes list."""
284 """Compute next time an update should occur."""
285 tomorrow = dt_util.as_local(now) +
timedelta(days=1)
286 return dt_util.start_of_local_day(tomorrow)
289 """Update state and setup listener for next interval."""
298 """Get the latest data and update state."""
303 """Set up first update."""
307 """Get date and look whether it is a holiday."""
311 """Service to check if date is workday or not."""
315 """Check if date is workday."""
321 day = adjusted_date.isoweekday() - 1
322 day_of_week = ALLOWED_DAYS[day]
324 if self.
is_includeis_include(day_of_week, adjusted_date):
327 if self.
is_excludeis_exclude(day_of_week, adjusted_date):
_attr_extra_state_attributes
bool date_is_workday(self, date check_date)
None __init__(self, HolidayBase obj_holidays, list[str] workdays, list[str] excludes, int days_offset, str name, str entry_id)
datetime get_next_interval(self, datetime now)
None async_added_to_hass(self)
None _update_state_and_setup_listener(self)
bool is_include(self, str day, date now)
bool is_exclude(self, str day, date now)
None point_in_time_listener(self, datetime time_date)
None update_data(self, datetime now)
ServiceResponse check_date(self, date check_date)
None async_write_ha_state(self)
None async_create_issue(HomeAssistant hass, str entry_id)
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
HolidayBase _get_obj_holidays(str|None country, str|None province, int year, str|None language, list[str]|None categories)
list[str] validate_dates(list[str] holiday_list)
CALLBACK_TYPE async_track_point_in_utc_time(HomeAssistant hass, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action, datetime point_in_time)