How to list multiple calendar event in a template

I don’t think they converted the variable definition from YAML to Jinja in the Template tool.

@dirk.hildebrand

The Template tool does not understand YAML, in order to test the “state” and “list” templates you will need to define ev_list in Jinja as follows:

{% set ns = namespace(cal_events=[]) %}
{%- for key, value in agenda.items() %}
  {%- for event in value.events %}
    {%- set ns.cal_events = ns.cal_events + [event] %}
  {%- endfor %}
{%- endfor %}

{% set ev_list = ns.cal_events | sort(attribute='start') | list %}

That should solve the ‘ev_list’ is undefined error. Also make sure the variable name copied from the Action tool matches “agenda”.

I have tested it in a new, clean homeassistant installation.
Yes, there is an error in the logs:

Template variable warning: ‘ev_list’ is undefined when rendering ‘{{ ev_list }}’

And you won’t get that error if you have this action


      - variables:
          ev_list: |
            {% set ns = namespace(cal_events=[]) %}
            {%- for key, value in agenda.items() %}
              {%- for event in value.events %}
                {%- set ns.cal_events = ns.cal_events + [event] %}
              {%- endfor %}
            {%- endfor %}
            {{ ns.cal_events | sort(attribute='start') | list }}

So you’re either looking at the wrong error, you’re not properly making the variable as @Didgeridrew said when you transfer it over to the template editor.

In the template tool it works fine, if I copy the answer from the action in the template tool.
But in the yaml configuration it didn’t work.

You can’t use the exact same template in both places. YOu have to use

in your script.

And remove those

The two lines are removed.
And I understand, that there is a difference in code for yaml and for template editor.
With the code for yaml I get an empty ev_list.
I don’t understand why it works in action- and template-editor, but not in yaml?

Here is my code again:

calendar:
  - platform: caldav
    username: !secret icloud_user
    password: !secret icloud_password
    url: https://caldav.icloud.com
    calendars:
      - Spielplan_U17_24-25
      - Spielplan_Damen_24-25
      - Spielplan_U20_24-25
template:
  - trigger:
      - platform: time_pattern
        minutes: /1
    action:
      - service: calendar.get_events
        data:
          duration:
            hours: 216 # 9 Tage
        target:
          entity_id:
            - calendar.spielplan_u17_24_25
            - calendar.spielplan_damen_24_25
            - calendar.spielplan_u20_24_25
        response_variable: agenda
      - variables:
          ev_list: |
            {% set ns = namespace(cal_events=[]) %}
            {%- for key, value in agenda.items() %}
              {%- for event in value.events %}
                {%- set ns.cal_events = ns.cal_events + [event] %}
              {%- endfor %}
            {%- endfor %}
            {{ ns.cal_events | sort(attribute='start') | list }}

  - sensor:
      - name: Naechste_Spiele
        unique_id: "naechste_spiele"
        state: "{{ ev_list | count }}"
        attributes:
          list: "{{ ev_list }}"
          test: "0.01"
        icon: mdi:calendar

this should be

    sensor:
      - name: Naechste_Spiele
        unique_id: "naechste_spiele"
        state: "{{ ev_list | count }}"
        attributes:
          list: "{{ ev_list }}"
          test: "0.01"
        icon: mdi:calendar

Thank you for your help and time.
That damnd little dash!

Hello
Since October my template has stopped working, and the data is not updated

templates.yaml:

- trigger:
    - platform: time_pattern
      seconds: "/5"
  action:
    - service: calendar.get_events
      data:
        duration:
          hours: 2160
          minutes: 0
          seconds: 0
        start_date_time: "{{ today_at() }}"
      target:
        entity_id:
          - calendar.booking
          - calendar.airbnb
      response_variable: calendars
    - variables:
        ev_list: |
          {% set ns = namespace(cal_events=[]) %}
          {%- for key, value in calendars.items() | unique(attribute='start') %}
            {%- for event in value.events %}
              {%- set ns.cal_events = ns.cal_events + [event] %}
            {%- endfor %}
          {%- endfor %}
          {{ ns.cal_events | sort(attribute='start') | list }}
        in_1_unix: |
          {% set dif = 16*3600 %}
          {{ as_timestamp(ev_list[0].start) + dif | float }}
        out_1_unix: |
          {% set dif = 11*3600 %}
          {{ as_timestamp(ev_list[0].end) + dif | float }}
        in_2_unix: |
          {% set dif = 16*3600 %}
          {{ as_timestamp(ev_list[1].start) + dif | float }}
        out_2_unix: |
          {% set dif = 11*3600 %}
          {{ as_timestamp(ev_list[1].end) + dif | float }}
        actual: |
          {% if as_timestamp(now()) >= in_1_unix and as_timestamp(now()) <= out_1_unix %}
            {{ 1 }}
          {% else %}
            {{ 0 }}
          {% endif %}

  sensor:
    - name: reservas
      unique_id: reservas

      state: |
        {% if actual == 1 %}
          {{ "reserva_actual" }}
        {% else %}
          {{ "no_reserva_actual" }}
        {% endif %}

      attributes:
        scheduled_events: "{{ ev_list }}"
        in_1_unix: |
          {{in_1_unix}}
        in_1: |
          {% from 'formatter.jinja' import format_date %}
          {{format_date(in_1_unix)}}

        out_1: |
          {% from 'formatter.jinja' import format_date %}
          {{format_date(out_1_unix)}}

        in_2: |
          {% from 'formatter.jinja' import format_date %}
          {{format_date(in_2_unix)}}

        out_2: |
          {% from 'formatter.jinja' import format_date %}
          {{format_date(out_2_unix)}}

      icon: mdi:calendar'

custom_templates/formatter.jinja:

{% macro format_date(date_unix) %}
    {% set weekdays = ['Lunes','Martes','Miércoles','Jueves','Viernes','Sábado','Domingo'] %}
    {% set months = ["enero", "febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre","diciembre"] %}
    {% set dt = date_unix | timestamp_custom("%Y-%m-%d %H:%M:%S") | as_datetime %}
    {{weekdays[dt.weekday()] }}, {{ dt.day }} de {{ months[dt.month-1] }} {{ dt.year }}, {{ dt.hour }}:{{ dt.minute }}{{ dt.second }}
{% endmacro %}

calendar.booking entity attributes:

message: CLOSED - Not available
all_day: true
start_time: 2024-11-14 00:00:00
end_time: 2024-11-18 00:00:00
location: 
description: 
offset_reached: false
friendly_name: booking

calendar.airbnb entity attributes:

message: Reserved
all_day: true
start_time: 2024-12-06 00:00:00
end_time: 2024-12-09 00:00:00
location: 
description: Reservation URL: https://www.airbnb.com/hosting/reservations/details/....
Phone Number (Last 4 Digits): xxx
offset_reached: false
friendly_name: airbnb

I get sensor.reservas entity old attributes. How is it possible? I’ve reload all YAML configuration, and I still get the old data:

scheduled_events: 
- start: '2024-10-31'
  end: '2024-11-03'
  summary: CLOSED - Not available
- start: '2024-11-14'
  end: '2024-11-18'
  summary: CLOSED - Not available

in_1_unix: 1730386800
in_1: Jueves, 31 de octubre 2024, 16:00
out_1: Domingo, 3 de noviembre 2024, 11:00
in_2: Jueves, 14 de noviembre 2024, 16:00
out_2: Lunes, 18 de noviembre 2024, 11:00
icon: mdi:calendar'
friendly_name: reservas

Check your logs for errors. The only thing that stands out for me is that you’re always assuming there will be at least 2 events. And you didn’t build in any safety when there is less than 2 events. Errors in your logs would show this.

Yes, I have to review my code. Thanks

Hi, calendar gurus. I am struggling to figure out how to retrieve multiple events in the past from calendars and would appreciate some pointers.

Edit: I’ve re-read the documentation and realised that a duration without a start time fetches events from now to the duration into the future, so it’s useless for finding prior events. So approach 2 is the one I’m looking for an elegant solution for. I now realise the other items in this thread are looking for events in the future.

I have two issues:
1. If I try calendar_get with a duration but no start time, I would expect (based on examples above) that it would return all events with that duration, but when I try it I don’t get any events returned; see code below
2. Ideally, I would like to retrieve a sequence of events, e.g. all calendar entries at 6:30pm today, same day last year, same day year before. It doesn’t seem that there’s any easy way to do this (as start time doesn’t accept wildcards), so the only way would be an automation that iterates through those dates, calls get_events for each and accumulates the results in an array. Does anyone know of any better way to achieve this?

Thanks in advance!

I don’t know what the limit for a response variable is, so this might not work if your calendar contains so many events it exceeds the limit (if there is one).

Set your action to get all the events from the past 2 years:

action: calendar.get_events
data:
  start_date_time: "{{today_at('18:30').replace(year=now().year-2)}}"
  end_date_time: "{{today_at('18:31')}}"
target:
  entity_id: calendar.EXAMPLE
response_variable: agenda

Then use a template to get the events with the correct dates and time:

{{ merge_response(agenda)
| selectattr( 'start', 'search', now().month~'-'~now().day)
| selectattr( 'start', 'search', "18:30")
| list }}``

If that doesn’t work, another option would be to use a template to define the items for a Repeat for Each then combine the responses:

actions:
  - repeat:
      for_each: |
        {% set ns = namespace(dates=[]) %}
        {% for i in range(3) %}
          {% set start = today_at('18:30').replace(year=now().year-i) %}
          {% set end = start.replace(minute=31) %}
          {% set ns.dates = ns.dates + [[start.isoformat(), end.isoformat()]] %}
        {% endfor %}
        {{ns.dates}}
      sequence:
        - action: calendar.get_events
          data:
            start_date_time: '{{repeat.item[0]}}'
            end_date_time: '{{repeat.item[1]}}'
          target:
            entity_id: calendar.EXAMPLE
          response_variable: agenda
        - variables:
            combined: |
              {% set a = merge_response(agenda) %}
              {{ a if combined is undefined else a + combined }}
# Then use the variable `combined` in whatever action you are doing.

Thanks Digeri, that sounds like a solid approach. I’ll give it a go and report back.

OK, here’s what I got working, with Digeri’s fantastic pointers. To elaborate a bit (a) the calendar is quite sparse, so I’m able to get results for several years in the response variable (b) I decided that I wanted to get events for the current week, any year. (This is a calendar where I track meals I’ve cooked/eaten so I’m trying to get seasonal inspiration for food).

Part one: get everything from the last ~5 years

    action:
      - action: calendar.get_events
        data: # Time ranges below provide a date range from start of 2021 to Sunday at end of current week
          start_date_time: "2021-01-01 00:00:00"
          end_date_time: "{{ (as_timestamp(today_at('18:30')) + (((6-today_at('19:30').weekday()))*24*60*60)) | timestamp_local }}"
        target:
          entity_id: calendar.family
        response_variable: menu_history

Part two: iterate through the results, using a regex match on start_date to match anything with month/day the same as the current week. I use a for loop to generate the regex -(08-31|09-01|09-02|09-03|09-04|09-05)T18:30:00.

{% set ns=namespace(date_regex='-(',start_of_week='') %}
{% set ns.start_of_week= (as_timestamp(today_at('18:30')) - ((today_at('18:30').weekday())*24*60*60)) | timestamp_local %}
{% for day in range(0,6) %}{% set ns.date_regex = ns.date_regex ~ (as_datetime(ns.start_of_week) + timedelta(days=day)).strftime('%m-%d') ~ iif(loop.last,'','|') %}{% endfor %}
{% set ns.date_regex = ns.date_regex ~ ')T18:30:00' %}

Menu this week: {{(menu_history['calendar.family'].events) | selectattr('start','search',ns.date_regex) |  list | sort(attribute='start') }}

Notes, caveats, disclaimers, how to do better:

  • Yes, I realise now I could have used timedelta(day=…) instead of subtracting timestamps, but it’s working now so…
  • Arguably “any event 18:30-19:00” is not the best way to flag food diary entries, but that’s just how I ended up capturing cooking notes and it stuck
  • I spent a lot of time trying to figure out the for loop. Since I’ve not seen it particularly well documented (and as I understand it), once you enter a for loop, you’re in a new scope, so any variables declared outside the loop no longer exist, unless you declare and reference them as part of a namespace.

Silly me, I was looking in the Jinja2 documentation rather than the HA documentation! Thanks.

It’s in there too… :laughing:

Assignments - Scoping Behavior

Haha! But “Assignments - Scoping Behaviour” would not have been the first search term I’d have thought to look for!