""" Sensor to indicate whether the current day is a workday. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.workday/ """ import asyncio import logging from datetime import datetime, timedelta import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME, WEEKDAYS from homeassistant.components.binary_sensor import BinarySensorDevice import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['holidays==0.9.7'] _LOGGER = logging.getLogger(__name__) # List of all countries currently supported by holidays # There seems to be no way to get the list out at runtime ALL_COUNTRIES = [ 'Argentina', 'AR', 'Australia', 'AU', 'Austria', 'AT', 'Belarus', 'BY' 'Belgium', 'BE', 'Canada', 'CA', 'Colombia', 'CO', 'Czech', 'CZ', 'Denmark', 'DK', 'England', 'EuropeanCentralBank', 'ECB', 'TAR', 'Finland', 'FI', 'France', 'FRA', 'Germany', 'DE', 'Hungary', 'HU', 'India', 'IND', 'Ireland', 'Isle of Man', 'Italy', 'IT', 'Japan', 'JP', 'Mexico', 'MX', 'Netherlands', 'NL', 'NewZealand', 'NZ', 'Northern Ireland', 'Norway', 'NO', 'Polish', 'PL', 'Portugal', 'PT', 'PortugalExt', 'PTE', 'Scotland', 'Slovenia', 'SI', 'Slovakia', 'SK', 'South Africa', 'ZA', 'Spain', 'ES', 'Sweden', 'SE', 'Switzerland', 'CH', 'UnitedKingdom', 'UK', 'UnitedStates', 'US', 'Wales', ] ALLOWED_DAYS = WEEKDAYS + ['holiday'] CONF_COUNTRY = 'country' CONF_PROVINCE = 'province' CONF_WORKDAYS = 'workdays' CONF_EXCLUDES = 'excludes' CONF_OFFSET = 'days_offset' # By default, Monday - Friday are workdays DEFAULT_WORKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri'] # By default, public holidays, Saturdays and Sundays are excluded from workdays DEFAULT_EXCLUDES = ['sat', 'sun', 'holiday'] DEFAULT_NAME = 'Workday Sensor' DEFAULT_OFFSET = 0 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_COUNTRY): vol.In(ALL_COUNTRIES), vol.Optional(CONF_EXCLUDES, default=DEFAULT_EXCLUDES): vol.All(cv.ensure_list, [vol.In(ALLOWED_DAYS)]), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): vol.Coerce(int), vol.Optional(CONF_PROVINCE): cv.string, vol.Optional(CONF_WORKDAYS, default=DEFAULT_WORKDAYS): vol.All(cv.ensure_list, [vol.In(ALLOWED_DAYS)]), }) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Workday sensor.""" import holidays sensor_name = config.get(CONF_NAME) country = config.get(CONF_COUNTRY) province = config.get(CONF_PROVINCE) workdays = config.get(CONF_WORKDAYS) excludes = config.get(CONF_EXCLUDES) days_offset = config.get(CONF_OFFSET) year = (get_date(datetime.today()) + timedelta(days=days_offset)).year obj_holidays = getattr(holidays, country)(years=year) if province: # 'state' and 'prov' are not interchangeable, so need to make # sure we use the right one if (hasattr(obj_holidays, 'PROVINCES') and province in obj_holidays.PROVINCES): obj_holidays = getattr(holidays, country)( prov=province, years=year) elif (hasattr(obj_holidays, 'STATES') and province in obj_holidays.STATES): obj_holidays = getattr(holidays, country)( state=province, years=year) else: _LOGGER.error("There is no province/state %s in country %s", province, country) return _LOGGER.debug("Found the following holidays for your configuration:") for date, name in sorted(obj_holidays.items()): _LOGGER.debug("%s %s", date, name) add_entities([IsWorkdaySensor( obj_holidays, workdays, excludes, days_offset, sensor_name)], True) def day_to_string(day): """Convert day index 0 - 7 to string.""" try: return ALLOWED_DAYS[day] except IndexError: return None def get_date(date): """Return date. Needed for testing.""" return date class IsWorkdaySensor(BinarySensorDevice): """Implementation of a Workday sensor.""" def __init__(self, obj_holidays, workdays, excludes, days_offset, name): """Initialize the Workday sensor.""" self._name = name self._obj_holidays = obj_holidays self._workdays = workdays self._excludes = excludes self._days_offset = days_offset self._state = None @property def name(self): """Return the name of the sensor.""" return self._name @property def is_on(self): """Return the state of the device.""" return self._state def is_include(self, day, now): """Check if given day is in the includes list.""" if day in self._workdays: return True if 'holiday' in self._workdays and now in self._obj_holidays: return True return False def is_exclude(self, day, now): """Check if given day is in the excludes list.""" if day in self._excludes: return True if 'holiday' in self._excludes and now in self._obj_holidays: return True return False @property def state_attributes(self): """Return the attributes of the entity.""" # return self._attributes return { CONF_WORKDAYS: self._workdays, CONF_EXCLUDES: self._excludes, CONF_OFFSET: self._days_offset } @asyncio.coroutine def async_update(self): """Get date and look whether it is a holiday.""" # Default is no workday self._state = False # Get iso day of the week (1 = Monday, 7 = Sunday) date = get_date(datetime.today()) + timedelta(days=self._days_offset) day = date.isoweekday() - 1 day_of_week = day_to_string(day) if self.is_include(day_of_week, date): self._state = True if self.is_exclude(day_of_week, date): self._state = False