The EPIC Time Conversion and Manipulation Thread!

FYI, you guys can utilize today_at to make the template really easy to read without any fancy math.

{% set last_changed = states.binary_sensor.main_door.last_changed %}
{% if last_changed < today_at() - timedelta(days=1) %}
  {{ last_changed.strftime('%A at %H:%M') }}
{% elif last_changed < today_at() %}
  {{ last_changed.strftime('Yesterday at %H:%M') }}
{% else %}
  {{ last_changed.strftime('Today at %H:%M') }}
{% endif %}
2 Likes

Wow… and I just finished modifying @finity 's to be:

{% set time = states('input_datetime.test_datetime_3') %}
{% set hrs = ((as_timestamp(now()) - as_timestamp(time)) / 3600 ) | int %}
{% set mins = ((as_timestamp(now()) - as_timestamp(time))  ) | timestamp_custom('%M') |int %}
{% if as_timestamp(time) | timestamp_custom('%Y-%m-%d') == 
     as_timestamp(now()) | timestamp_custom('%Y-%m-%d') %}
  Today at {{ as_timestamp(time) | timestamp_custom('%H:%M') }} 

    {% if hrs == 0 %}
        ({{ mins | int}} minute(s) ago)
    {% elif hrs == 1 %}
        ({{ hrs | int}} hour ago)
    {% else %}
        ({{ hrs | int}} hours ago)
    {% endif %}


{% elif as_timestamp(time) | timestamp_custom('%Y-%m-%d') == 
       (as_timestamp(now().replace(hour=0, minute=0, second=0)) - 3600) 
         | timestamp_custom('%Y-%m-%d') %}
  Yesterday at {{ as_timestamp(time) | timestamp_custom('%H:%M') }}

{% elif as_timestamp(time) < as_timestamp(now().replace(hour=0, minute=0, second=0)) - (3600 * 1)
    and as_timestamp(now()) - 84600 * 7 < as_timestamp(time) %}
  {{ as_timestamp(time) | timestamp_custom('%A at %H:%M') }}
{% else %}
  More than a week ago
{% endif %}

adding the following lines:

{% set hrs = ((as_timestamp(now()) - as_timestamp(time)) / 3600 ) | int %}
{% set mins = ((as_timestamp(now()) - as_timestamp(time))  ) | timestamp_custom('%M') |int %}

    {% if hrs == 0 %}
        ({{ mins | int}} minute(s) ago)
    {% elif hrs == 1 %}
        ({{ hrs | int}} hour ago)
    {% else %}
        ({{ hrs | int}} hours ago)
    {% endif %}

I will try your code @petro right away :slight_smile:

Thanks Petro, this is great. Which Jinja reference would you recommend for learning and/or referencing such functions i.e. today_at()

The two links in the developer tools are always a good starting point.

https://jinja.palletsprojects.com/en/3.1.x/templates/

I checked the ones at the HA Templates page… will re-check again though.
Much appreciate your advice. Thanks
Rami

I think this is what you’re looking for:

{% set last_changed = states.binary_sensor.main_door.last_changed %}
{% if last_changed < today_at() - timedelta(days=6) %}
  More than a week ago
{% elif last_changed < today_at() - timedelta(days=1) %}
  {{ last_changed.strftime('%A at %H:%M') }}
{% elif last_changed < today_at() %}
  {{ last_changed.strftime('Yesterday at %H:%M') }}
{% else %}
  {{ last_changed.strftime('Today at %H:%M') }} ({{ relative_time(last_changed) }} ago)
{% endif %}

Hi Petro,
May I trouble you with one last thing… I’ve trying to get it to work with no luck after several trial, I guess I need to do some intensive reading about States and Attributes.

What if instead of last changed attribute (which changes after each restart, I want to use an input_datetime helper to make it persistant over re-start… how can I insert this in your code.
It keeps giving me error either no attribute or can’t use < between strings and datetime.

Sorry for the inconvenience

States are always strings, so you will need to convert the state of the Input datetime helper to a offset-aware datetime object by replacing the first line of the template:

{% set last_changed = states('input_datetime.XXXX') | as_datetime | as_local %}
2 Likes

Thank you big time. I can now see what I did wrong… I had the as_local filter before the | as_datetime… makes perfect sense now and works flawlessly.
Much appreciated
RS

I’m trying to format elapsed time into something like ##d ##h ##m or ##:##:##

Here’s what I’ve got so far:

{{ (as_timestamp(now()) - as_timestamp(states('sensor.uptime'))) | timestamp_custom('%dd %hh %mm') }}

Which gives me 31d Dech 12m off an epoch of ~9000 which should be more like 0d 2h 30m. What’s the proper syntax to depict elapsed time from 0 instead of the start of the epoch?

This will be formatted ddd hh:mm:ss

{{ now() - states('sensor.uptime') | as_datetime | as_local }}

If you want a custom format, you have to do the math.

Here’s the math laid out…

With all that Python/Jinja2 is capable of…is that really the most compact way to convert/format seconds into another timeframe?

Yes, there is no way to format a timedelta that’s naturally built into python or jinja.

Just so you know, most languages don’t have this. I was surprised when I found out that C# does have it, but the implementation is terrible in comparison to the strftime equivalent.

Wow! That’s seems like something that should’ve been handled already. OK, thank you.

I use this on my “Router” uptime sensor , sure i didn’t bother about showing D/H/M, as i have a “personal perspective” of the word “UserFriendly” :slight_smile: … it’s my system, only i need to understand :grin:

        - type: custom:template-entity-row
          name: Uptime
          active: true
          state: >
            {% set s = states('sensor.rt_ax68u_d680_uptime')| int(3) %}{{
            '%02d:%02d:%02d' % ((s/86400)|int(3),((s%86400) /
            3600)|int(3),(s%3600) / 60) }}

Thanks. It doesn’t work for me. I get 00:00:00 using the whole thing. What type and format does your sensor output and what does the output of that template look like?

{% set s = states('sensor.uptime')| int(3) %}
{{'%02d:%02d:%02d' % ((s/86400)|int(3),((s%86400) / 3600)|int(3),(s%3600) / 60) }}
{{ s }}
{{ states('sensor.uptime') }}

gives me

image

if htat’s what you want, just use what I linked, no point in re inventing the wheel

          {%- set up_time = as_timestamp(now())-as_timestamp(states('sensor.uptime')) %}

          {%- macro phrase(name, divisor, mod=None) %}
            {%- set value = ((up_time // divisor) % (mod if mod else divisor)) | int %}
            {{- '{} {}'.format(value, name) if value | int > 0 else '' }}
          {%- endmacro %}
          
          {%- set values = [ 
                     phrase('w', 60*60*24*7), 
                     phrase('d', 60*60*24, 7),
                     phrase('h', 60*60, 24),
                     phrase('m', 60), 
                 ] | select('!=','') | list %}
                        
          {{ values | join(', ') }}

Thanks, and I probably will. I just need another day to process why this takes 12 LOC. :wink:

I made the template easily expandable for people who don’t understand code. They can copy the template and remove lines that they don’t want. This could be much shorter and custom, but why bother when this works for all needs/wants

2 Likes