diff --git a/README.md b/README.md
index 1cd0566..e3d5bfc 100644
--- a/README.md
+++ b/README.md
@@ -109,6 +109,8 @@ Entity_id change is not possible using the YAML configuration. Changing other pa
| `last_month` | Yes | Month three letter abbreviation.
**Default**: `"dec"`
| `exclude_dates` | Yes | List of dates with no collection (using international date format `'yyyy-mm-dd'`.
| `include_dates` | Yes | List of extra collection (using international date format `'yyyy-mm-dd'`.
+| `move_country_holidays` | Yes | A country code (see [holidays](https://github.com/dr-prodigy/python-holidays) for the list of valid country codes).
Automatically move garbage collection on public holidays to the following day.
*Example:* `US`
+
#### PARAMETERS FOR COLLECTION EVERY-N-WEEKS
|Attribute |Optional|Description
diff --git a/custom_components/garbage_collection/.translations/cs.json b/custom_components/garbage_collection/.translations/cs.json
index cb429fe..067bb36 100644
--- a/custom_components/garbage_collection/.translations/cs.json
+++ b/custom_components/garbage_collection/.translations/cs.json
@@ -57,7 +57,8 @@
"week_order_number_4": "Čtvrtý týden v měsíci",
"week_order_number_5": "Pátý týden v měsíci",
"include_dates": "Přidané datumy (volitelné)",
- "exclude_dates": "Zakázané datumy (volitelné)"
+ "exclude_dates": "Zakázané datumy (volitelné)",
+ "move_country_holidays": "Přesunout svátky na další den (volitelné)"
}
}
},
@@ -133,7 +134,8 @@
"week_order_number_4": "Čtvrtý týden v měsíci",
"week_order_number_5": "Pátý týden v měsíci",
"include_dates": "Přidané datumy (volitelné)",
- "exclude_dates": "Zakázané datumy (volitelné)"
+ "exclude_dates": "Zakázané datumy (volitelné)",
+ "move_country_holidays": "Pčesunout svátky na další den (volitelné)"
}
}
},
diff --git a/custom_components/garbage_collection/.translations/en.json b/custom_components/garbage_collection/.translations/en.json
index c73eb73..dedd642 100644
--- a/custom_components/garbage_collection/.translations/en.json
+++ b/custom_components/garbage_collection/.translations/en.json
@@ -57,7 +57,8 @@
"week_order_number_4": "4th week of month",
"week_order_number_5": "5th week of month",
"include_dates": "Include dates (optional)",
- "exclude_dates": "Exclude dates (optional)"
+ "exclude_dates": "Exclude dates (optional)",
+ "move_country_holidays": "Move holidays to next day (optional)"
}
}
},
@@ -132,7 +133,8 @@
"week_order_number_4": "4th week of month",
"week_order_number_5": "5th week of month",
"include_dates": "Include dates (optional)",
- "exclude_dates": "Exclude dates (optional)"
+ "exclude_dates": "Exclude dates (optional)",
+ "move_country_holidays": "Move holidays to next day (optional)"
}
}
},
diff --git a/custom_components/garbage_collection/.translations/fr.json b/custom_components/garbage_collection/.translations/fr.json
index c3ae28b..b71784b 100644
--- a/custom_components/garbage_collection/.translations/fr.json
+++ b/custom_components/garbage_collection/.translations/fr.json
@@ -57,7 +57,8 @@
"week_order_number_4": "4ème semaine du mois",
"week_order_number_5": "5ème semaine du mois",
"include_dates": "Inclusion de dates (optionel)",
- "exclude_dates": "Exclusion de dates (optionel)"
+ "exclude_dates": "Exclusion de dates (optionel)",
+ "move_country_holidays": "Move holidays to next day (optional)"
}
}
},
@@ -132,8 +133,8 @@
"week_order_number_4": "4ème semaine du mois",
"week_order_number_5": "5ème semaine du mois",
"include_dates": "Inclusion de dates (optionel)",
- "exclude_dates": "Exclusion de dates (optionel)"
-
+ "exclude_dates": "Exclusion de dates (optionel)",
+ "move_country_holidays": "Move holidays to next day (optional)"
}
}
},
diff --git a/custom_components/garbage_collection/.translations/it.json b/custom_components/garbage_collection/.translations/it.json
index fc1d744..716f800 100644
--- a/custom_components/garbage_collection/.translations/it.json
+++ b/custom_components/garbage_collection/.translations/it.json
@@ -57,7 +57,8 @@
"week_order_number_4": "4° settimana del mese",
"week_order_number_5": "5° settimana del mese",
"include_dates": "Includi date (opzionale)",
- "exclude_dates": "Escludi date (opzionale)"
+ "exclude_dates": "Escludi date (opzionale)",
+ "move_country_holidays": "Move holidays to next day (optional)"
}
}
},
@@ -132,7 +133,8 @@
"week_order_number_4": "4° settimana del mese",
"week_order_number_5": "5° settimana del mese",
"include_dates": "Includi date (opzionale)",
- "exclude_dates": "Escludi date (opzionale)"
+ "exclude_dates": "Escludi date (opzionale)",
+ "move_country_holidays": "Move holidays to next day (optional)"
}
}
},
diff --git a/custom_components/garbage_collection/config_flow.py b/custom_components/garbage_collection/config_flow.py
index a65d8e3..430888e 100644
--- a/custom_components/garbage_collection/config_flow.py
+++ b/custom_components/garbage_collection/config_flow.py
@@ -17,6 +17,7 @@
MONTHLY_FREQUENCY,
ANNUAL_FREQUENCY,
GROUP_FREQUENCY,
+ COUNTRY_CODES,
DEFAULT_FIRST_MONTH,
DEFAULT_LAST_MONTH,
DEFAULT_FREQUENCY,
@@ -44,6 +45,7 @@
CONF_DATE,
CONF_EXCLUDE_DATES,
CONF_INCLUDE_DATES,
+ CONF_MOVE_COUNTRY_HOLIDAYS,
CONF_PERIOD,
CONF_FIRST_WEEK,
CONF_SENSORS,
@@ -286,6 +288,9 @@ async def async_step_final(
final_info[CONF_EXCLUDE_DATES] = string_to_list(
user_input[CONF_EXCLUDE_DATES]
)
+ final_info[CONF_MOVE_COUNTRY_HOLIDAYS] = user_input[
+ CONF_MOVE_COUNTRY_HOLIDAYS
+ ]
if not is_dates(final_info[CONF_INCLUDE_DATES]) or not is_dates(
final_info[CONF_EXCLUDE_DATES]
):
@@ -311,6 +316,7 @@ async def _show_final_form(self, user_input):
last_month = DEFAULT_LAST_MONTH
include_dates = ""
exclude_dates = ""
+ include_country_holidays = ""
period = 1
first_week = 1
if user_input is not None:
@@ -326,6 +332,8 @@ async def _show_final_form(self, user_input):
include_dates = user_input[CONF_INCLUDE_DATES]
if CONF_EXCLUDE_DATES in user_input:
exclude_dates = user_input[CONF_EXCLUDE_DATES]
+ if CONF_MOVE_COUNTRY_HOLIDAYS in user_input:
+ include_country_holidays = user_input[CONF_MOVE_COUNTRY_HOLIDAYS]
data_schema = OrderedDict()
data_schema[vol.Optional(CONF_FIRST_MONTH, default=first_month)] = vol.In(
MONTH_OPTIONS
@@ -366,6 +374,9 @@ async def _show_final_form(self, user_input):
] = bool
data_schema[vol.Optional(CONF_INCLUDE_DATES, default=include_dates)] = str
data_schema[vol.Optional(CONF_EXCLUDE_DATES, default=exclude_dates)] = str
+ data_schema[
+ vol.Optional(CONF_MOVE_COUNTRY_HOLIDAYS, default=include_country_holidays)
+ ] = vol.In(COUNTRY_CODES)
return self.async_show_form(
step_id="final", data_schema=vol.Schema(data_schema), errors=self._errors
)
@@ -667,6 +678,9 @@ async def async_step_final(
final_info[CONF_EXCLUDE_DATES]
):
self._errors["base"] = "date"
+ final_info[CONF_MOVE_COUNTRY_HOLIDAYS] = user_input[
+ CONF_MOVE_COUNTRY_HOLIDAYS
+ ]
if self._data[CONF_FREQUENCY] in WEEKLY_FREQUENCY_X:
final_info[CONF_PERIOD] = user_input[CONF_PERIOD]
final_info[CONF_FIRST_WEEK] = user_input[CONF_FIRST_WEEK]
@@ -737,6 +751,12 @@ async def _show_final_form(self, user_input):
default=",".join(self.config_entry.options.get(CONF_EXCLUDE_DATES)),
)
] = str
+ data_schema[
+ vol.Optional(
+ CONF_MOVE_COUNTRY_HOLIDAYS,
+ default=self.config_entry.options.get(CONF_MOVE_COUNTRY_HOLIDAYS),
+ )
+ ] = vol.In(COUNTRY_CODES)
return self.async_show_form(
step_id="final", data_schema=vol.Schema(data_schema), errors=self._errors
)
diff --git a/custom_components/garbage_collection/const.py b/custom_components/garbage_collection/const.py
index de96295..e58d78a 100644
--- a/custom_components/garbage_collection/const.py
+++ b/custom_components/garbage_collection/const.py
@@ -35,6 +35,7 @@
CONF_DATE = "date"
CONF_EXCLUDE_DATES = "exclude_dates"
CONF_INCLUDE_DATES = "include_dates"
+CONF_MOVE_COUNTRY_HOLIDAYS = "move_country_holidays"
CONF_PERIOD = "period"
CONF_FIRST_WEEK = "first_week"
CONF_SENSORS = "sensors"
@@ -87,6 +88,57 @@
"dec",
]
+COUNTRY_CODES = [
+ "AR",
+ "AT",
+ "AU",
+ "AW",
+ "BE",
+ "BG",
+ "BR",
+ "BY",
+ "CA",
+ "CH",
+ "CO",
+ "CZ",
+ "DE",
+ "DK",
+ "DO",
+ "ECB",
+ "EE",
+ "ES",
+ "FI",
+ "FRA",
+ "HR",
+ "HU",
+ "IE",
+ "IND",
+ "IS",
+ "IT",
+ "JP",
+ "KE",
+ "LT",
+ "LU",
+ "MX",
+ "NG",
+ "NI",
+ "NL",
+ "NO",
+ "NZ",
+ "PE",
+ "PL",
+ "PT",
+ "PTE",
+ "RU",
+ "SE",
+ "SI",
+ "SK",
+ "UA",
+ "UK",
+ "US",
+ "ZA",
+]
+
def date_text(value):
if value is None or value == "":
@@ -137,6 +189,7 @@ def month_day_text(value):
vol.Optional(CONF_EXCLUDE_DATES, default=[]): vol.All(
cv.ensure_list, [date_text]
),
+ vol.Optional(CONF_MOVE_COUNTRY_HOLIDAYS): vol.In(COUNTRY_CODES),
vol.Optional(CONF_ICON_NORMAL, default=DEFAULT_ICON_NORMAL): cv.icon,
vol.Optional(CONF_ICON_TODAY, default=DEFAULT_ICON_TODAY): cv.icon,
vol.Optional(CONF_ICON_TOMORROW, default=DEFAULT_ICON_TOMORROW): cv.icon,
@@ -148,11 +201,8 @@ def month_day_text(value):
CONFIG_SCHEMA = vol.Schema(
{
-
DOMAIN: vol.Schema(
- {
- vol.Optional(CONF_SENSORS): vol.All(cv.ensure_list, [SENSOR_SCHEMA])
- }
+ {vol.Optional(CONF_SENSORS): vol.All(cv.ensure_list, [SENSOR_SCHEMA])}
)
},
extra=vol.ALLOW_EXTRA,
diff --git a/custom_components/garbage_collection/manifest.json b/custom_components/garbage_collection/manifest.json
index 92ab482..7354161 100644
--- a/custom_components/garbage_collection/manifest.json
+++ b/custom_components/garbage_collection/manifest.json
@@ -10,6 +10,7 @@
"requirements": [
"datetime",
"integrationhelper",
+ "holidays",
"typing",
"uuid",
"voluptuous"
diff --git a/custom_components/garbage_collection/sensor.py b/custom_components/garbage_collection/sensor.py
index 4c8be32..57829b6 100644
--- a/custom_components/garbage_collection/sensor.py
+++ b/custom_components/garbage_collection/sensor.py
@@ -1,6 +1,7 @@
"""Sensor platform for garbage_collection."""
from homeassistant.helpers.entity import Entity
import homeassistant.util.dt as dt_util
+import holidays
import logging
import locale
from datetime import datetime, date, timedelta
@@ -39,6 +40,7 @@
CONF_DATE,
CONF_EXCLUDE_DATES,
CONF_INCLUDE_DATES,
+ CONF_MOVE_COUNTRY_HOLIDAYS,
CONF_PERIOD,
CONF_FIRST_WEEK,
CONF_SENSORS,
@@ -129,6 +131,18 @@ def __init__(self, hass, config):
)
self.__include_dates = to_dates(config.get(CONF_INCLUDE_DATES, []))
self.__exclude_dates = to_dates(config.get(CONF_EXCLUDE_DATES, []))
+ country_holidays = config.get(CONF_MOVE_COUNTRY_HOLIDAYS)
+ self.__holidays = []
+ if country_holidays is not None and country_holidays != "":
+ this_year = dt_util.now().date().year
+ years = [this_year, this_year + 1]
+ try:
+ for date, name in holidays.CountryHoliday(
+ country_holidays, years=years
+ ).items():
+ self.__holidays.append(date)
+ except KeyError:
+ _LOGGER.error("Invalid country code (%s)", country_holidays)
self.__period = config.get(CONF_PERIOD)
self.__first_week = config.get(CONF_FIRST_WEEK)
self.__next_date = None
@@ -291,28 +305,31 @@ def find_candidate_date(self, day1: date) -> date:
_LOGGER.debug(f"({self.__name}) Unknown frequency {self.__frequency}")
return None
+ def __insert_include_date(self, day1: date, next_date: date) -> date:
+ include_dates = list(filter(lambda date: date >= day1, self.__include_dates))
+ if len(include_dates) > 0 and include_dates[0] < next_date:
+ return include_dates[0]
+ else:
+ return next_date
+
+ def __skip_holiday(self, day: date) -> date:
+ return day + timedelta(days=1)
+
def get_next_date(self, day1: date) -> date:
- """Find the next date starting from day1.
- Looks at include and exclude days"""
+ """Find the next date starting from day1."""
first_day = day1
i = 0
- while True:
+ while i < 365:
next_date = self.find_candidate_date(first_day)
- include_dates = list(
- filter(lambda date: date >= day1, self.__include_dates)
- )
- if len(include_dates) > 0 and include_dates[0] < next_date:
- next_date = include_dates[0]
+ while next_date in self.__holidays:
+ next_date = self.__skip_holiday(next_date)
+ next_date = self.__insert_include_date(first_day, next_date)
if next_date not in self.__exclude_dates:
- break
- else:
- first_day = next_date + timedelta(days=1)
+ return next_date
+ first_day = next_date + timedelta(days=1)
i += 1
- if i > 365:
- _LOGGER.error("(%s) Cannot find any suitable date", self.__name)
- next_date = None
- break
- return next_date
+ _LOGGER.error("(%s) Cannot find any suitable date", self.__name)
+ return None
async def async_update(self) -> None:
"""Get the latest data and updates the states."""