Hunanizing History Stats

History stats is great at collecting statistics, but the output isn’t readable or really usable as-is. A string value of 2.15 doesn’t correlate to 2 hours 15 minutes. It’s actually 2 hours 9 minutes. So I had a goal to fix this. I borrowed some code snippets from a few HA Uptime posts from @petro and various others to use as the base, and modified it to do the math needed to convert the History Stats values. Now, the output for a decimal value says “x hours x minutes”, which is much more readable for humans. I figured I’d share it in case others are interested. Enjoy!

template:
  - sensor:
      - name: "Daily Furnace Runtime"
        state: >-
          {% set min = states('sensor.daily_furnace_uptime').split('.')[1] | int %}
          {% set hr = states('sensor.daily_furnace_uptime').split('.')[0] | int %}
          {% set min1 = min / 100 %}
          {% set min2 = min1 * 60 %}

          {% set minutes = min2 | int % 60 %}
          {% set hours =  hr % 24 %}

          {% macro phrase(value,name) %}
            {%- set value = value %}
            {%- set end = 's' if value > 1 else '' %}
            {{- '{} {}{}'.format(value,name,end) if value|int > 0 else '{} {}{}'.format(value,name,'s') }}
          {%- endmacro %}

          {% set text = [phrase(hours,'hour'),
                        phrase(minutes,'min')]
                        |select('!=','')|list|join(', ') %}
          {% set last_comma = text.rfind(',') %}
          {% if last_comma != -1 %}
            {% set text = text[:last_comma] + ' and' + text[last_comma + 1:] %}
          {% endif %}
          {{text}}
1 Like
          {%- set word_for_and = 'and' %}
          {%- set up_time = (states('sensor.daily_furnace_uptime') | float(0) * 60 * 60) | int %}

          {%- macro phrase(name, plural_name, divisor, mod=None) %}
            {%- set value = ((up_time // divisor) % (mod if mod else divisor)) | int %}
            {%- set name = plural_name if value > 1 else name %}
            {{- '{} {}'.format(value, name) if value | int > 0 else '' }}
          {%- endmacro %}
          
          {%- set values = [ 
                     phrase('hour', 'hours', 60*60),
                     phrase('minute', 'minutes', 60), 
                 ] | select('!=','') | list %}
                        
          {{ values[:-1] | join(', ') ~ ' ' ~ word_for_and ~ ' ' ~ values[-1] if values | length > 1 else values | first | default }}

Just keep in mind that hours will be truncated at 24 in both of these versions. So you wont get anything over 24.

Also, you’ll probably need an availability template because both may error on restart.

EDIT: Nevermind, just fixed it so there was no top end to the number of hours.

EDIt2: and your availability would be…

 {{ states('sensor.daily_furnace_uptime') | as_timedelta is not none }}

All together now

template:
  - sensor:
      - name: "Daily Furnace Runtime"
        state: >
          {%- set word_for_and = 'and' %}
          {%- set up_time = (states('sensor.daily_furnace_uptime') | float(0) * 60 * 60) | int %}

          {%- macro phrase(name, plural_name, divisor, mod=None) %}
            {%- set value = ((up_time // divisor) % (mod if mod else divisor)) | int %}
            {%- set name = plural_name if value > 1 else name %}
            {{- '{} {}'.format(value, name) if value | int > 0 else '' }}
          {%- endmacro %}
          
          {%- set values = [ 
                     phrase('hour', 'hours', 60*60),
                     phrase('minute', 'minutes', 60), 
                 ] | select('!=','') | list %}
                        
          {{ values[:-1] | join(', ') ~ ' ' ~ word_for_and ~ ' ' ~ values[-1] if values | length > 1 else values | first | default }}
        availability: "{{ states('sensor.daily_furnace_uptime') | as_timedelta is not none }}"
1 Like