Wie lange noch bis zum Zahnarzt? Countdown bis zu einem bestimmten Kalendertermin

Moin

Ich hatte Spaß an dem Beitrag Renten-Countdown und war über das Interesse an einem Renten-Countdown überrascht. Am Ende tauchte der Kommentar auf.

Erst dachte ich, ohjee wird ja immer besser aber ich scripte gerne.

Momentane Umsetzung

Wie es funktioniert:

  • Man gibt ein Suchwort ein (Wildcard Suche im Titel der Termine)
  • Man wählt in einem Dropdown Helfer seinen Kalender aus.
  • Man sagt, wieviel Jahre in der Zukunft die Termine geprüft werden sollen
  • Man klickt auf Start Button und es
    • startet die Suche nach passenden Terminen
    • beim ersten Treffer wird der Starttermin des Termines genommen und damit oberer Countdown gestartet.
  • Man öffnet die obere Markdownkarte und gibt im Code den Titel ein
    {% set CoundownTitel = 'Zahnarzt' %}
  • Sobald der Countdown abgelaufen, wird dies entsprechend angezeigt.

Wie initial nachbauen:

  1. input_text.countdown_kalender_termintext anlegen (Minimalmenge kann bei 0 belassen werden, fange das im Code ab)
  2. input_select.selectbox_meinekalender anlegen und mit Deinen Kalendernamen befüllen. z.B. heißt die Entity “calendar.1sonos_alle_boxen” aber Du trägst nur “1sonos_alle_boxen” in. Icon auswählen was nachher auf dem Dashboard zu sehen sein soll
  3. input_number.countdown_kalenderjahre anlegen und Minimalwert auf 1 setzen, Icon auswählen was nachher auf dem Dashboard zu sehen sein soll
  4. input_datetime.countdown_start_date anlegen als Datum und Uhrzeit
  5. Ein Script “om_scr_tech_finde_naechsten_termin” anlegen mit unterem Code. Das Script sucht nach Terminen und setz den Helfer input_datetime.countdown_start_date entsprechend.
Zusammenfassung
sequence:
  - variables:
      kalendername: |
        calendar.{{states('input_select.selectbox_meinekalender')}}
      suchtext: |
        {{states('input_text.countdown_kalender_termintext') | lower}}
      kalenderjahre: |
        {{states('input_number.countdown_kalenderjahre')}}
      startzeit: |
        {{ now() }}
      endzeit: |
        {{ as_datetime(startzeit) + timedelta(days=kalenderjahre * 365) }}
  - if:
      - condition: template
        value_template: "{{states('input_text.countdown_kalender_termintext') | count == 0}}"
    then:
      - action: input_text.set_value
        metadata: {}
        data:
          value: Bitte ein Suchwort eingeben!
        target:
          entity_id: input_text.countdown_kalender_termintext
      - stop: Kein Suchwort vorhanden!
  - action: calendar.get_events
    metadata: {}
    data:
      start_date_time: "{{startzeit}}"
      end_date_time: "{{endzeit}}"
    target:
      entity_id: "{{kalendername}}"
    response_variable: termine
  - variables:
      starttermin: >-
        {% set TerminGefunden = false %} {% set events = termine.values() |
        map(attribute='events') | list | first %} {% set ns =
        namespace(found=false) %} {% for e in events %}
          {% if not ns.found and suchtext | string in e.summary | lower %}
            {{ e.start }}
            {% set ns.found = true %}
          {% endif %}
        {% endfor %}
  - if:
      - condition: template
        value_template: "{{starttermin != ''}}"
    then:
      - action: input_datetime.set_datetime
        metadata: {}
        data:
          datetime: "{{starttermin}}"
        target:
          entity_id: input_datetime.countdown_start_date
        enabled: true
    else:
      - action: input_text.set_value
        metadata: {}
        data:
          value: Keinen passenden Termin gefunden!
        target:
          entity_id: input_text.countdown_kalender_termintext
      - action: input_datetime.set_datetime
        metadata: {}
        data:
          date: "2025-01-01"
        target:
          entity_id: input_datetime.countdown_start_date
alias: om_scr_tech_finde_naechsten_termin
description: >-
  ## Script sucht in einem Kalender nach einen bestimmten Termin und speichert
  dessen Starttermin in einen Datumshelfer
mode: restart
  1. Auf dem Dashboard legt Ihr einen neuen Abschnitt an und kopiert über Bearbeiten diesen Code ein

Zusammenfassung
type: grid
cards:
  - type: heading
    icon: mdi:archive-clock-outline
    heading: Kalender Countdown
    heading_style: subtitle
  - type: markdown
    content: >-
      {# Code Version 1.0 #}

      {% set CoundownTitel = 'Zahnarzt' %}


      {# --- Layout Parameter --- #}

      {% set fontsize_zahlen = 6 %}

      {% set fontsize_worte = 4 %}

      {% set fontsize_slider = 5 %}

      {% set spaltenbreite = 100 %}

      {% set IconCode_active = '<ha-icon icon="mdi:heart-multiple"></ha-icon>'
      %}


      {# --- Start Datums --- #}

      {% set TargetDate =
      as_local(as_datetime(states('input_datetime.countdown_start_date'))) %}

      {% set Abgelaufen = now() > TargetDate %}


      {# --- Hilfe für Darstellung Zieldatum --- #}

      {# Wandelt englische Monate/Tage in dt #}

      {% set wochentag = {
        'Monday': 'Montag', 'Tuesday': 'Dienstag', 'Wednesday': 'Mittwoch',
        'Thursday': 'Donnerstag', 'Friday': 'Freitag', 'Saturday': 'Samstag', 'Sunday': 'Sonntag'
      }[TargetDate.strftime('%A')] %}


      {% set monat = {
        'January': 'Januar', 'February': 'Februar', 'March': 'März', 'April': 'April',
        'May': 'Mai', 'June': 'Juni', 'July': 'Juli', 'August': 'August',
        'September': 'September', 'October': 'Oktober', 'November': 'November', 'December': 'Dezember'
      }[TargetDate.strftime('%B')] %}


      {% set TargetDate_title = wochentag ~ ', ' ~ TargetDate.day ~ '. ' ~ monat
      ~ ' ' ~ TargetDate.year  ~ ' um ' ~ TargetDate.hour ~ ':' ~
      TargetDate.minute  ~ ' Uhr' %}


      {# --- Berechnung der verbleibenden Zeit --- #}

      {% if Abgelaufen == false %}
        {% set time_str = time_until(as_datetime(TargetDate), 5) %}
        
        {# Default Werte Null weil die Funktion time_until keine Leerzeiten anzeigt, z.B. im selben Monat steht nicht "0 months" sondern nichts #}
        {% set years_tmp = namespace(val=0) %}
        {% set months_tmp = namespace(val=0) %}
        {% set days_tmp = namespace(val=0) %}
        {% set hours_tmp = namespace(val=0) %}
        {% set minutes_tmp = namespace(val=0) %}
        {# prüft ob der zeitraum vorkommt und nimmt diesen oder bleibt bei default 0 #}
        {% for part in time_str.split(' ') %}
          {% if 'year' in part %}
            {% set years_tmp.val = time_str.split(' ')[loop.index0 - 1] | int %}
          {% elif 'month' in part %}
            {% set months_tmp.val = time_str.split(' ')[loop.index0 - 1] | int %}
          {% elif 'day' in part %}
            {% set days_tmp.val = time_str.split(' ')[loop.index0 - 1] | int %}
          {% elif 'hour' in part %}
            {% set hours_tmp.val = time_str.split(' ')[loop.index0 - 1] | int %}
          {% elif 'minute' in part %}
            {% set minutes_tmp.val = time_str.split(' ')[loop.index0 - 1] | int %}
          {% endif %}
        {% endfor %}
        
        {% set years = years_tmp.val %}
        {% set months = months_tmp.val %}
        {% set days = days_tmp.val %}
        {% set hours = hours_tmp.val %}
        {% set minutes = minutes_tmp.val %}

        {# --- Positionsberechnung --- #}
        {% if years > 0 %}
          {% set position_slider = 1 %}
        {% elif months > 0 %}
          {% set position_slider = 2 %}
        {% elif days > 0 %}
          {% set position_slider = 3 %}
        {% elif hours > 0 %}
          {% set position_slider = 4 %}
        {% else %}
          {% set position_slider = 5 %}
        {% endif %}
      {% endif %}

      {# --- Darstellung --- #}


      <font size=6>{{ CoundownTitel }}</font>

      <font size=4>{{ TargetDate_title }}</font>

      {% if Abgelaufen == false %}

      <table border=0>
        <tr>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_zahlen }}>{{ years }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_zahlen }}>{{ months }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_zahlen }}>{{ days }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_zahlen }}>{{ hours }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_zahlen }}>{{ minutes }}</font></center></td>
        </tr>
        <tr>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_worte }}>Jahre</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_worte }}>Monate</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_worte }}>Tage</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_worte }}>Stunden</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_worte }}>Minuten</font></center></td>
        </tr>
        <tr>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_slider }}>{{ IconCode_active if position_slider == 1 else '' }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_slider }}>{{ IconCode_active if position_slider == 2 else '' }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_slider }}>{{ IconCode_active if position_slider == 3 else '' }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_slider }}>{{ IconCode_active if position_slider == 4 else '' }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_slider }}>{{ IconCode_active if position_slider == 5 else '' }}</font></center></td>
        </tr>
      </table>

      {% else %}

        Der Countdown ist abgelaufen!

      {% endif %}
    card_mod:
      style: |
        ha-card {
          background: rgba(50, 50, 50, 0.25); /* 0,25=25% Deckung */
          color: white;
          box-shadow: none;
        }
  - type: heading
    icon: mdi:cog-outline
    heading_style: subtitle
    heading: Einstellungen
  - type: entities
    entities:
      - entity: input_text.countdown_kalender_termintext
        name: Suchtext
        secondary_info: none
        card_mod:
          style: |
            :host {
              --state-icon-color: white;
            }
      - entity: input_select.selectbox_meinekalender
        name: Kalender
        card_mod:
          style: |
            :host {
              --state-icon-color: white;
            }
      - entity: input_number.countdown_kalenderjahre
        name: Wieviele Jahre ?
        card_mod:
          style: |
            :host {
              --state-icon-color: white;
              font-size: 11pt !important;
            }
    show_header_toggle: true
    state_color: false
    card_mod:
      style: |
        ha-card {
          background: rgba(50, 50, 50, 0.25);
          color: white;
          --card-primary-color: white;
          box-shadow: none;
        }
  - show_name: true
    show_icon: true
    type: button
    name: Start
    grid_options:
      columns: full
      rows: 2
    hold_action:
      action: none
    tap_action:
      action: perform-action
      perform_action: script.turn_on
      target:
        entity_id: script.om_scr_tech_finde_naechsten_termin
    card_mod:
      style: |
        ha-card {
          height: 30px  !important;
        }

Und dann probiert und paßt alles an was Ihr anpassen wollt.

Ideen, was man noch verändern könnte:

  • Layout
  • Andere Elemente für Text, Selectbox und Button Jahre verwenden
  • Einstellungen ein-/ausblenden
  • Kalernamen in der Selectbox schöne Namen geben und dann im Script ein Mapping schöner Name zu ID machen.
  • Anstelle in der Markdown Karte den Titel manuell ausgeben könnte dort auch automatisiert der komplette Termin-Titel stehen oder die ersten 20 Zeichen etc.

Bitte meldet mir gefundene Fehler und/oder postet Eure Veränderungen und Screenshots.

Ich bin gespannt.

8 „Gefällt mir“

Also meine Farbe ist das definitiv nicht aber meiner Frau gefällts :wink:

Die Einstellungen habe ich aber auf-/zuklappbar gemacht.

1 „Gefällt mir“

Ein Hinweis zur Zeitberechnung:

Der obige Countdown verwendet die HA eigene Funktion time_until, z.B.
{{time_until(as_datetime('2025-10-10 00:00'),5)}}
Diese Funktion berechnet den Zeitunterschied zwischen now() und dem TargetDate.
Leider ist das keine kalendertaggenaue Berechnung sondern weicht leicht von dieser ab. Je näher das Target rückt, je genauer. Das ist unbefriedigend im Kontext eines Countdown von dem man Exaktheit von Anfang an erwartet aber ich kenne momentan nichts Besseres in HA.
Bitte also keine Medikament Erinnerungen etc. oder irgendwas wirklich Wichtiges.

Dann schreib ich halt meine eigene time_until_kalender Funktion (Wenn mich etwas wurmt, dann wurmt es mich …)

Ich habs versucht die Ungenauigkeit zu ignorieren :slight_smile: aber von einem Counter erwarte ich Genauigkeit von Anfang an.

Und so schrieb ich meine eigene Funktion, die zum einen auf eine fest hinterlegtes Dictionary mit der Anzahl von Kalendertagen pro Monat beruht inklusive Schaltjahre und zum anderen auf die wirklich genaue HA Berechnung über das .days Attribut eines Delta Time Objects.

Die Idee:

{% set kalender_dict = {
  "normal": ["01:31", "02:28", "03:31", "04:30", "05:31", "06:30", "07:31", "08:31", "09:30", "10:31", "11:30", "12:31"],
  "schaltjahr": ["01:31", "02:29", "03:31", "04:30", "05:31", "06:30", "07:31", "08:31", "09:30", "10:31", "11:30", "12:31"]
} %}

Anhand dieses Dictionaries werden die Monate zwischen now() und einem individuellem End-Datum/Zeit stur gezählt und nicht irgendwelche Deltas auf Jahre/Monate/Tage bestmöglich umgebrochen

Wie funktioniert es?
Man übergibt der Funktion/Makro einen Timestring

{{ time_until_kalender("2025-06-15 00:00") }}

und erhält so einen String zurück

Und was ist der Unterschied zur HA eigenen time_until Funktion?

Die in HA hinterlegte Berechnung bricht Jahre/Monate/Tage in einer Art und Weise um, die meistens aber eben nicht immer stimmt. Monate haben unterschiedliche Anzahl von Tagen und Schaltjahre gibt es auch.
Meine Funktion zählt wirklich die Monate anhand eines hinterlegten Musters.

Hier der Code

Zusammenfassung
{% macro time_until_kalender(enddate) %}
  {#
    Version 1.1 Bacardi
    Makro zur Berechnung von Zeitdifferenz zwischen now() und dem geliefertem Enddatumin
    Das Ergebnis ist ein String "x years x months x days x hours x minutes".
    
    - Basis für die Berechnung von Jahren/Monaten ist ein hinterlegtes Dictionarys mit der Anzahl von Kalendertagen pro Monat.
      Schaltjahre sind ebenfalls berücksichtigt.
    - Basis für Tage/Stunden/Minuten ist das Time-Object Attribut ().days.
      Es ermittelt die korrekte Anzahl von Kalendertagen inklusive der Berücksichtigung von Schaltjahren.

    - Warum nicht einfach die time_until Funktion von Homeassistant verwenden?
      Weil die hinterlegte Berechnung nicht kalendertaggenau ist. Sie bricht Jahre/Monate/Tage in einer Art und Weise die meistens aber eben nicht immer stimmt.
  #}

  {% set kalender_dict = {
    "normal": ["01:31", "02:28", "03:31", "04:30", "05:31", "06:30", "07:31", "08:31", "09:30", "10:31", "11:30", "12:31"],
    "schaltjahr": ["01:31", "02:29", "03:31", "04:30", "05:31", "06:30", "07:31", "08:31", "09:30", "10:31", "11:30", "12:31"]
  } %}

  {% set start = now().replace(tzinfo=None) %}
  {% set end = strptime(enddate, "%Y-%m-%d %H:%M") %}
  {% set ns = namespace(months=0, current=start) %}

  {# Anhand der Kalendertage pro jeweiligen Monat aus dem Dictionary wird zum nächsten Monat gesprungen und Monate gezählt #}
  {% set max_months = ((end - start).days // 28) + 2 %}
  {% for _ in range(max_months) %}
    {# Zählt Jahre hoch sobald 12 Monate erreicht #}
    {% set year_tmp = ns.current.year + (1 if ns.current.month == 12 else 0) %}
    {# Zählt Monate von 1-12 #}
    {% set month_tmp = 1 if ns.current.month == 12 else ns.current.month + 1 %}
    {# Prüft ob momentan geprüftes Jahr Schaltjahr ist #}
    {% set kalender = kalender_dict[
      "schaltjahr" if year_tmp % 4 == 0 and (year_tmp % 100 != 0 or year_tmp % 400 == 0) else "normal"
    ] %}
    {# Prüft ob momentaner Tag bereits Monatsende ist und springt 1 Monat vorwärts#}
    {% set days_per_month = kalender[month_tmp - 1].split(':')[1] | int %}
    {% set day_tmp = ns.current.day if ns.current.day <= days_per_month else days_per_month %}
    {% set nextMonthJump = ns.current.replace(year=year_tmp, month=month_tmp, day=day_tmp) %}
    {# Abbruch Schleife sofern End Monat erreicht #}
    {% if nextMonthJump > end %}
      {% break %}
    {% endif %}
    {% set ns.months = ns.months + 1 %}
    {% set ns.current = nextMonthJump %}
  {% endfor %}


  {# Berechnung der Zeiten #}
  {# Ganzzahl der Jahre basierend auf gezählte Monate #}
  {% set years = ns.months // 12 %}
  {# Teilt die gezählten Monat durch 12 und der Divisionsrest sind die Monate #}
  {% set months = ns.months % 12 %}
  {# Berechnet die Tagesdifferenz zwischen letzter Spungmarke aus obiger Monatsermittlung und dem Enddatum#}
  {% set time_delta = end - ns.current %}
  {% set days = time_delta.days %}
  {# Berechnung der Stunden/Minuten #}
  {% set hours = (time_delta.seconds // 3600) %}
  {% set minutes = ((time_delta.seconds % 3600) // 60) %}

  {{ years }} years {{ months }} months {{ days }} days {{ hours }} hours {{ minutes }} minutes
{% endmacro %}


{{ time_until_kalender("2026-07-02 00:00") }}

Testen könntet Ihr es, indem Ihr den gesamten Code in die Entwicklerwerkzeuge → Templates kopiert und dann in unterster Zeile das Datum/Zeit ändert und schaut was ausgegeben wird.

Was ich als nächstes machen werde?

  • Nochmal den Code durchgehen und neue Beispiele testen
  • Dann den Renten-Countdown und obien Kalender-Countdown anpassen und hier veröffentlichen.

Wenn Ihr helfen möchtet, testet und hinterfragt kritisch

Natürlich sagt bitte auch wenn Tests funktionieren :slight_smile:

Danke

EDIT’s:
Ich hab den Post nochmal komplett überarbeitet.

2 „Gefällt mir“

Aufgrund meiner Fragestellung,
hast Du mir ja dankenswerterweise den Tipp zu Deine Skripten gegeben.

Ich habe heute mal Beide “Rente und Terminsuche” getestet und muss sagen,
dass diese wirklich

  • extrem cool sind :clap:
  • auf einem Niveau sind, wo ich nicht mehr hinkomme und eher mit paste und copy und kleineren Anpassungen arbeite :sob:

Von daher erst nochmal danke, für die Zeit und Mühe die Du in solche Projekte steckst.

Die Terminsuche in die Zukunft ist eigentlich ziemlich genau das was ich gesucht habe.
Es wird das Datum angezeigt, die Dauer berechnet und funktioniert auch mit dem eingebundenen CALDAV Kalender (Suche dauert ein paar Sekunden)

Wenn man sich was wünschen würde…
Wäre es bei der Terminsuche, auch eine zu haben mit der Möglichkeit rückwärts zu suchen
und von den gezeigten Datumsfeldern direkt in den Kalender oder Kalender-Karte zu dem Event springen zu können.

Aber soweit erst einmal Top
Beste Grüße

@Bacardi coole Idee!

Ich sehe dich bald schon HA Python Scripts zu erstellen oder direkt Jinja integrationen :wink:

Haha, danke. In 12 Jahren gehe ich in Regelrente, vielleicht dann. Lust hätte ich schon in diese Richtung zu wachsen. Momentan sagt mein Umfeld, weniger Computer und mehr Bewegung :sleeping:

Rom wurde auch nicht an einem Tag gebaut.
Ich glaube wenn ich deine templates anschaue wirst du mit so einem jinja template und Datei zurecht kommen :slight_smile:

@Bacardi danke nochmal :slight_smile:
Hab’s hier eingefügt:

Kommt noch in meine Übersicht Markdown Karte, Bild folgt dann :slight_smile:

Starttermin habe ich so angepasst, weil ich ein Fehler bekommen habe

  - variables:
      starttermin: |-
        {% set ns = namespace(start='') %} {% if termine is mapping %}
          {% for item in termine.values() %}
            {% if item is mapping and 'events' in item %}
              {% for e in item.events %}
                {% if suchtext in e.summary | lower %}
                  {% set ns.start = e.start %}
                  {% break %}
                {% endif %}
              {% endfor %}
            {% endif %}
          {% endfor %}
        {% endif %} {{ ns.start }}

Hier mein ganzes Script mit Übergabefelder für die Berechnung der Tage

sequence:
  - variables:
      suchtext: |
        {{suchtext_field | lower}}
      startzeit: |
        {{ now() }}
      endzeit: |
        {{ as_datetime(startzeit) + timedelta(days=500) }}
  - action: calendar.get_events
    metadata: {}
    data:
      start_date_time: "{{startzeit}}"
      end_date_time: "{{endzeit}}"
    target:
      entity_id: "{{kalender_enitity}}"
    response_variable: termine
  - variables:
      starttermin: |-
        {% set ns = namespace(start='') %} {% if termine is mapping %}
          {% for item in termine.values() %}
            {% if item is mapping and 'events' in item %}
              {% for e in item.events %}
                {% if suchtext in e.summary | lower %}
                  {% set ns.start = e.start %}
                  {% break %}
                {% endif %}
              {% endfor %}
            {% endif %}
          {% endfor %}
        {% endif %} {{ ns.start }}
  - if:
      - condition: template
        value_template: "{{starttermin != ''}}"
    then:
      - action: input_number.set_value
        metadata: {}
        data:
          value: "{{ (as_datetime(starttermin) - now()).days | int + 1}}"
        target:
          entity_id: "{{day_enitity}}"
    else:
      - action: input_number.set_value
        metadata: {}
        data:
          value: 0
        target:
          entity_id: "{{day_enitity}}"
alias: finde_naechsten_termin
mode: restart
fields:
  day_enitity:
    description: day_enitity
    example: input_number.days_xxx_countdown
  kalender_enitity:
    description: kalender_enitity
    example: calendar.rolladen_xxx
  suchtext_field:
    description: suchtext_field
    example: "text"
description: ""
2 „Gefällt mir“

Hallo, das klingt und sieht sehr gut aus. Bis hierher hab ich alles korrekt gemacht. Nur der neue Abschnitt will bei mir nicht. Was mach ich denn da falsch?
Das sieht bei mir so aus:

Sieht gut aus, könntest du den Quelltext posten bitte? :wink:

Wen konkret meinst Du und für was den Quellcode?

Meinte den Code von deiner rosa Karte. Gefällt meiner Frau auch​:blush::nerd_face:

Der rosa Abschnitts Code

Zusammenfassung
type: grid
cards:
  - type: heading
    icon: mdi:archive-clock-outline
    heading: Kalender Countdown
    heading_style: title
  - type: markdown
    content: >-
      {# Code Version 1.0 #}

      {% set CoundownTitel = 'Sommerferien' %}


      {# --- Layout Parameter --- #}

      {% set fontsize_zahlen = 6 %}

      {% set fontsize_worte = 4 %}

      {% set fontsize_slider = 5 %}

      {% set spaltenbreite = 100 %}

      {% set IconCode_active = '<ha-icon icon="mdi:heart-multiple"></ha-icon>'
      %}


      {# --- Start Datums --- #}

      {% set TargetDate =
      as_local(as_datetime(states('input_datetime.countdown_start_date'))) %}

      {% set Abgelaufen = now() > TargetDate %}


      {# --- Hilfe für Darstellung Zieldatum --- #}

      {# Wandelt englische Monate/Tage in dt #}

      {% set wochentag = {
        'Monday': 'Montag', 'Tuesday': 'Dienstag', 'Wednesday': 'Mittwoch',
        'Thursday': 'Donnerstag', 'Friday': 'Freitag', 'Saturday': 'Samstag', 'Sunday': 'Sonntag'
      }[TargetDate.strftime('%A')] %}


      {% set monat = {
        'January': 'Januar', 'February': 'Februar', 'March': 'März', 'April': 'April',
        'May': 'Mai', 'June': 'Juni', 'July': 'Juli', 'August': 'August',
        'September': 'September', 'October': 'Oktober', 'November': 'November', 'December': 'Dezember'
      }[TargetDate.strftime('%B')] %}


      {% set TargetDate_title = wochentag ~ ', ' ~ TargetDate.day ~ '. ' ~ monat
      ~ ' ' ~ TargetDate.year  ~ ' um ' ~ TargetDate.hour ~ ':' ~
      TargetDate.minute  ~ ' Uhr' %}


      {# --- Berechnung der verbleibenden Zeit --- #}

      {% if Abgelaufen == false %}
        {% set time_str = time_until(as_datetime(TargetDate), 5) %}
        
        {# Default Werte Null weil die Funktion time_until keine Leerzeiten anzeigt, z.B. im selben Monat steht nicht "0 months" sondern nichts #}
        {% set years_tmp = namespace(val=0) %}
        {% set months_tmp = namespace(val=0) %}
        {% set days_tmp = namespace(val=0) %}
        {% set hours_tmp = namespace(val=0) %}
        {% set minutes_tmp = namespace(val=0) %}
        {# prüft ob der zeitraum vorkommt und nimmt diesen oder bleibt bei default 0 #}
        {% for part in time_str.split(' ') %}
          {% if 'year' in part %}
            {% set years_tmp.val = time_str.split(' ')[loop.index0 - 1] | int %}
          {% elif 'month' in part %}
            {% set months_tmp.val = time_str.split(' ')[loop.index0 - 1] | int %}
          {% elif 'day' in part %}
            {% set days_tmp.val = time_str.split(' ')[loop.index0 - 1] | int %}
          {% elif 'hour' in part %}
            {% set hours_tmp.val = time_str.split(' ')[loop.index0 - 1] | int %}
          {% elif 'minute' in part %}
            {% set minutes_tmp.val = time_str.split(' ')[loop.index0 - 1] | int %}
          {% endif %}
        {% endfor %}
        
        {% set years = years_tmp.val %}
        {% set months = months_tmp.val %}
        {% set days = days_tmp.val %}
        {% set hours = hours_tmp.val %}
        {% set minutes = minutes_tmp.val %}

        {# --- Positionsberechnung --- #}
        {% if years > 0 %}
          {% set position_slider = 1 %}
        {% elif months > 0 %}
          {% set position_slider = 2 %}
        {% elif days > 0 %}
          {% set position_slider = 3 %}
        {% elif hours > 0 %}
          {% set position_slider = 4 %}
        {% else %}
          {% set position_slider = 5 %}
        {% endif %}
      {% endif %}

      {# --- Darstellung --- #}


      <font size=6>{{ CoundownTitel }}</font>

      <font size=4>{{ TargetDate_title }}</font>

      {% if Abgelaufen == false %}

      <table border=0>
        <tr>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_zahlen }}>{{ years }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_zahlen }}>{{ months }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_zahlen }}>{{ days }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_zahlen }}>{{ hours }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_zahlen }}>{{ minutes }}</font></center></td>
        </tr>
        <tr>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_worte }}>Jahre</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_worte }}>Monate</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_worte }}>Tage</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_worte }}>Stunden</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_worte }}>Minuten</font></center></td>
        </tr>
        <tr>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_slider }}>{{ IconCode_active if position_slider == 1 else '' }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_slider }}>{{ IconCode_active if position_slider == 2 else '' }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_slider }}>{{ IconCode_active if position_slider == 3 else '' }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_slider }}>{{ IconCode_active if position_slider == 4 else '' }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_slider }}>{{ IconCode_active if position_slider == 5 else '' }}</font></center></td>
        </tr>
      </table>

      {% else %}

        Der Countdown ist abgelaufen!

      {% endif %}
    card_mod:
      style: |
        ha-card {
          background: rgba(255, 1, 100, 0.30); /* 0,25=25% Deckung */
          color: white;
          box-shadow: none;
        }
  - type: heading
    icon: mdi:cog-outline
    heading_style: subtitle
    heading: Einstellungen
    tap_action:
      action: perform-action
      perform_action: input_boolean.toggle
      target:
        entity_id: input_boolean.dashboard_raum_menue_countdown
  - type: markdown
    content: >-
      Suche in einem Homeassi Kalender nach einem bestimmten Termin, z.B.
      Zahnarzt und lasse Dir einen Countdown bis zu diesem Termin anzeigen. 
    text_only: true
    visibility:
      - condition: state
        entity: input_boolean.dashboard_raum_menue_countdown
        state: "on"
  - type: entities
    entities:
      - entity: input_text.countdown_kalender_termintext
        name: Suchtext
        secondary_info: none
        card_mod:
          style: |
            :host {
              --state-icon-color: white;
            }
      - entity: input_select.selectbox_meinekalender
        name: Kalender
        card_mod:
          style: |
            :host {
              --state-icon-color: white;
            }
      - entity: input_number.countdown_kalenderjahre
        name: Wieviele Jahre ?
        card_mod:
          style: |
            :host {
              --state-icon-color: white;
              font-size: 11pt !important;
            }
    show_header_toggle: true
    state_color: false
    card_mod:
      style: |
        ha-card {
          background: rgba(50, 50, 50, 0.25);
          color: white;
          --card-primary-color: white;
          box-shadow: none;
        }
    visibility:
      - condition: state
        entity: input_boolean.dashboard_raum_menue_countdown
        state: "on"
  - show_name: true
    show_icon: true
    type: button
    name: Start
    grid_options:
      columns: full
      rows: 2
    hold_action:
      action: none
    tap_action:
      action: perform-action
      perform_action: script.turn_on
      target:
        entity_id: script.om_scr_tech_finde_naechsten_termin
    card_mod:
      style: |
        ha-card {
          height: 30px  !important;
        }
    visibility:
      - condition: state
        entity: input_boolean.dashboard_raum_menue_countdown
        state: "on"

Viel Spaß

Hi @Bacardi
Vielen Dank. Das passt super. Komischerweise sind die Minuten beim Zieldatum abgeschnitten. Statt 08:00 wird 0:0 Uhr angezeigt.

Hallo Zusammen und va @Bacardi und @andifidi ,
ich habe versucht Eure Lösung nachzubauen…… scheitere ich zB an “script.turn_on” und evtl mehr.
Habe zwar Helfer und finde_naechsten_termn eingebaut aber erhalte nur

Kann mir jemand an dieser tollen Karte weiterhelfen?

Es gab eine Änderung vor 1-2 Monaten, die die Tabellenränder zeigt, ala

Wenn Ihr den de card_mode Code dort ersetzt durch

card_mod:
  debug: true
  style:
    ".": |
      ha-card {
        --ha-card-background: rgba(255, 1, 100, 0.30) !important;
        color: white !important;
      }
    "ha-markdown$": |
      td, th, tr {
        border: 0 !important;
        outline: 0 !important;
      }

dann sind die Ränder weg.
Dann hatte ich noch eine horizontale Scrollbar “plötzlich”

Da spielte ich dann solange mit den Parametern

{# --- Layout Parameter --- #}
{% set fontsize_zahlen = 6 %}
{% set fontsize_worte = 4 %}
{% set fontsize_slider = 5 %}
{% set spaltenbreite = 100 %}
{% set IconCode_active = '<ha-icon icon="mdi:heart-multiple"></ha-icon>' %}

bis es wieder paßte. Konkret war es am Ende nur
{% set fontsize_worte = 4 %} auf 3.

PS: Zwischendurch immer schön STRG + F5 drücken damit Ihr nicht vom Browsercache ausgetrickst werdet.

Jetzt sieht es wieder so aus.

Spontan vermute ich bei Dir, daß ein STRG+F5 helfen wird
+
die Änderungen von eben oben im Code vornehmen.

EDIT3:
Hach, das ist alles so lange her - wie man doch die Details vergißt :frowning:
Ich muß mich da erst wieder rein arbeiten.
Was für einen Scriptfehler bekommst Du denn?
Sry, ich dachte, ich mach das so im Vorbeigehen :slight_smile:

ad1: hat leider nicht geholfen (im dashboard)
ad2: habe das script nochmal kopiert - hat leider nicht geholfen
ad3: ich bekomme - wenn ich das script “manuell” starte (da ja keine Variablen gesetzt sind) “Template rendered invalid entity IDs”

PS: welches “turn_on”-Script benötige ich denn (tap_action: action: perform-action perform_action: script.turn_on)?

habe jetzt folgendes script für: “script.finde_naechsten_termin”

sequence:
  - variables:
      suchtext: |
        {{suchtext_field | lower}}
      startzeit: |
        {{ now() }}
      endzeit: |
        {{ as_datetime(startzeit) + timedelta(days=500) }}
  - action: calendar.get_events
    metadata: {}
    data:
      start_date_time: "{{startzeit}}"
      end_date_time: "{{endzeit}}"
    target:
      entity_id: "{{kalender_enitity}}"
    response_variable: termine
  - variables:
      starttermin: |-
        {% set ns = namespace(start='') %} {% if termine is mapping %}
          {% for item in termine.values() %}
            {% if item is mapping and 'events' in item %}
              {% for e in item.events %}
                {% if suchtext in e.summary | lower %}
                  {% set ns.start = e.start %}
                  {% break %}
                {% endif %}
              {% endfor %}
            {% endif %}
          {% endfor %}
        {% endif %} {{ ns.start }}
  - if:
      - condition: template
        value_template: "{{starttermin != ''}}"
    then:
      - action: input_number.set_value
        metadata: {}
        data:
          value: "{{ (as_datetime(starttermin) - now()).days | int + 1}}"
        target:
          entity_id: "{{day_enitity}}"
    else:
      - action: input_number.set_value
        metadata: {}
        data:
          value: 0
        target:
          entity_id: "{{day_enitity}}"
alias: finde_naechsten_termin
mode: restart
fields:
  day_enitity:
    description: day_enitity
    example: input_number.days_xxx_countdown
  kalender_enitity:
    description: kalender_enitity
    example: calendar.rolladen_xxx
  suchtext_field:
    description: suchtext_field
    example: "text"
description: ""

Die Karte im Dashboard sieht so aus:

type: grid
cards:
  - type: heading
    icon: mdi:archive-clock-outline
    heading: Kalender Countdown
    heading_style: title
  - type: markdown
    content: >-
      {# Code Version 1.0 #}

      {% set CoundownTitel = 'BU' %}


      {# --- Layout Parameter --- #}

      {% set fontsize_zahlen = 6 %}

      {% set fontsize_worte = 4 %}

      {% set fontsize_slider = 5 %}

      {% set spaltenbreite = 100 %}

      {% set IconCode_active = '<ha-icon icon="mdi:heart-multiple"></ha-icon>'
      %}


      {# --- Start Datums --- #}

      {% set TargetDate =
      as_local(as_datetime(states('input_datetime.countdown_start_date'))) %}

      {% set Abgelaufen = now() > TargetDate %}


      {# --- Hilfe für Darstellung Zieldatum --- #}

      {# Wandelt englische Monate/Tage in dt #}

      {% set wochentag = {
        'Monday': 'Montag', 'Tuesday': 'Dienstag', 'Wednesday': 'Mittwoch',
        'Thursday': 'Donnerstag', 'Friday': 'Freitag', 'Saturday': 'Samstag', 'Sunday': 'Sonntag'
      }[TargetDate.strftime('%A')] %}


      {% set monat = {
        'January': 'Januar', 'February': 'Februar', 'March': 'März', 'April': 'April',
        'May': 'Mai', 'June': 'Juni', 'July': 'Juli', 'August': 'August',
        'September': 'September', 'October': 'Oktober', 'November': 'November', 'December': 'Dezember'
      }[TargetDate.strftime('%B')] %}


      {% set TargetDate_title = wochentag ~ ', ' ~ TargetDate.day ~ '. ' ~ monat
      ~ ' ' ~ TargetDate.year  ~ ' um ' ~ TargetDate.hour ~ ':' ~
      TargetDate.minute  ~ ' Uhr' %}


      {# --- Berechnung der verbleibenden Zeit --- #}

      {% if Abgelaufen == false %}
        {% set time_str = time_until(as_datetime(TargetDate), 5) %}
        
        {# Default Werte Null weil die Funktion time_until keine Leerzeiten anzeigt, z.B. im selben Monat steht nicht "0 months" sondern nichts #}
        {% set years_tmp = namespace(val=0) %}
        {% set months_tmp = namespace(val=0) %}
        {% set days_tmp = namespace(val=0) %}
        {% set hours_tmp = namespace(val=0) %}
        {% set minutes_tmp = namespace(val=0) %}
        {# prüft ob der zeitraum vorkommt und nimmt diesen oder bleibt bei default 0 #}
        {% for part in time_str.split(' ') %}
          {% if 'year' in part %}
            {% set years_tmp.val = time_str.split(' ')[loop.index0 - 1] | int %}
          {% elif 'month' in part %}
            {% set months_tmp.val = time_str.split(' ')[loop.index0 - 1] | int %}
          {% elif 'day' in part %}
            {% set days_tmp.val = time_str.split(' ')[loop.index0 - 1] | int %}
          {% elif 'hour' in part %}
            {% set hours_tmp.val = time_str.split(' ')[loop.index0 - 1] | int %}
          {% elif 'minute' in part %}
            {% set minutes_tmp.val = time_str.split(' ')[loop.index0 - 1] | int %}
          {% endif %}
        {% endfor %}
        
        {% set years = years_tmp.val %}
        {% set months = months_tmp.val %}
        {% set days = days_tmp.val %}
        {% set hours = hours_tmp.val %}
        {% set minutes = minutes_tmp.val %}

        {# --- Positionsberechnung --- #}
        {% if years > 0 %}
          {% set position_slider = 1 %}
        {% elif months > 0 %}
          {% set position_slider = 2 %}
        {% elif days > 0 %}
          {% set position_slider = 3 %}
        {% elif hours > 0 %}
          {% set position_slider = 4 %}
        {% else %}
          {% set position_slider = 5 %}
        {% endif %}
      {% endif %}

      {# --- Darstellung --- #}


      <font size=6>{{ CoundownTitel }}</font>

      <font size=4>{{ TargetDate_title }}</font>

      {% if Abgelaufen == false %}

      <table border=0>
        <tr>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_zahlen }}>{{ years }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_zahlen }}>{{ months }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_zahlen }}>{{ days }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_zahlen }}>{{ hours }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_zahlen }}>{{ minutes }}</font></center></td>
        </tr>
        <tr>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_worte }}>Jahre</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_worte }}>Monate</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_worte }}>Tage</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_worte }}>Stunden</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_worte }}>Minuten</font></center></td>
        </tr>
        <tr>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_slider }}>{{ IconCode_active if position_slider == 1 else '' }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_slider }}>{{ IconCode_active if position_slider == 2 else '' }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_slider }}>{{ IconCode_active if position_slider == 3 else '' }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_slider }}>{{ IconCode_active if position_slider == 4 else '' }}</font></center></td>
          <td width={{ spaltenbreite }}><center><font size={{ fontsize_slider }}>{{ IconCode_active if position_slider == 5 else '' }}</font></center></td>
        </tr>
      </table>

      {% else %}

        Der Countdown ist abgelaufen!

      {% endif %}
    card_mod:
      style: |
        ha-card {
          background: rgba(255, 1, 100, 0.30); /* 0,25=25% Deckung */
          color: white;
          box-shadow: none;
        }
  - type: heading
    icon: mdi:cog-outline
    heading_style: subtitle
    heading: Einstellungen
    tap_action:
      action: perform-action
      perform_action: input_boolean.toggle
      target:
        entity_id: input_boolean.dashboard_raum_menue_countdown
  - type: markdown
    content: >-
      Suche in einem Homeassi Kalender nach einem bestimmten Termin, z.B.
      Zahnarzt und lasse Dir einen Countdown bis zu diesem Termin anzeigen. 
    text_only: true
    visibility:
      - condition: state
        entity: input_boolean.dashboard_raum_menue_countdown
        state: "on"
  - type: entities
    entities:
      - entity: input_text.countdown_kalender_termintext
        name: Suchtext
        secondary_info: none
        card_mod:
          style: |
            :host {
              --state-icon-color: white;
            }
      - entity: input_select.selectbox_meinekalender
        name: Kalender
        card_mod:
          style: |
            :host {
              --state-icon-color: white;
            }
      - entity: input_number.countdown_kalenderjahre
        name: Wieviele Jahre ?
        card_mod:
          style: |
            :host {
              --state-icon-color: white;
              font-size: 11pt !important;
            }
    show_header_toggle: true
    state_color: false
    card_mod:
      style: |
        ha-card {
          background: rgba(50, 50, 50, 0.25);
          color: white;
          --card-primary-color: white;
          box-shadow: none;
        }
    visibility:
      - condition: state
        entity: input_boolean.dashboard_raum_menue_countdown
        state: "on"
  - show_name: true
    show_icon: true
    type: button
    name: Start
    grid_options:
      columns: full
      rows: 2
    hold_action:
      action: none
    tap_action:
      action: perform-action
      perform_action: script.turn_on
      target:
        entity_id: script.finde_naechsten_termin
    card_mod:
      style: |
        ha-card {
          height: 30px  !important;
        }
    visibility:
      - condition: state
        entity: input_boolean.dashboard_raum_menue_countdown
        state: "on"
grid_options:
  columns: 48
  rows: auto

Teste es halt erstmal in den Entwicklerwerkzeuge da siehst du dann entweder den Script Fehler und/oder in den Logs

Und du musst natürlich schon dein Script und nicht turn_on aufrufen und die ganzen variablen setzen sonst ist es klar das es nicht geht.

Darum Entwicklerwerkzeuge Script aufrufen und dann von dort kopiere