Skip to content

Commit

Permalink
Add date range to Workday (home-assistant#96255)
Browse files Browse the repository at this point in the history
  • Loading branch information
gjohansson-ST authored Sep 26, 2023
1 parent 9c1944f commit 7b1b189
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 11 deletions.
30 changes: 26 additions & 4 deletions homeassistant/components/workday/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from typing import Any

from holidays import (
DateLike,
HolidayBase,
__version__ as python_holidays_version,
country_holidays,
Expand Down Expand Up @@ -45,6 +44,26 @@
)


def validate_dates(holiday_list: list[str]) -> list[str]:
"""Validate and adds to list of dates to add or remove."""
calc_holidays: list[str] = []
for add_date in holiday_list:
if add_date.find(",") > 0:
dates = add_date.split(",", maxsplit=1)
d1 = dt_util.parse_date(dates[0])
d2 = dt_util.parse_date(dates[1])
if d1 is None or d2 is None:
LOGGER.error("Incorrect dates in date range: %s", add_date)
continue
_range: timedelta = d2 - d1
for i in range(_range.days + 1):
day = d1 + timedelta(days=i)
calc_holidays.append(day.strftime("%Y-%m-%d"))
continue
calc_holidays.append(add_date)
return calc_holidays


def valid_country(value: Any) -> str:
"""Validate that the given country is supported."""
value = cv.string(value)
Expand Down Expand Up @@ -119,7 +138,7 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the Workday sensor."""
add_holidays: list[DateLike] = entry.options[CONF_ADD_HOLIDAYS]
add_holidays: list[str] = entry.options[CONF_ADD_HOLIDAYS]
remove_holidays: list[str] = entry.options[CONF_REMOVE_HOLIDAYS]
country: str | None = entry.options.get(CONF_COUNTRY)
days_offset: int = int(entry.options[CONF_OFFSET])
Expand All @@ -141,14 +160,17 @@ async def async_setup_entry(
else:
obj_holidays = HolidayBase()

calc_add_holidays: list[str] = validate_dates(add_holidays)
calc_remove_holidays: list[str] = validate_dates(remove_holidays)

# Add custom holidays
try:
obj_holidays.append(add_holidays)
obj_holidays.append(calc_add_holidays) # type: ignore[arg-type]
except ValueError as error:
LOGGER.error("Could not add custom holidays: %s", error)

# Remove holidays
for remove_holiday in remove_holidays:
for remove_holiday in calc_remove_holidays:
try:
# is this formatted as a date?
if dt_util.parse_date(remove_holiday):
Expand Down
41 changes: 37 additions & 4 deletions homeassistant/components/workday/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,24 @@ def add_province_to_schema(
return vol.Schema({**DATA_SCHEMA_OPT.schema, **add_schema})


def _is_valid_date_range(check_date: str, error: type[HomeAssistantError]) -> bool:
"""Validate date range."""
if check_date.find(",") > 0:
dates = check_date.split(",", maxsplit=1)
for date in dates:
if dt_util.parse_date(date) is None:
raise error("Incorrect date in range")
return True
return False


def validate_custom_dates(user_input: dict[str, Any]) -> None:
"""Validate custom dates for add/remove holidays."""
for add_date in user_input[CONF_ADD_HOLIDAYS]:
if dt_util.parse_date(add_date) is None:
if (
not _is_valid_date_range(add_date, AddDateRangeError)
and dt_util.parse_date(add_date) is None
):
raise AddDatesError("Incorrect date")

year: int = dt_util.now().year
Expand All @@ -88,9 +102,12 @@ def validate_custom_dates(user_input: dict[str, Any]) -> None:
obj_holidays = HolidayBase(years=year)

for remove_date in user_input[CONF_REMOVE_HOLIDAYS]:
if dt_util.parse_date(remove_date) is None:
if obj_holidays.get_named(remove_date) == []:
raise RemoveDatesError("Incorrect date or name")
if (
not _is_valid_date_range(remove_date, RemoveDateRangeError)
and dt_util.parse_date(remove_date) is None
and obj_holidays.get_named(remove_date) == []
):
raise RemoveDatesError("Incorrect date or name")


DATA_SCHEMA_SETUP = vol.Schema(
Expand Down Expand Up @@ -223,8 +240,12 @@ async def async_step_options(
)
except AddDatesError:
errors["add_holidays"] = "add_holiday_error"
except AddDateRangeError:
errors["add_holidays"] = "add_holiday_range_error"
except RemoveDatesError:
errors["remove_holidays"] = "remove_holiday_error"
except RemoveDateRangeError:
errors["remove_holidays"] = "remove_holiday_range_error"
except NotImplementedError:
self.async_abort(reason="incorrect_province")

Expand Down Expand Up @@ -284,8 +305,12 @@ async def async_step_init(
)
except AddDatesError:
errors["add_holidays"] = "add_holiday_error"
except AddDateRangeError:
errors["add_holidays"] = "add_holiday_range_error"
except RemoveDatesError:
errors["remove_holidays"] = "remove_holiday_error"
except RemoveDateRangeError:
errors["remove_holidays"] = "remove_holiday_range_error"
else:
LOGGER.debug("abort_check in options with %s", combined_input)
try:
Expand Down Expand Up @@ -328,9 +353,17 @@ class AddDatesError(HomeAssistantError):
"""Exception for error adding dates."""


class AddDateRangeError(HomeAssistantError):
"""Exception for error adding dates."""


class RemoveDatesError(HomeAssistantError):
"""Exception for error removing dates."""


class RemoveDateRangeError(HomeAssistantError):
"""Exception for error removing dates."""


class CountryNotExist(HomeAssistantError):
"""Exception country does not exist error."""
10 changes: 7 additions & 3 deletions homeassistant/components/workday/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,17 @@
"excludes": "List of workdays to exclude",
"days_offset": "Days offset",
"workdays": "List of workdays",
"add_holidays": "Add custom holidays as YYYY-MM-DD",
"remove_holidays": "Remove holidays as YYYY-MM-DD or by using partial of name",
"add_holidays": "Add custom holidays as YYYY-MM-DD or as range using `,` as separator",
"remove_holidays": "Remove holidays as YYYY-MM-DD, as range using `,` as separator or by using partial of name",
"province": "State, Territory, Province, Region of Country"
}
}
},
"error": {
"add_holiday_error": "Incorrect format on date (YYYY-MM-DD)",
"remove_holiday_error": "Incorrect format on date (YYYY-MM-DD) or holiday name not found"
"add_holiday_range_error": "Incorrect format on date range (YYYY-MM-DD,YYYY-MM-DD)",
"remove_holiday_error": "Incorrect format on date (YYYY-MM-DD) or holiday name not found",
"remove_holiday_range_error": "Incorrect format on date range (YYYY-MM-DD,YYYY-MM-DD)"
}
},
"options": {
Expand All @@ -61,7 +63,9 @@
},
"error": {
"add_holiday_error": "[%key:component::workday::config::error::add_holiday_error%]",
"add_holiday_range_error": "[%key:component::workday::config::error::add_holiday_range_error%]",
"remove_holiday_error": "[%key:component::workday::config::error::remove_holiday_error%]",
"remove_holiday_range_error": "[%key:component::workday::config::error::remove_holiday_range_error%]",
"already_configured": "Service with this configuration already exist"
}
},
Expand Down
50 changes: 50 additions & 0 deletions tests/components/workday/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,53 @@ async def init_integration(
"add_holidays": ["2023-12-32"],
"remove_holidays": ["2023-12-32"],
}
TEST_CONFIG_INCORRECT_ADD_DATE_RANGE = {
"name": DEFAULT_NAME,
"country": "DE",
"province": "BW",
"excludes": DEFAULT_EXCLUDES,
"days_offset": DEFAULT_OFFSET,
"workdays": DEFAULT_WORKDAYS,
"add_holidays": ["2023-12-01", "2023-12-30,2023-12-32"],
"remove_holidays": [],
}
TEST_CONFIG_INCORRECT_REMOVE_DATE_RANGE = {
"name": DEFAULT_NAME,
"country": "DE",
"province": "BW",
"excludes": DEFAULT_EXCLUDES,
"days_offset": DEFAULT_OFFSET,
"workdays": DEFAULT_WORKDAYS,
"add_holidays": [],
"remove_holidays": ["2023-12-25", "2023-12-30,2023-12-32"],
}
TEST_CONFIG_INCORRECT_ADD_DATE_RANGE_LEN = {
"name": DEFAULT_NAME,
"country": "DE",
"province": "BW",
"excludes": DEFAULT_EXCLUDES,
"days_offset": DEFAULT_OFFSET,
"workdays": DEFAULT_WORKDAYS,
"add_holidays": ["2023-12-01", "2023-12-29,2023-12-30,2023-12-31"],
"remove_holidays": [],
}
TEST_CONFIG_INCORRECT_REMOVE_DATE_RANGE_LEN = {
"name": DEFAULT_NAME,
"country": "DE",
"province": "BW",
"excludes": DEFAULT_EXCLUDES,
"days_offset": DEFAULT_OFFSET,
"workdays": DEFAULT_WORKDAYS,
"add_holidays": [],
"remove_holidays": ["2023-12-25", "2023-12-29,2023-12-30,2023-12-31"],
}
TEST_CONFIG_ADD_REMOVE_DATE_RANGE = {
"name": DEFAULT_NAME,
"country": "DE",
"province": "BW",
"excludes": DEFAULT_EXCLUDES,
"days_offset": DEFAULT_OFFSET,
"workdays": DEFAULT_WORKDAYS,
"add_holidays": ["2022-12-01", "2022-12-05,2022-12-15"],
"remove_holidays": ["2022-12-04", "2022-12-24,2022-12-26"],
}
55 changes: 55 additions & 0 deletions tests/components/workday/test_binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@
from homeassistant.util.dt import UTC

from . import (
TEST_CONFIG_ADD_REMOVE_DATE_RANGE,
TEST_CONFIG_DAY_AFTER_TOMORROW,
TEST_CONFIG_EXAMPLE_1,
TEST_CONFIG_EXAMPLE_2,
TEST_CONFIG_INCLUDE_HOLIDAY,
TEST_CONFIG_INCORRECT_ADD_DATE_RANGE,
TEST_CONFIG_INCORRECT_ADD_DATE_RANGE_LEN,
TEST_CONFIG_INCORRECT_ADD_REMOVE,
TEST_CONFIG_INCORRECT_COUNTRY,
TEST_CONFIG_INCORRECT_PROVINCE,
TEST_CONFIG_INCORRECT_REMOVE_DATE_RANGE,
TEST_CONFIG_INCORRECT_REMOVE_DATE_RANGE_LEN,
TEST_CONFIG_NO_COUNTRY,
TEST_CONFIG_NO_COUNTRY_ADD_HOLIDAY,
TEST_CONFIG_NO_PROVINCE,
Expand Down Expand Up @@ -264,3 +269,53 @@ async def test_setup_incorrect_add_remove(
in caplog.text
)
assert "No holiday found matching '2023-12-32'" in caplog.text


async def test_setup_incorrect_add_holiday_ranges(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test setup with incorrect add/remove holiday ranges."""
freezer.move_to(datetime(2017, 1, 6, 12, tzinfo=UTC)) # Friday
await init_integration(hass, TEST_CONFIG_INCORRECT_ADD_DATE_RANGE)
await init_integration(hass, TEST_CONFIG_INCORRECT_ADD_DATE_RANGE_LEN, "2")

hass.states.get("binary_sensor.workday_sensor")

assert "Incorrect dates in date range: 2023-12-30,2023-12-32" in caplog.text
assert (
"Incorrect dates in date range: 2023-12-29,2023-12-30,2023-12-31" in caplog.text
)


async def test_setup_incorrect_remove_holiday_ranges(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test setup with incorrect add/remove holiday ranges."""
freezer.move_to(datetime(2017, 1, 6, 12, tzinfo=UTC)) # Friday
await init_integration(hass, TEST_CONFIG_INCORRECT_REMOVE_DATE_RANGE)
await init_integration(hass, TEST_CONFIG_INCORRECT_REMOVE_DATE_RANGE_LEN, "2")

hass.states.get("binary_sensor.workday_sensor")

assert "Incorrect dates in date range: 2023-12-30,2023-12-32" in caplog.text
assert (
"Incorrect dates in date range: 2023-12-29,2023-12-30,2023-12-31" in caplog.text
)


async def test_setup_date_range(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test setup with date range."""
freezer.move_to(
datetime(2022, 12, 26, 12, tzinfo=UTC)
) # Boxing Day should be working day
await init_integration(hass, TEST_CONFIG_ADD_REMOVE_DATE_RANGE)

state = hass.states.get("binary_sensor.workday_sensor")
assert state.state == "on"
Loading

0 comments on commit 7b1b189

Please sign in to comment.