How to show DateTime as 'xx minutes ago'

I’m trying to do an entity which shows current active window on PC and “last changed” time. I have two MQTT sensors, one which shows me the current active window on PC, second, which shows how long ago it was active. With ordinary “entities” window it works fine:

type: entities
entities:
  - entity: sensor.jarek_activewindow
  - entity: sensor.jarek_lastactive
show_header_toggle: false

image

But… I’m trying to display it with a bit other form. And here I have a problem with this “lastactive” sensor. With this code:

                - type: custom:button-card
                  color_type: label-card
                  color: lightgrey
                  name: |
                    [[[ return states['sensor.jarek_activewindow'].state; ]]]
                  label: |
                    [[[ return states['sensor.jarek_lastactive'].state; ]]]
                  show_label: true

it is shown as here:

image

Question: how to display this LastActive sensor with more user-friendly form, as “2 second ago”, “just now”, “yesterday”, etc, as it is inside of ordinary Entity window?

1 Like

You have to perform the calculation yourself when using the custom button card.

Ok, so there is no way to just get such value from inside of sensor, by any magic states[sensor.aaa].user_firendly_state command and in Entities window it is a functionality of this card, right?

Do you have any example, how to do such calculation?

Something alike this…

{{ strptime('2022-03-21 11:00:00', "%YYYY-%MM-%DD %HH:%MM:%SS") | as_timestamp - now().timestamp() | round }}

instead of the strptime part, use what ever timestamp you want to pass over

Unfortunately doesn’t work. Or I’m using it wrong. Or… maybe custom-button doesn’t support such features.
Here is my try:

                - type: custom:button-card
                  color_type: label-card
                  color: lightgrey
                  name: |
                    [[[ return states['sensor.jarek_activewindow'].state; ]]]
                  label: |
                    [[[ {{ states['sensor.jarek_lastactive'].state | as_timestamp - now().timestamp() | round }} ]]]
                  show_label: true

here is the result:

ButtonCardJSTemplateError: ReferenceError: as_timestamp is not defined in ‘{{ states[‘sensor.jarek_lastactive’].state | as_timestamp - now().timestamp() | round }}’

try it out in the deverloper tools > templates first… allows you to play around a bit until you have the correct format. Note that my strptime is preparing the text-data before setting timestamp
So,…what is the acual value of states[‘sensor.jarek_lastactive’].state ? Depending on what it is / how it is shaped, you may need to prepare this before it can get transformed in a timestamp

Ok… good hint with this developer tools, thanks (I knew about such tool but of course even didn’t think about using it here).
In this tool I did something like this:

[[[ {{now().timestamp() | round - states[‘sensor.jarek_lastactive’].state | as_timestamp}} ]]]

And here it works, gives the result in seconds from the last activity. It is not as clear output as this working in “Entities” card (3 seconds ago, 2 hours ago, yesterday, 5 days ago, etc.). Unfortunately the same code put into the label of button-card returns error:

ButtonCardJSTemplateError: ReferenceError: now is not defined in ‘{{now().timestamp() | round - states[‘sensor.jarek_lastactive’].state | as_timestamp}}’

So I afraid that here Custom Button card is a problem, I would have to make this value somewhere outside, OMG… My idea was only to have a clearly visible proof that “youtube” window instead of lessons on PC of my son is from now, not from one hour ago :slight_smile:

Pure {{ states[‘sensor.jarek_lastactive’].state }} returns string: 2022-03-21T20:38:15+00:00

Create a template sensor and use the output of that in the custom button field.

Here is an example from my config, you’ll have to play around with it to get exactly what you want.

template:
  - sensor:
      - name: 'Server Uptime'
        unique_id: server_uptime
        icon: mdi:clock-start
        state: >
          {% if states('sensor.last_boot')|lower not in ['unknown','unavailable','none'] %}
            {% set uptime = now().timestamp() - states('sensor.last_boot')|as_timestamp %}
            {% set minutes = (uptime // 60)|int(0) %}
            {% set hours = (minutes // 60) %}
            {% set days = (hours // 24) %}
            {% set weeks = (days // 7) %}
            {% set minutes = (minutes % 60) %}
            {% set hours =  (hours % 24) %}
            {% set days = (days % 7) %}
            {% macro phrase(value, name) %}
              {%- set value = value %}
              {%- set end = 's' if value > 1 else '' %}
              {{- '{} {}{}'.format(value, name, end) if value|int(0) > 0 else '' }}
            {%- endmacro %}
            {% set text = [ phrase(weeks, 'week'), phrase(days, 'day'), phrase(hours, 'hr'), 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 }}
          {% endif %}

The output of this sensor is: 1 week, 1 hr and 45 mins

3 Likes

Use the strptime

People are confusing you becaues they do not understand that custom button card uses different templating.

{{ ... }} signifies a jinja template, only useable in the configuration of home assistant.

[[[ ... ]]] is a JS template. The syntax is completely different than jinja and the solutions here will not work.

jazzyisj’s solution will work if you create a template sensor, like he suggests.

Everything vingerha has said will not work because of the reason I highlighted above.

The simplest template that will work (only on times that are in the past) is to make a template sensor and display that value:

template:
  - sensor:
      - name: 'Server Uptime'
        unique_id: server_uptime
        icon: mdi:clock-start
        state: >
          {% set t = now() %}
          {{ states('sensor.jarek_activewindow') | as_datetime | as_local | relative_time }}

You have to include now() in your template even though you aren’t using it to ensure that the sensor updates once a minute.

If you want this to update once a second, you have to tailor it a bit differently.


template:
  - trigger:
    - platform: time_pattern
      seconds: /1
    sensor:
      - name: 'Server Uptime'
        unique_id: server_uptime
        icon: mdi:clock-start
        state: >
          {{ states('sensor.jarek_activewindow') | as_datetime | as_local | relative_time }}

If you want to do this calculation in the custom button card, you’ll have to build the syntax yourself and add an item to the card to force updates on an interval. You’ll have to adjust it because it’s written in english.

                  label: |
                    [[[
                      entity_id = (entity === undefined) ? 'Invalid Entity' : entity.entity_id;
                      var statestr = (entity === undefined || entity.state === undefined) ? null : entity.state;
                      var date = (statestr && entity.attributes.device_class === 'timestamp') ? new Date(statestr) : null;
                      if (date){
                        let now = new Date();
                        var tdelta = Math.floor((now - date)/1000);
                        // console.log(`${entity_id}: ${tdelta}`);
                        function plural(one, more_than_one, divisor){
                          var ret = Math.floor(tdelta/divisor);
                          return (ret == 1) ? `${ret} ${one} ago` : `${ret} ${more_than_one} ago`;
                        }
                        var value;
                        if (tdelta < 60)
                          value = plural('second', 'seconds', 1);
                        else if (tdelta < 60 * 60)
                          value = plural('minute', 'minutes', 60);
                        else if (tdelta < 60 * 60 * 24)
                          value = plural('hour', 'hours', 60 * 60);
                        else if (tdelta < 7 * 60 * 60 * 24)
                          value = plural('day', 'days', 60 * 60 * 24);
                        else
                          value = plural('week', 'weeks', 7 * 60 * 60 * 24);
                        return value;
                      } else {
                        return "Unknown";
                      }
                    ]]]
3 Likes

Thanks for the clarification… I was indeed not aware

You have a right, I spent lot of time trying to translate jinja templates to JS, without success. With your example finally I did what I needed, but also with totally different style. First I started to prepare displaying of human-readable time, but due to fact that my language has one of most crazy grammars in the world and simple “plural” function is absolutely not enough (I would need few separate), I started to think how to do displaying more simple.
Finally I stated that in fact I don’t need exact time, quite enough is simple information whether active window is really active now, or it was active minute ago, 5min ago, 1 hour ago or 1 day ago. So i did it with colors. This part of code:

                - type: custom:button-card
                  color_type: label-card
                  color: |
                    [[[
                      var statestr = (states['sensor.jarek_lastactive'] === undefined || states['sensor.jarek_lastactive'].state === undefined) ? null : states['sensor.jarek_lastactive'].state;
                      var date = (statestr && states['sensor.jarek_lastactive'].attributes.device_class === 'timestamp') ? new Date(statestr) : null;
                      let now = new Date();
                      var tdelta = Math.floor((now - date)/1000);;
                      var value;
                      if (tdelta < 2)
                        value = 'lightgreen';
                      else if (tdelta < 10)
                        value = 'mediumseagreen';
                      else if (tdelta < 60)
                        value = 'green';
                      else if (tdelta < 300)
                        value = 'MediumAquaMarine';
                      else if (tdelta < 3600)
                        value = 'Turquoise';
                      else if (tdelta < 86400)
                        value = 'Wheat';
                      else 
                        value = 'Gray';
                      return value;
                    ]]]
                  name: |
                    [[[ return states['sensor.jarek_activewindow'].state; ]]]

And whole entity, at now only two PC’s are monitoring: mine for tests, and my son’s (not switched on yet today):

image

To do: maybe any possibility to send simple text message, which will appear direct onto the screen. I can imagine this feeling, when during final war in my son’s lovest game, screen will be covered by popup “stop playing, go doing homework NOW!!!”… :grin: :joy:

2 Likes