Template Guide: Days away from calendar event

I keep seeing questions about calculating days away from a calendar date. Here’s a “simple” template that will solve that problem for anyone.

{% set midnight = today_at() %}
{% set event = state_attr('calendar.xxx', 'start_time') | as_datetime | as_local %}
{% set delta = event - midnight %}
{% if delta.days == 0 %}
  Today
{% elif delta.days == 1 %}
  Tomorrow
{% elif delta.days == 2 %}
  Day After Tomorrow
{% else %}
  In {{ delta.days }} Days
{% endif %}

Explanation

This first line grabs the current day and sets the timestamp to midnight. I.E. The first second of today, 00:00:00.

{% set midnight = today_at() %}

The second line is your event. You’ll have to make this line yourself. I can help if you post an image of your calendar entity from the Developer tools States page. Make sure to include attributes in the image.

{% set event = <THIS WILL CHANGE DEPENDING ON YOUR SETUP> %}

The third line calculates an offset as an object. This offset contains the number of days, hours, minutes, seconds, etc.

{% set delta = event - midnight %}

The last section is an if statement code block. It checks to see what day it is and properly sets the name. Based on the previous statement: “if the event occurs today, the delta will be zero. If it occurs tomorrow the delta will be 1, etc.” the if statement will be:

{% if delta.days == 0 %}
  Today
{% elif delta.days == 1 %}
  Tomorrow
{% elif delta.days == 2 %}
  Day After Tomorrow
{% else %}
  In {{ delta.days }} Days
{% endif %}

If you like to have short and concise templates, you can simplify this using a list:

{% set values = [ 'Today', 'Tomorrow', 'Day After Tomorrow' ] %}
{{ values.get(delta.days, 'In %s Days'%delta.days) }}

Making a template sensor

1. Integrate sensor.date**

sensor:
  - platform: time_date
    display_options:
      - 'date'

2. Create your template sensor. (as an if statement) ***

template:
- sensor:
  - name: Event Day
    state: >
      {% set t = now() %}
      {% set midnight = today_at() %}
      {% set event = state_attr('calendar.xxx', 'start_time') | as_datetime | as_local %}
      {% set delta = event - midnight %}
      {% if delta.days == 0 %}
        Today
      {% elif delta.days == 1 %}
        Tomorrow
      {% elif delta.days == 2 %}
        Day After Tomorrow
      {% else %}
        In {{ delta.days }} Days
      {% endif %}

or

2. Create your template sensor. (using a list) ***

template:
- sensor:
  - name: Event Day
    state: >
      {% set t = now() %}
      {% set midnight = today_at() %}
      {% set event = state_attr('calendar.xxx', 'start_time') | as_datetime | as_local %}
      {% set delta = event - midnight %}
      {% set values = {0: 'Today', 1:'Tomorrow', 2:'Day After Tomorrow'} %}
      {{ values.get(delta.days, 'In %s Days'%delta.days) }}

** If you already have a template section in configuration.yaml, do not duplicate it. Simply copy and paste the code blocks without the template: line into your current sensor section.

*** The template sensor will update every day at midnight. The {% set t = now() %} sensor will update every minute. Because we attach this to our template, it forces our template to update at that time. The sensor will also update if the calendar date changes.

24 Likes

Awesome. I’m setting one up for Nov 3 to show on my dashboard. :wink:

Thanks again, @petro. Here’s my template, slightly adjusted, and can be used for any arbitrary date:

{% set midnight = now().replace(hour=0, minute=0, second=0, microsecond=0).timestamp() %}
{% set event = '2020-11-03' | as_timestamp %}
{% set delta = ((event - midnight) // 86400) | int %}
{% if delta < 0 %}
 {{ -delta }} Days Ago
{% elif delta == 0 %}
 Today!
{% elif delta == 1 %}
 Tomorrow
{% else %}
 In {{ delta }} Days
{% endif %}
1 Like

Also in case it’s useful for anyone else, I already had this sensor:

- platform: time_date
  display_options:
    - 'date'

And I added this new template sensor:

- platform: template
  sensors:
    days_until_election:
      friendly_name: Election Day
      entity_id: sensor.date
      icon_template: mdi:calendar-check
      value_template: >
        {% set midnight = now().replace(hour=0, minute=0, second=0, microsecond=0).timestamp() %}
        {% set event = '2020-11-03' | as_timestamp %}
        {% set delta = ((event - midnight) // 86400) | int %}
        {% if delta < 0 %}
          {{ -delta }} Days Ago
        {% elif delta == 0 %}
          Today!
        {% elif delta == 1 %}
          Tomorrow
        {% else %}
          In {{ delta }} Days
        {% endif %}

And a Lovelace card:

- type: entity
  entity: sensor.days_until_election

And here we are, 95 days out:
image

I believe you can shorten this:

        {% set midnight = now().replace(hour=0, minute=0, second=0, microsecond=0).timestamp() %}

to this:

        {% set midnight = now().date() | as_timestamp %}

Ignore below: fixed by setting my event as follows:

{% set event = state_attr('calendar.ufc', 'start_time') | as_timestamp %}

----FIXED–
Could anyone please assist with the below? I 'm trying to make a countdown to the next UFC event. Followed this guide as best I could?

  - platform: time_date
    display_options:
      - "date"

  - platform: template
    sensors:
      ufc_countdown:
        friendly_name: UFC Event
        value_template: >
          {% set midnight = now().replace(hour=0, minute=0, second=0, microsecond=0).timestamp() %}
          {% set event = states('calendar.ufc') | as_timestamp %}
          {% set delta = ((event - midnight) // 86400) | int %}
          {% if delta == 0 %}
            Today
          {% elif delta == 1 %}
            Tomorrow
          {% else %}
            In {{ delta }} Days
          {% endif %}

My calendar.ufc is working:

message: "\U0001F94A UFC Fight Night: Holloway vs. Kattar"
all_day: false
start_time: '2021-01-16 20:00:00'
end_time: '2021-01-16 23:00:00'
location: Etihad Arena - Abu Dhabi
description: >

However the sensor.ufc_countdown returns

state:  unavailable
friendly_name: UFC Event

Has this been superceded? I’ve not been able to find anything more upto date.

Note:
If I set the date manually

{% set event = '2021-01-16' | as_timestamp %}

It does work.

state: In 9 Days

1 Like

Hello,
I try your code and integrate the sensor.date.

But it’s give me the ‘unavailable’ state instead of ‘Day After Tomorrow’:

HAve someone an idea why?

sensor:
  - platform: time_date 
    display_options:
      - 'date'
      - 'time'     

  - platform: template
    sensors:
      event_day:
        friendly_name: Event Day
        value_template: >
          {% set midnight = now().replace(hour=0, minute=0, second=0, microsecond=0).timestamp() %}
          {% set event = states('calendar.myadress_gmail_com') | as_timestamp %}
          {% set delta = ((event - midnight) // 86400) | int %}
          {% if delta == 0 %}
            Today
          {% elif delta == 1 %}
            Tomorrow
          {% else %}
           Day After Tomorrow
          {% endif %}

image

ok i manage to make it work by modifyng some code:

{% set event = as_timestamp(states.calendar.myadress_gmail_com.attributes.start_time) %}
1 Like

Hi ive successfully used @petro number of days template and it works perfectly returning the correct value. But when I try to to use that output in a subsequent calculation I get the error “ZeroDivisionError: float division by zero” eventhough the number of days is not zero. Can someone please help?. See my template below:
#Average AC Sold
avg_energy_sold:
value_template: >-
{% set ac_sold = states.sensor.fronius_smartmeter_energy_ac_sold.state | float %}
{% set days = states.days_since_installation.state | float %}
{{ ((ac_sold | float) / (days | int)) | round(2) }}
friendly_name: “Avg Energy AC Sold”
unit_of_measurement: “kWh$”

This refers to a non-existent entity:

{% set days = states.days_since_installation.state | float %}

If it’s a sensor entity then it should include the word sensor

{% set days = states.sensor.days_since_installation.state | float %}

Thanks @123 that was an oversight on my part, my bad. Even after i include the word sensor it returns the error “ZeroDivisionError: float division by zero”. sensor.days_since_installation reads 241 days currently.

Copy-paste all of the following into the Template Editor:

{{ states('sensor.fronius_smartmeter_energy_ac_sold') | float }}
{{ states('sensor.days_since_installation') | float }}
{{ (states('sensor.fronius_smartmeter_energy_ac_sold') | float / states('sensor.days_since_installation') | float) | round(2) }}

It should report three values, the first two are the sensor values and the last one is the result of the calculation. Let us know the results.

Thanks for the quick reply. It reports back the same error “ZeroDivisionError: float division by zero”. What seems like a straight forward calculation continues to baffle me.

Then remove the third line. The first two lines should report numbers greater than zero. If the second one reports zero, there’s the problem.

See results below with third line removed. Even in the previous formula that i had it is always the sensor.days_since_installation that gets reported as zero when used in a subsequent calculation. Whereas the actual sensor currently reports a value of 242 days. Ive not had this problem when creating template sensor with any other entity. Is it some how related to how the time calculation is reported. I have tried a different formula to calculate days and and has the same issue.

9266.21
0.0

Post a screenshot of that sensor as it appears in Developer Tools > States.

If the value was truly 242 then the float filter would have no difficulty converting it from a numeric string to a floating-point value. However, if either the sensor doesn’t exist or it does but its value isn’t purely numeric then the conversion will fail and the result will be zero.

See below:

The state is 242 days, not 242. When converting to a float it will be zero because the word days is not a number. Parse out the days by replacing it or splitting the string.

Thanks @petro and @123. Parsed out days and added it as unit of measurement instead and everything works as expected.

It is so obvious what the problem was now. Thanks to you experts spending the time and energy helping out noobs like myself.

As suspected (and confirmed by the screenshot), when you said “a value of 242 days” the value literally contained the word “days” (actually " Days"). As explained earlier:

Something to keep in mind should you ever encounter a similar situation.