Sensor for Holidays

I developed a sensor to check whether the current day is a holiday (state = on if so). I use this sensor for automation - only play sound in the morning on workdays, not weekend nor holidays. I used python module “holidays” which must be installed with pip (https://pypi.python.org/pypi/holidays/0.5).

This is my first python script :grinning: .

Please have mercy. I hope it works for your need. Unfortunately I had to put the script in the directory home-assistant/components/sensors. If I put it into custom_components it will not be loaded. I don’t know why. If someone has an idea, please tell me.

"""
Sensor to check whether current date is a holiday.
"""
import asyncio
import logging

import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (STATE_ON, STATE_OFF, CONF_NAME)
import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv

_LOGGER = logging.getLogger(__name__)

REQUIREMENTS = ['holidays==0.5']

ALL_COUNTRIES = ['AU', 'AT', 'CA', 'CO', 'DK', 'DE', 'MX', 'NZ', 'ES',
                 'UK', 'US']
CONF_COUNTRY = 'country'
CONF_PROVINCE = 'province'
DEFAULT_NAME = 'Holiday Sensor'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_COUNTRY): vol.In(ALL_COUNTRIES),
    vol.Optional(CONF_PROVINCE, default=None): cv.string,
    vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})


@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
    """Setup the Holiday sensor."""

    import holidays

    name = config.get(CONF_NAME)

    country = None

    country = config.get(CONF_COUNTRY)
    obj_holidays = getattr(holidays, country)()

    province = config.get(CONF_PROVINCE)

    if province:
        if province not in obj_holidays.PROVINCES:
            _LOGGER.error('There is no province/state %s in country %s',
                          province, country)
            return False
        else:
            obj_holidays = getattr(holidays, country)(state=province)
 
    yield from async_add_devices([IsHolidaySensor(obj_holidays, name)], True)
    return True


class IsHolidaySensor(Entity):
    """Implementation of a Holiday sensor."""

    def __init__(self, obj_holidays, name):
        """Initialize the sensor."""
        self._name = name
        self._obj_holidays = obj_holidays
        self._state = None

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def state(self):
        """Return the state of the device."""
        return self._state

    @asyncio.coroutine
    def async_update(self):
        """Get date and look whether it is a holiday."""

        self._date = dt_util.now()
        if self._date in self._obj_holidays:
            self._state = STATE_ON
        else:
            self._state = STATE_OFF
6 Likes

What a cool idea and a nice donation! You should share your method with this pull request by @mnestor ; it would be a great addition to a great work in progress…

https://github.com/home-assistant/home-assistant/pull/4161

It will already support that, just have to have a Holiday calendar showing up in your Google calendar view. It works with any calendar you can get Google to show you. :slight_smile:

1 Like

@mnestor: That sounds great. :+1:

I didn’t think of that. Can’t wait until it’s released! Been following the PR.

If you wanted you could even make it create separate sensors for each special holiday off of the same calendar. It’s pretty flexible. I did the same thing for SmartThings back when I had one of those.

1 Like

Hi, newbie here and very first post.
Although new to Home Assistant, I have been putting around with home automation for a while. Was a Homeseer user, been using the ISY-994i now for over 2 years, but looking to supplement with something else. I’ve been trying OpenHab but someone pointed me to this solution and I like it already although I haven’t installed it yet.

I’ve been waiting for a few things, integration with Harmony Hub (I see that’s on it’s way) and a way to figure out holidays and birthdays/anniversaries. I like this solution from @fgabriel but I am wondering if there is something missing. How is this script getting triggered? Usually, such a script get triggered at midnight and on startup. Any examples for us newbies would be great. Also the python module Holidays 0.5 … could I also see a snippet of example on how yours is setup? Do we need to specify the year everytime in this file? we all know that xmas is 12/25 of every year. is there a wild card that can be used?

Thanks
Bernie

@berniebl: As far as I know, the sensor will be updated asyncron. You don’t have to define a year. It will always use the current year (year is an optional parameter for python module holidays; if not defined it pick the current year). To use the sensor you have to put following lines in your configuration.yaml - for example:

sensor:
  - platform: isholiday
    name: Today is a holiday
    country: US
    province: CA

name and province are optional. Please note, if you use a name, then the sensor will be named in this example sensor.today_is_a_holiday. Province means sometimes state. If you use a province which is not valid for the country, you will receive a error message in the log. Look at https://pypi.python.org/pypi/holidays/0.5 for valid countries and provinces.

Thanks … seems like I have a lot to learn with HA in order to understand all of this, but it will serve me as a good reference. Thanks again

I have just found out from this page that the sensor has to be in directory

custom_components/sensor

And here is Google Calendar Event component

I have tried to upgrade to v0.40 this morning, and I’m getting this error

ERROR:homeassistant.components.sensor:Error while setting up platform isholiday
Traceback (most recent call last):
  File "/home/graham/lib/python3.5/site-packages/homeassistant/helpers/entity_component.py", line 148, in _async_setup_platform
    entity_platform.async_schedule_add_entities, discovery_info
  File "/home/graham/.homeassistant/custom_components/sensor/isholiday.py", line 56, in async_setup_platform
    yield from async_add_devices([IsHolidaySensor(obj_holidays, name)], True)
TypeError: 'NoneType' object is not iterable

Any hints on how to fix it?

This error is due to a breaking change in 0.40. According to the release notes:
If you are using async custom components, the passed in async_add_devices method is now a callback instead of a coroutine function.

You have to edit line number 56 and remove the words “yield from”

yield from async_add_devices([IsHolidaySensor(obj_holidays, name)], True)

The line must contain

async_add_devices([IsHolidaySensor(obj_holidays, name)], True)

That’s it.

2 Likes

Thanks. That worked fine.

I am also looking to create a sensor that basically tell me if the a day is a work day or not. Looking at the google calendar event component I do not really understand how to create a “workday” sensor from it.

You’d have to have an all day event on days that are work days. Since it’s triggered off events on a calendar.

Depending on your schedule, it might be easier to setup recurring meetings on days that are not workdays and make your sensor report the absence of a meeting as being a workday.

I just tested this on 0.43.2.

Being new to HA I probably made all the possible erors, but will share here anyway.

My country is Norway (NO) and I noticed this is implemented in later versions of the holidays module, so I just added the country code ‘NO’ to the sensor script (and removed the ‘yield from’ in line 55).

Then I copied your example definition into configuration.yaml like this:

 sensor:
  - platform: isholiday
    name: Today is a holiday
    country: NO

It took me a lot of debugging to see that the unquoted NO automagically translated into False, which is why I kept getting an invalid country code error. Adding double quotes around NO solved the problem. So any other Norwegian users out there should use this config entry:

 sensor:
  - platform: isholiday
    name: Today is a holiday
    country: "NO"

As much as I like it for convenience, weakly (or dynamically) typed languages sometimes are a real PIA.

1 Like

Is there any way to use this sensor to check if tomorrow is a workday?

I was looking for the exact same thing.

The solution I found was to add a new config option ‘days_ahead’ specifying the offset in days for which to get the result. Then I just apply this offset to self._date adding ‘datetime.timedelta(days=self._days_ahead)’

The sensor is defined like this:

- platform: isholiday
  name: free.tomorrow
  country: "NO"
  days_ahead: 1

I have no real knowledge of python or any understanding of customizing Hass, but this seems to work fine for me.

1 Like