How to list multiple calendar event in a template

If that’s what you want to do then it can be done without the use of calendar.list_events. What you described can be achieved with a Calendar Trigger.

If you mean the example in this post, then read the post prior to it. You’ll see I took someone else’s example (containing German text) and simplified its templates.

You’ll need to explain what you want exactly.

I would like to format the event times, when the TTS reads it out so that Tuesday @ 4pm etc at the moment I have
image

For determining if a date is today, tomorrow, in X days, etc then you may want to consider using these custom macros:

1 Like

Okay thanks, I will have a read

Thanks for the tip!

I used a different approach, because I would like to send multiple notifications. I’m now using this automation at a given time:

automation:
  - alias: "Notify Events for Today"
    trigger:
      platform: time
      at: "08:00:00"
    action:
      service: notify.your_notification_service
      data:
        title: "Today's Events"
        message: >-
          {% set today = now().strftime('%Y-%m-%d') %}
          {% set events_today = state_attr('sensor.gary_scheduled_events', 'scheduled_events') | selectattr('start', 'eq', today) | list %}
          {% for e in events_today %}
          {{ e.summary }}
          {% endfor %}

This works like a charm!

Next step is cleaning up code (using correct names for this service) and I’ll put a ‘how to’ on this topic.

automation:
  - alias: "Notify Events for Today"
    trigger:
      platform: time
      at: "08:00:00"
    action:
      service: notify.your_notification_service
      data:
        title: "Today's Events"
        message: >-
          {% for e in state_attr('sensor.robin_scheduled_events', 'scheduled_events')
            | selectattr('start', 'eq', now().strftime('%Y-%m-%d')) %}
          {{ e.summary }}
          {% endfor %}
1 Like

Thanks guys for the inspiration, I could not figure out the easy time approach, I resolved it by just inserting a timestamp similar to Robin.

    Garys Agenda for today: {% for e in
    state_attr('sensor.garys_agenda','scheduled_events') %}
      {{ as_timestamp(e.start) | timestamp_custom ('%A %H:%M %p') }}: {{ e.summary }} {% endfor %}

    Christines Aganda for today: {% for e in
    state_attr('sensor.christines_agenda', 'scheduled_events') %}
      {{ as_timestamp(e.start) | timestamp_custom ('%A %H:%M %p') }}: {{ e.summary }} {% endfor %}
1 Like

Just one thing left for me to polish it a little, just have to work out the code to do the following:
if day = now then “Today” not the day of the week.

I finally got day changed to today and tomorrow, I am not sure if there is a better way to do it? I would be interested if there was a better way, however this seems to work okay. The only issue I cant resolve is the line spaces in between each event. See Garys agenda below as apposed to Christines agenda
image

    Gary you have {{states('sensor.garys_agenda')}} items in your Agenda: {% for
    e in state_attr('sensor.garys_agenda','scheduled_events') %}
      {% set start = e.start | as_datetime | as_local %}
      {% if start.strftime('%A') == now().strftime('%A') %}Today at {{ as_timestamp(e.start) | timestamp_custom ('%H:%M %p')}} {{ e.summary }}{%else%}{% if start.strftime('%A') > now().strftime('%A')%}Tomorrow at {{ as_timestamp(e.start) | timestamp_custom ('%H:%M %p')}} {{ e.summary }}{% endif %}{% endif %}{% endfor %}

The blank lines seen in the output are due to this line in your template.

      {% set start = e.start | as_datetime | as_local %}

It’s represented by a blank line for each iteration of the for-loop.

You can suppress it by adding hypens to the braces, like this:

      {%- set start = e.start | as_datetime | as_local -%}

Reference:
Jinja2 - Whitespace control

Assuming your sensor is constrained to containing events exclusively for today and tomorrow, you can use this streamlined version:

Gary you have {{ states('sensor.garys_agenda') }} items in your Agenda:
{% for e in state_attr('sensor.garys_agenda','scheduled_events') %}
{%- set start = e.start | as_datetime | as_local -%}
{{ ['Today', 'Tomorrow'][(start.date() - now().date()).days] }} at {{ start.strftime('%H:%M %p') }} {{ e.summary }}
{% endfor %}

Thanks Taras, I did notice the {%- in a few posts, but didn’t understand the significance? Will give it a go.
Will also give the new code a go and try to understand it.

On a side note: I see you helping a number of individuals throughout the community - you must know this stuff inside out, you not only give instruction but provide insight as well, thank you and good work from the whole community.

1 Like

This is a list containing two items:

['Today', 'Tomorrow']

List items are indexed starting with zero. Therefore this will display the first item in the list (Today).

['Today', 'Tomorrow'][0]

This displays the second item in the list (Tomorrow).

['Today', 'Tomorrow'][1]

Subtracting two datetime objects produces a timedelta object. One of the properties of a timedelta object is days. In other words, the timedelta object’s value is reported as the number of days.

(start.date() - now().date()).days

If the difference between start.date() and now().date() is 0 then it means the event starts today. If the difference is 1 then it starts tomorrow. We use this difference value to get the associated item from the list.

{{ ['Today', 'Tomorrow'][(start.date() - now().date()).days] }}
1 Like

Thank you for the explanation, helps a lot especially in the future,
By adding the streamline ver, I realise now why my original ver wasn’t pulling in the next day, I only defend 2 in the list, however your solution brings in the whole list and defines 2 names. Yours is streamlined but also a better solution as if I have items in the third day I can see them as below:
image
I have been playing around with if statements trying to get the items after tomorrow to default back to the day “Monday” etc? No success so far. Also I have been looking into “carriage returns” something so obvious seems difficult, say I want a “carriage return” after the first line, how do you do that?

It’s 8 days and dozens of posts later and I have run out of free time for this topic. Hopefully someone else can assist you. Good luck.

1 Like

No problem, thank you for all your help

image

something is wrong here

Invalid config for [sensor.template]: [sensor] is an invalid option for [sensor.template]. Check: sensor.template->sensor. (See ?, line ?).

Yes it’s because you tried to configure a Trigger-based Template Sensor using a mixture of the formats for both a legacy and modern Template Sensor.

  • There are two ways to configure a Template Sensor, using either modern or legacy format.

  • There’s only one way to configure a Trigger-based Template Sensor and that’s using modern format.

Modern format Template entities are configured under the template: key and use different option names from legacy format. For example, modern format doesn’t use platform: template.

works now…
is it
a) possible to add more as one calendar
b) possible to split the entities in e.g.
event01name
event01daystart
event01dayend
event01timestart
event01timeend
event01location
event02name

background is, i want show this entities on a display (esphome + epaper)

Just going to add this here since someone asked in Discord and linked back to this thread…

To get events from multiple calendar entities into a single sensor, send them to unique response variables then concatenate the lists:

2023.8 version using Automation
alias: Multiple Cal to Event
trigger:
  - platform: calendar
    event: end
    entity_id: calendar.a
  - platform: calendar
    event: end
    entity_id: calendar.b
sequence:
  - service: calendar.list_events
    data:
      duration:
        hours: 72
        minutes: 0
        seconds: 0
    target:
      entity_id:
        - calendar.a
    response_variable: agenda1
  - service: calendar.list_events
    data:
      duration:
        hours: 72
        minutes: 0
        seconds: 0
    target:
      entity_id:
        - calendar.b
    response_variable: agenda2
  - event: custom_multical_event
    event_data:
      scheduled_events: "{{ agenda1.events + agenda2.events}}"
mode: single
template:
  - trigger:
      - platform: event
        event_type: custom_multical_event
    sensor:
      - name: Multi Cal Events
        unique_id: multical_events_0001
        state: "{{ trigger.event.data.scheduled_events | count() }}"
        attributes:
          events: "{{ trigger.event.data.scheduled_events }}"
        icon: mdi:calendar

UPDATE: 2023.9

Introduction of actions into trigger-based template sensors make the intermediary automation unnecessary.

  - trigger:
      - platform: calendar
        event: end
        entity_id: calendar.a
      - platform: calendar
        event: end
        entity_id: calendar.b
      - platform: calendar
        event: end
        entity_id: calendar.c
      - platform: event
        event_type: event_template_reloaded
    action:
      - service: calendar.list_events
        data:
          duration:
            hours: 24
        target:
          entity_id:
            - calendar.a
        response_variable: agenda1
      - service: calendar.list_events
        data:
          duration:
            hours: 24
        target:
          entity_id:
            - calendar.b
        response_variable: agenda2
      - service: calendar.list_events
        data:
          duration:
            hours: 24
        target:
          entity_id:
            - calendar.c
        response_variable: agenda3
    sensor:
      - name: Next 24hr Events
        unique_id: custom_multical_todays_events
        state: |
          {% set ns = namespace(alt_events=[]) %}
          {% for e in agenda1.events %}
          {% set ns.alt_events = ns.alt_events + [e]  %}
          {% endfor%}
          {% for e in agenda2.events %}
          {% set ns.alt_events = ns.alt_events + [e]  %}
          {% endfor%}
          {% for e in agenda3.events %}
          {% set ns.alt_events = ns.alt_events + [e]  %}
          {% endfor%}
          {{ns.alt_events|selectattr('start', 'search', now().date()|string)
          | list | count}}
        attributes:
          scheduled_events: |
            {% set ns = namespace(alt_events=[]) %}
            {% for e in agenda1.events %}
              {% set ns.alt_events = ns.alt_events + [dict(e, **{"calendar":"A"})]  %}
            {% endfor%}
            {% for e in agenda2.events %}
              {% set ns.alt_events = ns.alt_events + [dict(e, **{"calendar":"B"})]  %}
            {% endfor%}
            {% for e in agenda3.events %}
              {% set ns.alt_events = ns.alt_events + [dict(e, **{"calendar":"C"})]  %}
            {% endfor%}
            {{ns.alt_events | sort(attribute='start') | list }}

EDIT: The template above is more complicated than it needs to be for normal use. However, using this method allows you to add data to every event. I have updated the template to show how to do that.

If you do not want/need to add calendar origin data to your events you can use the list concatenation shown in petro’s post below.

1 Like