Templating -- sort a list of entities by an attribute

‘Hello’ to everybody out there,
i’m struggling.
Szenario:
I did install custom anniversairy-card via UI. Everything works, means: i can create new entries and so on.
For displaying the values (and other stuff), i use the great custom-html-template-card.
Now i’m struggling, but first pure template code (without html, css and others):
First attemp:

{% set alle_sensoren = states.sensor | list %}
{% set geburtstag_sensoren = alle_sensoren | selectattr('entity_id', 'search',
  'geburtstag') | sort(attribute='state', reverse=false) %}

Target: to sort the entries by ‘day to birthday’ (=> state).
Error: state is a string, so the order is that 9 is more than 334.

hmm, ok for me the solution i thought: change string to int
Result: ha, ha, ha — didn’t find a solution for that.

2nd attemp: use dictionairy and namespace

{%- set alle_sensoren = states.sensor | list -%}
{%- set geburtstag_sensoren = alle_sensoren | selectattr('entity_id', 'search',
  'geburtstag') | sort(attribute='state', reverse=false) -%}
{%- set gebliste=namespace(gebkind={}) -%}
{%- for sensor in geburtstag_sensoren -%}
{%- set jubilar = { sensor.attributes.friendly_name : {'gebdatum': sensor.attributes.date, 'gebmonat': sensor.attributes.date, 'gebtag':sensor.attributes.date.day, 'gebalter':sensor.attributes.current_years +1, 'gebdeadline':sensor.state|int}} -%}
{# DEBUGGING #}
{{- jubilar -}}
{# --------- #}
{# {% set gebliste.gebkind = gebliste.gebkind + jubilar %} #}
{ #DEBUGGING  #}
{# {{ gebliste.gebkind }} #}
{# --------- #}
{%- endfor -%}

Result:
Unbenannt

ok, boy, go forward use namespace:

{%- set alle_sensoren = states.sensor | list -%}
{%- set geburtstag_sensoren = alle_sensoren | selectattr('entity_id', 'search',
  'geburtstag') | sort(attribute='state', reverse=false) -%}
{%- set gebliste=namespace(gebkind=[]) -%}
{%- for sensor in geburtstag_sensoren -%}
{%- set jubilar = { sensor.attributes.friendly_name : {'gebdatum': sensor.attributes.date, 'gebmonat': sensor.attributes.date, 'gebtag':sensor.attributes.date.day, 'gebalter':sensor.attributes.current_years +1, 'gebdeadline':sensor.state|int}} -%}
{# DEBUGGING #}
{{- jubilar -}}
{# --------- #}
{% set gebliste.gebkind = gebliste.gebkind + jubilar %}
{# DEBUGGING #}
{{ gebliste.gebkind }} #}
{# --------- #}
{%- endfor -%}

ha, ha, ha …
Unbenannt2

doesn’t matter: use a dictionairy instead:
replace that:

{%- set gebliste=namespace(gebkind=[]) -%}

by that:

{%- set gebliste=namespace(gebkind={}) -%}

ha, ha, ha…
Unbenannt3

ooooookayyyyy. then i ‘dirty’ one :
replace:

{%- set jubilar = { ... }

with:

{%- set jubilar = [{ ... }]

holla, die Waldfee !
Unbenannt4

Now sorting…:

{%- set alle_sensoren = states.sensor | list -%}
{%- set geburtstag_sensoren = alle_sensoren | selectattr('entity_id', 'search',
  'geburtstag') | sort(attribute='state', reverse=false) -%}
{%- set gebliste=namespace(gebkind=[]) -%}
{%- for sensor in geburtstag_sensoren -%}
{%- set jubilar = [{ sensor.attributes.friendly_name : {
'gebdatum': sensor.attributes.date, 
'gebmonat': sensor.attributes.date, 
'gebtag':sensor.attributes.date.day, 
'gebalter':sensor.attributes.current_years +1, 
'gebdeadline':sensor.state|int
 }}] -%}
{# DEBUGGING #}
{# {{- jubilar -}} #}
{# --------- #}
{% set gebliste.gebkind = gebliste.gebkind + jubilar | sort(attribute='gebdeadline', reverse=true) %}

{# DEBUGGING #}
{{ gebliste.gebkind}}
{# --------- #}
{%- endfor -%}

ha, ha, ha, 9 or 99 are both more than 334 (still a string, or again ?)

that’s the point i’m standing at, right now…

INFO:
testing in HA by template editor under developer tools
Please, somebody here who can help me ?
Many thanks in advance !

So long
PC

TLDR? Too much rambling in your wall of text to tell what the actual problem is…

A state is always a string, regardless of what it looks like. To get a number out of a state you must filter to a float or an int. (An attribute though can be a different data type.)

ha, ha, ha
first reading, than answering…

{%- set jubilar = [{ sensor.attributes.friendly_name : {
'gebdatum': sensor.attributes.date, 
'gebmonat': sensor.attributes.date, 
'gebtag':sensor.attributes.date.day, 
'gebalter':sensor.attributes.current_years +1, 
'gebdeadline':sensor.state|int
 }}] -%}
{% set ns = namespace(items=[]) %}
{% for s in states.sensor | selectattr('entity_id', 'search', 'geburtstag') | selectattr('entity_id', 'has_value') %}
  {% set ns.items = ns.items + [ dict('name': s.name, 'date': (s.state | as_datetime | as_local).date()) ] %}
{% endfor %}
{{ ns.items | sort(attribute='date', reverse=True) }}

That’s if the days are always in the past, if they are always in the future…

{% set ns = namespace(items=[]) %}
{% for s in states.sensor | selectattr('entity_id', 'search', 'geburtstag') | selectattr('entity_id', 'has_value') %}
  {% set ns.items = ns.items + [ dict('name': s.name, 'date': (s.state | as_datetime | as_local).date()) ] %}
{% endfor %}
{{ ns.items | sort(attribute='date') }}

If you want to count the number of days to the birthday, you can use easy_time.

{% from 'easy_time.jinja' import count_the_days %}
{% set ns = namespace(items=[]) %}
{% for s in states.sensor | selectattr('entity_id', 'search', 'geburtstag') | selectattr('entity_id', 'has_value') %}
  {% set ns.items = ns.items + [ dict('name': s.name, 'days_away': count_the_days(s.entity_id) | int) ] %}
{% endfor %}
{{ ns.items | sort(attribute='days_away') }}

As best I can tell, there are at the very least two errors in your sorting:

  1. You are sorting the wrong thing. You are only sorting jubilar, which is a list containing a single attribute, not the sum of the lists. The filter operator has precedence over the addition.

  2. The attribute you are trying to sort on does not exist? The attribute gebdeadline is on a “higher” level so to speak. On the level your sort operates each item only has a single attribute, whatever sensor.attributes.friendly_name evaluated to. At least that’s what it looks like to me…

Do not make each item a dict. Make each item an entry, that is a list or tuple containing a key+value pair. Once the loop is finished, then convert the list of entries into a dict, and at the very end sort.

Last thing to say, if these sensors have attributes already for sorting etc, then you can utilize that instead of using namespace.

I’m still not sure what your end goal is, but if you just want the next birthday, it could be really simple code if the attributes exist.

If you want to transform the data, that will require namespace. But you won’t need to sort it if the attributes already exist and you sort on the iterator.

You’re welcome, i will test this…

Thank YOUUUUU !

i did this to get an overview:

{%- set alle_sensoren = states.sensor | list -%}
{%- set geburtstag_sensoren = alle_sensoren | selectattr('entity_id', 'search',
  'geburtstag') | sort(attribute='state', reverse=false) -%}
{{- geburtstag_sensoren -}}

Unbenannt5

than i wanted to filter directly, something like this:

{%- set alle_sensoren = states.sensor | list -%}
{%- set geburtstag_sensoren = alle_sensoren | selectattr('entity_id', 'search',
  'geburtstag') | sort(attribute='state'|int, reverse=false) -%}
{{- geburtstag_sensoren -}}

the MAIN GOAL is to display a list of the next upcoming 5 birthdays (so the card is always filled) - i know, there are examples with the auto-entities-card and ohers, but i want to use the html-template-card, because i want to use my own style (icons, highlighting, and so on)

and i would love it to go the simpliest way (don’t need namespace), but i only know this way to create a list of dictionairies, that i than can iterate. BUT the list should be sorted by day to birthday, so i can use loop <=5 for displaying the jubilars…
My list at the moment looks like :
Unbenannt6

Short: i need a sorted list of all geburtstag_ entities … that’s all. Beginners like me, don’t know the straight way, so… how you can see,

Do you have the card information you want to fill out?

uff, thank you. I will try this, too.
but i don’t understand, cause jubilar is a list which contains dictionairies(friendly_name) which value is another dictionairy that contains about five key:value entries… and there finally i did change the type from string to int.
Every loop a new dictionairy (containing a dictionairy) of the following entry will added to the list jubilar.
And now i want to sort all the main-dictionairies (friendly_name) by day to next birthday…

You mean the code of the card?
Yes, here it is (style, etc, not finished yet, without namespace, is the first attemp, the others i tested naked in template editor) :

type: custom:html-template-card
title: ""
ignore_line_breaks: true
content: >
  <style>

  ha-card {}

  #geb_table {
      /* Debugging 
      border: 1px solid green;
      */
      width: 188px;
      min-width: 188px;
      max-width: 188px;      
      /*  */
      padding: 0px 0px 0px 0px;
      margin: -7px 0px 0px -15px;
      font-family: 'Bahnschrift';
      text-transform: uppercase;
      font-size: 11px;
      letter-spacing: 1px;
      color: #aaaaaa;
  }

  td {      
      height: 14px;
      min-height: 14px;
      max-height: 14px;
      line-heigt: 14px;
      text-aling: left;
      vertical-align: botton;
  }

  .heute {color:gold;}

  .geb_titel {
      font-family: verdana;
      text-transform: uppercase;
      font-size: 22px;
      letter-spacing: 2px;
      height: 30px;
      vertical-align: top;
      padding: 0px 0px 0px 7px;
      color: #bbbbbb;      
  }  

  .line {
      height: 10px;
      border-top: 1px solid steelblue;
  }

  .geb_datum {
      /* Debugging
      border: 1px solid red;
      */
      width: 50px;
      min-width: 50px;
      max-width: 50px;
      padding:  0px 0px 0px 4px;      
  }

  .geb_name {
      /* Debugging
      border: 1px solid pink;
      */      
  }

  .geb_alter {
      /* Debugging 
      border: 1px solid gold;
      */
      width: 20px;      
      min-width: 20px;
      max-width: 20px;
      text-align: right;
      font-size: 14px;
      padding-right: 4px;
      color: #cccccc;
  }

  </style>


  {# globale Parameter #}

  {% set debug_mode = false %} {# Debug-Modus aktivieren: debug_mode = true #}

  {% set debug_date = '2025-01-06' %} {# Datum als Referenzwert (simuliert
  aktuelles Datum) #}

  {% if debug_mode %}

  {% set aktuell_datum = strptime(debug_date, '%Y-%m-%d').date() %}

  {% else %}

  {% set aktuell_datum = now().date() %}

  {% endif %}

  {% set aktuell_jahr = aktuell_datum.year %}

  {% set aktuell_monat = aktuell_datum.month %}

  {% set aktuell_tag = aktuell_datum.day %}

  {% set alle_sensoren = states.sensor | list %}

  {% set geburtstag_sensoren = alle_sensoren | selectattr('entity_id', 'search',
  'geburtstag_') | sort(attribute='state') | list %}

  {% set geburtstag_sensoren = geburtstag_sensoren | sort(attribute='state') |
  list %}

  <table id='geb_table'>
    <tr>
      <td colspan='3' class='geb_titel'>GEBURTSTAG</td>
    </tr>
    <tr>
      <td colspan='3' class='line'></td>
    </tr>
    {% for sensor in geburtstag_sensoren %}
    {% set geburtstag_datum = sensor.attributes.date -%}
    {% set geburtstag_monat = geburtstag_datum.month -%}
    {% set geburtstag_tag = geburtstag_datum.day -%}
    {% set geburtstag_name = sensor.attributes.friendly_name -%}
    {% set geburtstag_alter = sensor.attributes.current_years + 1 -%}
    {% set geburtstag_tage_noch = sensor.state -%}
    {% if loop.index <= 5 -%}
    <tr>
      <td class='geb_datum'>
        {% if geburtstag_monat == aktuell_monat and geburtstag_tag == aktuell_tag %}
        <span class='heute'>
        {% endif -%}
          {{ geburtstag_datum.strftime('%d.%b') }}
        </span>
      </td>
      <td class='geb_name'>
        {% if geburtstag_monat == aktuell_monat and geburtstag_tag == aktuell_tag %}
        <span class='heute'>
        {% endif -%}
          {{ geburtstag_name }}
        </span>
      </td>
      <td class='geb_alter'>
        {% if geburtstag_monat == aktuell_monat and geburtstag_tag == aktuell_tag %}
        <span class='heute'>
        {% endif -%}
          {{ geburtstag_alter }}
        </span>
      </td>
      <!--
      <td class='geb_tage_noch'>
        {% if geburtstag_monat == aktuell_monat and geburtstag_tag == aktuell_tag %}
        <span class='heute'>
        {% endif -%}
          ({{ geburtstag_tage_noch}})
        </span>
      </td>
      -->
    </tr>
    {%- endif -%}
    {% endfor -%}
    </table>
view_layout:
  position: sidebar

type: custom:html-template-card
title: ""
ignore_line_breaks: true
content: >
  <style>

  ha-card {}

  #geb_table {
      /* Debugging 
      border: 1px solid green;
      */
      width: 188px;
      min-width: 188px;
      max-width: 188px;      
      /*  */
      padding: 0px 0px 0px 0px;
      margin: -7px 0px 0px -15px;
      font-family: 'Bahnschrift';
      text-transform: uppercase;
      font-size: 11px;
      letter-spacing: 1px;
      color: #aaaaaa;
  }

  td {      
      height: 14px;
      min-height: 14px;
      max-height: 14px;
      line-heigt: 14px;
      text-aling: left;
      vertical-align: botton;
  }

  .heute {color:gold;}

  .geb_titel {
      font-family: verdana;
      text-transform: uppercase;
      font-size: 22px;
      letter-spacing: 2px;
      height: 30px;
      vertical-align: top;
      padding: 0px 0px 0px 7px;
      color: #bbbbbb;      
  }  

  .line {
      height: 10px;
      border-top: 1px solid steelblue;
  }

  .geb_datum {
      /* Debugging
      border: 1px solid red;
      */
      width: 50px;
      min-width: 50px;
      max-width: 50px;
      padding:  0px 0px 0px 4px;      
  }

  .geb_name {
      /* Debugging
      border: 1px solid pink;
      */      
  }

  .geb_alter {
      /* Debugging 
      border: 1px solid gold;
      */
      width: 20px;      
      min-width: 20px;
      max-width: 20px;
      text-align: right;
      font-size: 14px;
      padding-right: 4px;
      color: #cccccc;
  }

  </style>


  {# globale Parameter #}

  {% set debug_mode = false %} {# Debug-Modus aktivieren: debug_mode = true #}

  {% set debug_date = '2025-01-06' %} {# Datum als Referenzwert (simuliert
  aktuelles Datum) #}

  {% if debug_mode %}

  {% set aktuell_datum = strptime(debug_date, '%Y-%m-%d').date() %}

  {% else %}

  {% set aktuell_datum = now().date() %}

  {% endif %}

  {% set aktuell_jahr = aktuell_datum.year %}

  {% set aktuell_monat = aktuell_datum.month %}

  {% set aktuell_tag = aktuell_datum.day %}

  {% set alle_sensoren = states.sensor | list %}

  {% set geburtstag_sensoren = alle_sensoren | selectattr('entity_id', 'search',
  'geburtstag_') | sort(attribute='state') | list %}

  {% set geburtstag_sensoren = geburtstag_sensoren | sort(attribute='state') |
  list %}

  <table id='geb_table'>
    <tr>
      <td colspan='3' class='geb_titel'>GEBURTSTAG</td>
    </tr>
    <tr>
      <td colspan='3' class='line'></td>
    </tr>
    {% for sensor in states.sensor | selectattr('entity_id', 'search', 'geburtstag') | selectattr('attributes.next_date', 'defined') | rejectattr('attributes.next_date', 'none') | sort(attribute='attributes.next_date') %}
    {% if loop.index <= 5 -%}
    {% set geburtstag_datum = sensor.attributes.date -%}
    {% set geburtstag_monat = geburtstag_datum.month -%}
    {% set geburtstag_tag = geburtstag_datum.day -%}
    {% set geburtstag_name = sensor.attributes.friendly_name -%}
    {% set geburtstag_alter = sensor.attributes.current_years + 1 -%}
    {% set geburtstag_tage_noch = sensor.state -%}
    <tr>
      <td class='geb_datum'>
        {% if geburtstag_monat == aktuell_monat and geburtstag_tag == aktuell_tag %}
        <span class='heute'>
        {% endif -%}
          {{ geburtstag_datum.strftime('%d.%b') }}
        </span>
      </td>
      <td class='geb_name'>
        {% if geburtstag_monat == aktuell_monat and geburtstag_tag == aktuell_tag %}
        <span class='heute'>
        {% endif -%}
          {{ geburtstag_name }}
        </span>
      </td>
      <td class='geb_alter'>
        {% if geburtstag_monat == aktuell_monat and geburtstag_tag == aktuell_tag %}
        <span class='heute'>
        {% endif -%}
          {{ geburtstag_alter }}
        </span>
      </td>
      <!--
      <td class='geb_tage_noch'>
        {% if geburtstag_monat == aktuell_monat and geburtstag_tag == aktuell_tag %}
        <span class='heute'>
        {% endif -%}
          ({{ geburtstag_tage_noch}})
        </span>
      </td>
      -->
    </tr>
    {%- endif -%}
    {% endfor -%}
    </table>
view_layout:
  position: sidebar
1 Like

only an empty card is now displaying.

fixed, try again

1 Like

WOW wow wow, you are definitivly my hero of this day.
It works perfectly, couldn’t discover the code yet because of tears in my eyes…

own debug mode (set date to 2025-02-15) :
Unbenannt7

Really great, thank YOU !

and how i should now adjust the title ?

last question:
what happened here ?

| selectattr('attributes.next_date', 'defined') | rejectattr('attributes.next_date', 'none') | sort(attribute='attributes.next_date')

update the value, if it is not none, and then it’s automatically int ?
i’m an idiot, using a number instead of… Ok you check if date is set and then sort by it, so there is no type conflict.
And i did delete the three unnecessary lines