Easy Time Macros for Templates!

Brilliant. Thanks - i just managed to get that working a few seconds before and came here to update my query! Thank you again for this great set of macros.

Im loving these macros so far.
Just whilst fiddling at work ive managed to reduce my overall lovelace frontend from 22800 lines down to 21600 lines and ive only been through the first 7000 lines or so! I know there is much more to come off too!

One thing that I would like to see but I am not sure is currently possible is to output the time when your passed just the seconds from an entity.

For example, at present I have the uptime of my nvr as a sensor reading such as 340015 (seconds).

However, there is no way to output this as “3 days, x hours”. It would be amazing if this could be considered in a future release.

Thanks

You can do that now without much effort, but yes I can add that

{% from 'easy_time.jinja' import custom_time, easy_time, big_time %}
{{ easy_time(now() - timedelta(seconds=340015)) }}
{{ big_time(now() - timedelta(seconds=340015)) }}
{{ custom_time(now() - timedelta(seconds=340015), 'hour,minute') }}
{{ custom_time(now() - timedelta(seconds=340015), 'day,hour') }}

as a side note, if the sensor is a duration device_class sensor, it works with just

{% from 'easy_time.jinja' import custom_time, easy_time, big_time %}
{{ easy_time('sensor.xyz') }}
{{ big_time('sensor.xyz') }}
{{ custom_time('sensor.xyz', 'hour,minute') }}
{{ custom_time('sensor.xyz', 'day,hour') }}

Many thanks for this.

The below code works for me - is this the tidiest way I can achieve it?

      {% from 'easy_time.jinja' import easy_time %}
      {{ easy_time(now() - timedelta(seconds=int(states('sensor.nvr_uptime')))) }}

Unfortunately, the sensor isnt a duration device_class sensor so I cant make the code as tidy as your second set of suggestions to me.

Thanks again.

now this would be something to actually use multiple times in a config:

a jinja replacement for a date countdown, we’ve all worked on: Python_script for countdown to birthdays/anniversaries/significant dates - #58 by petro

my personal v version is like this now:

today = datetime.datetime.now().date()

name = data.get('name')
type = data.get('type')
event = data.get('event')
sensorName = 'sensor.{}_{}'.format(type ,name.replace(' ','_'))

dateStr = data.get('date')
dateSplit = dateStr.split('/')

dateDay = int(dateSplit[0])
dateMonth = int(dateSplit[1])
dateYear = int(dateSplit[2])
date = datetime.date(dateYear,dateMonth,dateDay)

thisYear = today.year
nextOccur = datetime.date(thisYear,dateMonth,dateDay)

numberOfDays = 0
years = int(thisYear) - dateYear


if today < nextOccur:
  numberOfDays = (nextOccur - today).days

elif today > nextOccur:
  nextOccur = datetime.date(thisYear+1,dateMonth,dateDay)
  numberOfDays = int((nextOccur - today).days)
  years = years+1

phrase = 'dag' if numberOfDays == 1 else 'dagen'

hass.states.set(sensorName,numberOfDays,
  {
    'entity_picture': '/local/family/{}.jpg'.format(name.lower()),
    'unit_of_measurement': phrase,
    'friendly_name': '{} wordt {} over:'.format(name,years),
    'persoon': name,
    'type': '{}'.format(type.title()),
    'years': years,
    'datum': '{}-{}-{}'.format(dateDay,dateMonth,dateYear)
  }
)

binarysensorName = 'binary_sensor.' + sensorName.split('.')[-1]
binarysensorState = 'on' if numberOfDays == 0 else 'off'
hass.states.set(binarysensorName,binarysensorState,
  {
    'persoon': name,
    'type': '{}'.format(type.title()),
    'years': years,
    'datum': '{}-{}-{}'.format(dateDay,dateMonth,dateYear),
    'event': event,
    'name': name
  }
)

and I would hope to be able to move that all to some jinja wizardry

could it be done in your macro’s?

probably need some adaptation of GitHub - Petro31/easy-time-jinja: Easy Time calculations for Home Assistant templates because I can not do:

{% from 'easy_time.jinja' import count_the_days %}


{{ count_the_days("2023-07-27 00:00:00", utc=True) }}

because of

TypeError: unsupported operand type(s) for -: 'NoneType' and 'datetime.datetime'

this however:

{% from 'easy_time.jinja' import count_the_days %}


{{ count_the_days("2023-07-27 00:00:00") }}

or even

{% from 'easy_time.jinja' import count_the_days %}


{{ count_the_days("2023-07-27") }}

return a day count alright,

ofc that is not the same, as the python is built upon an original date of an event (birthday…) and calculate from that, showing current age, and next date day count…

update

this seems to bring me the correct year count:

{{(states('sensor.date') | as_timestamp - as_timestamp('1964-08-24'))/( 86400*365)}}

so that could be done with a setter:

 {% set date = '1964-08-24' %}
 {{(states('sensor.date') | as_timestamp - as_timestamp(date))/( 86400*365)}}

but then we’d need some easy way to use that same date and calculate the

{% from 'easy_time.jinja' import count_the_days %}
{% set current_date = date.replace('1964', (now().year)|string) %}
{{count_the_days(current_date)}}

had hoped to use a true datetime for those, but we cant… my dates precede the 1970 epoch.

this can be done:

{% set event = '1964-08-24' %}
 {{((states('sensor.date') | as_timestamp - 
     as_timestamp(event))/( 86400*365))|int}}
 

{% from 'easy_time.jinja' import count_the_days %}
{% set current_date = event.replace((event|as_datetime).year|string, (now().year)|string) %}
{{count_the_days(current_date)}}

Petro,

This must be a brute force method, but ive got most of it working:

{% from 'easy_time.jinja' import count_the_days %}
{% set event = '1902-08-28' %}
{% set month = (event|as_datetime).month %}
{% set day = (event|as_datetime).day %}

{% if month < now().month or
      month == now().month and day < now().day %} {% set yearnext = 1 %} {% set yearlast = 0 %}
{% else %} {% set yearnext = 0 %} {% set yearlast = -1 %}
{% endif %}

{% set next_date = event.replace((event|as_datetime).year|string, (now().year + yearnext)|string) %}
{% set last_date = event.replace((event|as_datetime).year|string, (now().year + yearlast)|string) %}

days to next event = {{count_the_days(next_date)}}
days since last event = {{count_the_days(last_date)|int|abs}}


days = {{count_the_days(event)|int|abs}}
years = {{(count_the_days(event)|int|abs/365)|int}}
age =   {{((states('sensor.date') | as_timestamp - as_timestamp(event))/( 86400*365))|int}}

if you feel this would be a nice addition to the easy time macros, ill leave it in this thread.
if not, I would love some feedback in the Discord or maybe a separate post, please let me know ?

Not sure if you are still looking for feature suggestions, but here is one:

In addition to big_*, it would be nice to have a “small_time” function for including relative times in relatively small lovelace cards. small_time would abbreviate the string to something like “1 hr 30 min”, possibly with max_period never exceeding hour? (or I guess you could do “1 day 11 hr 30 min 20 sec” for a complete verison). Potentially could use a small_custom version too to be more specific on which parts to use as abbreviations. I would use this in the secondary field of a mushroom card.

Thanks for considering!

That’s custom_*

My bad, maybe it is a misunderstanding on my part. How do you get custom_time to abbreviate time labels, so it says: “2h 10m” instead of “2 hours, 10 minutes”?

Thanks!

Ah yes, sorry. I don’t have ‘abbreviations’ built in yet. I haven’t decided how to handle that yet. It’ll come in time!

1 Like

Excellent helpers. Thx a lot. :+1:

How can I use languages? I have set de as default, but every try to use the language parameter from the examples is not working.

{% from 'easy_time.jinja' import easy_time %}

{# Overriding language or utc entity attribute #}
{{ easy_time("2023-04-07 00:00:00", 'de', True) }}
{{ easy_time("2023-04-07 00:00:00", language='nl', utc=True) }}
{{ easy_time("2023-04-07 00:00:00", max_period='hour') }}

{% from 'easy_time.jinja' import big_time %}

{# Overriding language or utc entity attribute #}
{{ big_time("2023-04-07 00:00:00", 'en', True) }}
{{ big_time("2023-04-07 00:00:00", language='en', utc=True) }}
{{ big_time("2023-04-07 00:00:00", max_period='hour') }}

image

I’m away on holiday, I’ll check when
I get home

ofc not urgent. Enjoy your holidays.:sunny:

for when you get back:

{% set batteries = states.sensor
              |selectattr('attributes.device_class','defined')
              |selectattr('attributes.device_class','eq','battery')
              |rejectattr('state','in',['unknown','unavailable'])
              |map(attribute='entity_id')|list %}

in a custom_template batteries jinja. seems very useful addition

using it like:

      - unique_id: low_level_batteries
        state: >
          {% set alert_level = states('input_number.battery_alert_level')|int(default=0) %}
          {% from 'batteries.jinja' import batteries %}
          {{expand(batteries)
                   |map(attribute='state')
                   |map('int',default=0)
                   |select('<',alert_level)
                   |list|count}}
        attributes:
          batteries: >
            {% from 'batteries.jinja' import batteries %}
            {{ batteries }}
          low_batteries: >
            {%- set alert_level = states('input_number.battery_alert_level')|int(default=0) %}
            {% from 'batteries.jinja' import batteries %}
            {%- set ns = namespace(batt_low=[]) %}
            {%- for s in expand(batteries)
              if s.state|int(default=0) < alert_level %}
            {%- set ns.batt_low = ns.batt_low + [' ' + s.entity_id] %}
            {%- endfor %}
            {{- ns.batt_low|join(',')}}

or in the Frontend:

  - type: custom:auto-entities
    card:
      type: entities
      state_color: true
    filter:
      template: >
        {% set threshold = states('input_number.battery_alert_level')|float(0) %}
        {% from 'batteries.jinja' import batteries %}
        {% for s in expand(batteries)
           if s.state|is_number and s.state|float(0) < threshold %}
          {{s.entity_id}}
        {%- endfor %}

might even be able to do

  - alias: Create battery group
    id: create_battery_group
    trigger:
      platform: homeassistant
      event: start
    action:
      service: group.set
      data:
        object_id: battery_sensors_auto
        icon: mdi:battery-sync
        entities: >
            {% from 'batteries.jinja' import batteries %}
            {{ batteries }}

though it might be racing there…

update
hmm, upon restart (and configured as trigger template) this wont work, all showing 0. even though the template in dev tools shows perfectly.
is this yet another example of trigger trouble…?
yep, rewriting the identical template to a regular template sensor solves it. available immediately…

There seems to be an issue with utc keyword arg and arg. So if you omit that, it will work. I’ll have to look into why this is happening with UTC.

Secondly, one of the examples is mistakingly missing the attribute. When you don’t use langauge='xxx', and you specify just hte arugments, you need to supply all arguments. My example is missing the attribute argument.

{% from 'easy_time.jinja' import easy_time %}

{# Overriding language or utc entity attribute #}
{{ easy_time("2023-04-07 00:00:00", None, 'de') }}
{{ easy_time("2023-04-07 00:00:00", language='nl') }}
{{ easy_time("2023-04-07 00:00:00", max_period='hour') }}
1 Like

1.0.9

Fixes

  • Fixes an issue with utc true/false flag on all functions that have it.
  • Fixes an issue with last_day_in_month.
  • Update documents to reflect correct function calls.

New languages

  • Polish
1 Like

Why “custom_time” does not work but “custom_time_between” does in this code?

{% from 'easy_time.jinja' import custom_time_between, custom_time, easy_time %}
{% set t1 = (states('sensor.hours_slept_0_night_ago') | float(0) * 60 * 60) | int %}
{% set t2 = (states('sensor.hours_slept_per_week') | float(0) * 60 * 60)/7| int  %}
Last night I spent {{ iif(t1 >= t2, "more time", "less time")   }} in bed
than weeks average by {{ custom_time_between(t1,t2, 'hour,minute') }}

{{ custom_time( t1, 'hour,minute') }}

Here is picture bellow with sensors info

thanks in advance great work

This library expects timestamps. I.e. dates in time. Not durations. “45 hours” is not a date in time, it’s a duration. The functions do accept duration sensors, which you have. That means you don’t have to do the math yourself.

custom_time('sensor.hours_slept_0_night_ago', 'hour,minute')

When you convert the values to number of seconds, you have to use time between, you have no choice.

There is a typo in the Portuguese language.
The day of the week is “Domingo”, not “Somingo”

  'pt':{
    '_language': 'Português',
    'and': 'e',
    'in': 'em',
    'ago': 'antes',
    'now': 'agora',
    'lose': 'perde',
    'gain': 'ganha',
    'time':{
      'format': '24-hr',
      'year': [
        'aa',
        'ano',
        'anos',
      ],
      'week': [
        'sem',
        'semana',
        'semanas',
      ],
      'day': [
        'd',
        'dia',
        'dias',
      ],
      'hour': [
        'h',
        'hora',
        'horas',
      ],
      'minute': [
        'min',
        'minuto',
        'minutos',
      ],
      'second': [
        'seg',
        'segundo',
        'segundos',
      ],
    },
    'delta':{
      'today': 'hoje',
      'tomorrow': 'amanhã',
      'yesterday': 'ontem',
      'next': 'seguinte',
      'last': 'anterior',
    },
    'days':[
      "Segunda",
      "Terça",
      "Quarta",
      "Quinta",
      "Sexta",
      "Sábado",
      "Domingo",
    ],
    'months':[
      'Janeiro',
      'Fevereiro',
      'Março',
      'Abril',
      'Maio',
      'Junho',
      'Julho',
      'Agosto',
      'Setembro',
      'Outubro',
      'Novembro',
      'Dezembro',
    ]
  },