Générer une liste d'anniversaires depuis Home Assistant et un calendrier

Afin de ne plus oublier aucun anniversaire, j'utilise Home Assistant depuis plusieurs mois pour recevoir des notifications d'anniversaire personnalisées, à partir d'un calendrier

Dans cet article, nous allons voir comment réaliser un script Home Assistant permettant la génération du texte suivant :

🎂 C'est l'anniversaire de Pauline. Prochainement, ce sera celui de Benjamin (sam 16) et Xavier (ven 15).

Ce script permet ensuite de générer des notifications sur téléphone ou de créer des annonces sur des enceintes connectées.

Pré-requis

Avant de commencer, vous devez disposer de :

  • Une instance Home Assistant fonctionnelle ;
  • Un calendrier déjà intégré à Home Assistant (Google Calendar, CalDAV...) sur lequel se trouvent les anniversaires.

Configuration du script

Le script nommé « Génération liste anniversaires » est configuré avec :

  • une icône de gâteau ;
  • un fonctionnement en mode parallèle, avec jusqu'à 10 instances simultanées.
(yml)
1alias: Génération liste anniversaires
2icon: mdi:cake
3mode: parallel
4max: 10

Paramètres d'entrée

En entrée, le script accepte trois paramètres :

  • date : date de référence pour la recherche (utile pour préparer un message la veille par exemple) ;
  • days_to_show : nombre de jours à afficher après la date de référence (pour savoir que d'autres anniversaires vont arriver) ;
  • calendar_name : entité du calendrier où récupérer les événements.
(yml)
1fields:
2  date:
3    name: Date de référence
4    selector:
5      date: {}
6
7  days_to_show:
8    name: Nombre de jours à afficher
9    selector:
10      number:
11        min: 1
12        max: 60
13        step: 1
14        mode: slider
15    default: 15
16
17  calendar_name:
18    name: Calendrier à utiliser
19    selector:
20      entity:
21        domain: calendar
22    default: calendar.anniversaires

Déroulé du script

Les différentes étapes du script sont définies dans un objet sequence :

(yml)
1sequence:

Définition de variables

Nous allons définir un certain nombre de variables qui nous seront utiles plus tard :

  • daynames et monthnames : le nom des jours de la semaine et des mois en français ;
  • period_start et period_end : qui correspondent à la date de début et de fin de la période.
(yml)
1- variables:
2    daynames: ['lun', 'mar', 'mer', 'jeu', 'ven', 'sam', 'dim']
3    monthnames: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre']
4
5    period_start: "{% if date is not defined %}{{ now().date() }}{% else %}{{ date }}{% endif %}"
6    period_end: "{{ (as_datetime(period_start) + timedelta(days=days_to_show)).date() }}"

Les variables auront donc des valeurs correspondantes aux suivantes :

period_start: '2025-08-13'
period_end: '2025-08-28'

Récupération des événements du calendrier

Nous allons maintenant récupérer les événements du calendrier pour la période :

(yml)
1- action: calendar.get_events
2    target:
3      entity_id: "{{ calendar_name }}"
4    data:
5      start_date_time: "{{ period_start }}"
6      end_date_time: "{{ period_end }}"
7    response_variable: events_entities

ce qui retournera un objet comme suit :

events_entities:
  calendar.anniversaires:
    events:
      - start: '2025-08-13'
        end: '2025-08-14'
        summary: Pauline
      - start: '2025-08-16'
        end: '2025-08-17'
        summary: Benjamin
      - start: '2025-08-22'
        end: '2025-08-23'
        summary: Xavier

Séparation des événements

Nous allons maintenant séparer les événements en deux :

  • Les événements du jour de référence (period_start), et les autres événements à venir :
(yml)
1- variables:
2      date_events: "{{ events_entities[calendar_name]['events'] | selectattr('start', 'eq', period_start) | list }}"
3      later_events: "{{ events_entities[calendar_name]['events'] | rejectattr('start', 'eq', period_start) | list }}"
date_events:
  - start: '2025-08-13'
    end: '2025-08-14'
    summary: Pauline

later_events:
  - start: '2025-08-16'
    end: '2025-08-17'
    summary: Benjamin
  - start: '2025-08-22'
    end: '2025-08-23'
    summary: Xavier

Génération du message

Nous allons enfin pouvoir passer à la construction du message final. Celui-ci est composé de plusieurs parties, qui pourront être adaptées selon le cas et les préférences :

  • start_sentence : la phrase d'introduction (aujourd'hui, demain ou la date) ;
  • date_names : le nom des personnes dont l'anniversaire correspond à period_start, séparés par des virgules ou "et" pour le dernier ;
  • later_events_with_date : la liste des anniversaires à venir avec la date ;
  • response : la mise en forme finale.
(yml)
1- variables:
2      start_sentence: |-
3        {% if date_events | count > 0 %}
4          {% set d = as_datetime(period_start).date() %}
5          {% set today = now().date() %}
6          {% set tomorrow = (now() + timedelta(days=1)).date() %}
7          🎂 {% if d == today %}C'est{% elif d == tomorrow %}Demain, ce sera{% else %}Le {{ daynames[d.weekday()] ~ ' ' ~ '%02d'|format(d.day) }}, ce sera{% endif %} l'anniversaire de
8        {% endif %}
9        
10      date_names: |-
11        {% set names = date_events | map(attribute='summary') | list %}
12        {% if names | count > 1 %}
13          {{ names[:-1] | join(', ') ~ ' et ' ~ names[-1] }}
14        {% else %}
15          {{ names | join('') }}
16        {% endif %}
17
18      later_events_with_date: |-
19        {% set l = namespace(values=[]) %}
20        {% for event in later_events %}
21          {% set d = as_datetime(event.start) %}
22          {% set l.values = l.values + [
23            event.summary ~ ' (' ~ daynames[d.weekday()] ~ ' ' ~ '%02d'|format(d.day) ~ ')'
24          ] %}
25        {% endfor %}
26        {{ l.values }}
27        
28      later_names_with_date: |-
29        {% if later_events_with_date | count > 1 %}
30          {{ later_events_with_date[:-1] | join(', ') ~ ' et ' ~ later_events_with_date[-1] }}
31        {% else %}
32          {{ later_events_with_date | join('') }}
33        {% endif %}
34
35      response:
36        value: >-
37          {% if date_events | count > 0 %}{{ start_sentence }} {{ date_names }}.{% endif %}
38          {% if later_events_with_date %} Prochainement, ce sera {% if date_events | count <= 0 %}l'anniversaire{% else %}celui{% endif %} de {{ later_names_with_date }}.{% endif %}

Sauvegarde dans un input_text (optionnel)

Il peut être intéressant de stocker le résultat du script dans une variable pour le réutiliser sur un portail par exemple :

(yml)
1- target:
2      entity_id: input_text.anniversaires_journee
3    data:
4      value: "{{ response.value }}"
5    action: input_text.set_value

Retour du script

Enfin, le script retourne le texte en résultat du script. Ce qui permet d'utiliser le script directement dans une autre automatisation :

(yml)
1- stop: returning value
2    response_variable: response

Script final

Fichier : anniversaires.yml (yml)
1alias: Génération liste anniversaires
2icon: mdi:cake
3mode: parallel
4max: 10
5
6fields:
7  date:
8    name: Date de référence
9    selector:
10      date: {}
11
12  days_to_show:
13    name: Nombre de jours à afficher
14    selector:
15      number:
16        min: 1
17        max: 60
18        step: 1
19        mode: slider
20    default: 15
21
22  calendar_name:
23    name: Calendrier à utiliser
24    selector:
25      entity:
26        domain: calendar
27    default: calendar.anniversaires
28
29sequence:
30  - variables:
31      daynames: ['lun', 'mar', 'mer', 'jeu', 'ven', 'sam', 'dim']
32      monthnames: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre']
33
34      period_start: "{% if date is not defined %}{{ now().date() }}{% else %}{{ date }}{% endif %}"
35      period_end: "{{ (as_datetime(period_start) + timedelta(days=days_to_show)).date() }}"
36
37  - action: calendar.get_events
38    target:
39      entity_id: "{{ calendar_name }}"
40    data:
41      start_date_time: "{{ period_start }}"
42      end_date_time: "{{ period_end }}"
43    response_variable: events_entities
44
45  - variables:
46      date_events: "{{ events_entities[calendar_name]['events'] | selectattr('start', 'eq', period_start) | list }}"
47      later_events: "{{ events_entities[calendar_name]['events'] | rejectattr('start', 'eq', period_start) | list }}"
48
49  - variables:
50      start_sentence: |-
51        {% if date_events | count > 0 %}
52          {% set d = as_datetime(period_start).date() %}
53          {% set today = now().date() %}
54          {% set tomorrow = (now() + timedelta(days=1)).date() %}
55          🎂 {% if d == today %}C'est{% elif d == tomorrow %}Demain, ce sera{% else %}Le {{ daynames[d.weekday()] ~ ' ' ~ '%02d'|format(d.day) }}, ce sera{% endif %} l'anniversaire de
56        {% endif %}
57        
58      date_names: |-
59        {% set names = date_events | map(attribute='summary') | list %}
60        {% if names | count > 1 %}
61          {{ names[:-1] | join(', ') ~ ' et ' ~ names[-1] }}
62        {% else %}
63          {{ names | join('') }}
64        {% endif %}
65
66      later_events_with_date: |-
67        {% set l = namespace(values=[]) %}
68        {% for event in later_events %}
69          {% set d = as_datetime(event.start) %}
70          {% set l.values = l.values + [
71            event.summary ~ ' (' ~ daynames[d.weekday()] ~ ' ' ~ '%02d'|format(d.day) ~ ')'
72          ] %}
73        {% endfor %}
74        {{ l.values }}
75        
76      later_names_with_date: |-
77        {% if later_events_with_date | count > 1 %}
78          {{ later_events_with_date[:-1] | join(', ') ~ ' et ' ~ later_events_with_date[-1] }}
79        {% else %}
80          {{ later_events_with_date | join('') }}
81        {% endif %}
82
83      response:
84        value: >-
85          {% if date_events | count > 0 %}{{ start_sentence }} {{ date_names }}.{% endif %}
86          {% if later_events_with_date %} Prochainement, ce sera {% if date_events | count <= 0 %}l'anniversaire{% else %}celui{% endif %} de {{ later_names_with_date }}.{% endif %}
87
88  - target:
89      entity_id: input_text.anniversaires_journee
90    data:
91      value: "{{ response.value }}"
92    action: input_text.set_value
93
94  - stop: returning value
95    response_variable: response

Valentin LORTET

Cet article vous a plu ? N'hésitez pas à le partager.

Découvrir d’autres articles