Need template to calculate time between alarm and now in weekdays and weekend

HI,

for my speech engine I am trying to use my weekday and weekend alarm sensors to create a subsidiary sensor for the time to next alarm.

All goes well when either on weekdays, or weekends. But how to calculate that from friday (being a workday) to alarm on saturday (non workday with other alarmtime)…

these are my current sensors:

sensor:
  - platform: template
    sensors:
      alarmclock_wd_time:
        friendly_name: 'Time wD'
        value_template: >
          {{ '%0.02d:%0.02d' | format(states('input_number.alarmclock_wd_hour') | int, 
                                      states('input_number.alarmclock_wd_minute') | int) }}

      alarmclock_we_time:
        friendly_name: 'Time wE'
        value_template: >
          {{ '%0.02d:%0.02d' | format(states('input_number.alarmclock_we_hour') | int,
                                      states('input_number.alarmclock_we_minute') | int) }}

      next_alarm_wd:
        friendly_name: Seconds untill next wD alarm
        value_template: >
          {% set relative_time =  (states('input_number.alarmclock_wd_hour')|float|multiply(60) + 
                                   states('input_number.alarmclock_wd_minute')|float) - 
                                   (now().hour|float|multiply(60) + now().minute) %}
          {% if relative_time < 0 %} {{23*60 + relative_time}}
          {% else %} {{ relative_time - 60}}
          {%- endif %}

      next_alarm_ww:
        friendly_name: Seconds untill next wE alarm
        value_template: >
          {% set relative_time =  (states('input_number.alarmclock_wd_hour')|float|multiply(60) + 
                                   states('input_number.alarmclock_wd_minute')|float) - 
                                   (now().hour|float|multiply(60) + now().minute) %}
          {% if relative_time < 0 %} {{23*60 + relative_time}}
          {% else %} {{ relative_time - 60}}
          {%- endif %}

      time_until_next_alarm:
        friendly_name: Time untill next alarm
        value_template: >
          {% if is_state('binary_sensor.workday_sensor', 'on') %}
            {{ (states('sensor.next_alarm_wd').split(':')[0] | int * 60 ) | timestamp_custom('%H:%M') }}
          {% else %}
            {{ (states('sensor.next_alarm_we').split(':')[0] | int * 60 ) | timestamp_custom('%H:%M') }}
          {% endif %}

thanks for having a look.

Something like this

      next_alarm:
        friendly_name: Next alarm
        entity_id:
          - sensor.date
          - input_number.alarmclock_wd_hour
          - input_number.alarmclock_wd_minute
          - input_number.alarmclock_we_hour
          - input_number.alarmclock_we_minute
        value_template: >
          {%- macro getalarm(day) %}
          {%- set fmat = 'd' if day in range(5) else 'e' %}
          {%- set hour = 'input_number.alarmclock_w{}_hour'.format(fmat) %}
          {%- set minute = 'input_number.alarmclock_w{}_minute'.format(fmat) %}
          {{ (states(hour) | float | multiply(60) + states(minute) | float) - (now().hour * 60 + now().minute) }}
          {%- endmacro %}
          {% set todaynumber = now().weekday() %}
          {% set today_alarm = getalarm(todaynumber) | float %}
          {% set tomorrownumber = todaynumber + 1 if todaynumber + 1 < 7 else 0 %}
          {% set tomorrow_alarm = getalarm(tomorrownumber) | float %}
          {% set alarm = tomorrow_alarm if today_alarm < 0 else today_alarm %}
          {{ alarm | timestamp_custom('%H:%M') %}
1 Like

wow, cooool! thanks!!

only had to change the final %} in }}

and rebooting now… hope this is real templating magic…

thanks @petro

will now be able have HA talk to me about my next rising…:

  say_sleep_left:
    alias: Say Sleep Left
    sequence:
      - service: tts.google_say
        data_template:
          entity_id: media_player.master.bedroom
          message: >
            Next alarm in {{ (states('sensor.time_until_next_alarm').split(':')[0]) | timestamp_custom('%H') | int }} 
            hours and {{ (states('sensor.time_until_next_alarm').split(':')[1] )| timestamp_custom("%M")  }} 
            minutes. Good night and sleep well.

or even warn me if the remains time to the next wakeup is getting short (#GOT season 8 is nearing…april14th, and watching all shows over again)

So it works?

I am afraid it doesn’t just yet. Had a coincidental match before, but now the sensor won’t update correctly:

28

bottom one being yours. and the top one being correct…It doesn’t update on changing the individual sliders, so I must check why that is.

might have to add now() until midnight to the tomorrow value.

hmmm, where would I do that…?
secondly, of minor importance, but still worth a shot: as you can see here:

there’s a leading ‘0’ for minutes under 10. which is a bit weird having that over tts… how could I ditch that? It doesn’t do so with the hours, which sounds just as I would like it to

btw, seems I can just do:

            Next alarm in {{ (states('sensor.time_until_next_alarm').split(':')[0])}} 
            hours and {{ (states('sensor.time_until_next_alarm').split(':')[1] )  }} 
            minutes. Good night and sleep well.

been having a go with the .replace(‘0’,’ '), but then it cuts the 0 of ‘10’ also …:wink:

Ugh, more complicated once I thought about it. This should work:

      next_alarm:
        friendly_name: Seconds until next alarm
        entity_id:
          - sensor.date
          - input_number.alarmclock_wd_hour
          - input_number.alarmclock_wd_minute
          - input_number.alarmclock_we_hour
          - input_number.alarmclock_we_minute
        value_template: >
          {%- macro getalarm(offsetday=0) %}
          {%- set day = (now().weekday() + offsetday) % 7 %}
          {%- set offset = offsetday * 86400 %}
          {%- set fmat = 'd' if day in range(5) else 'e' %}
          {%- set hour = 'input_number.alarmclock_w{}_hour'.format(fmat) %}
          {%- set minute = 'input_number.alarmclock_w{}_minute'.format(fmat) %}
          {%- set alarm = states(hour) | float | multiply(3600) + states(minute) | float | multiply(60) %}
          {%- set time = now().hour * 3600 + now().minute * 60 %}
          {{ (offset - time) + alarm if offsetday else alarm - time }}
          {%- endmacro %}
          {% set today_alarm = getalarm() | float %}
          {% set tomorrow_alarm = getalarm(1) | float %}
          {% set alarm = tomorrow_alarm if today_alarm < 0 else today_alarm %}
          {{ alarm | timestamp_custom('%H:%M', False) }}

bingo!

46

1 caveat: won’t pass the 24 hour limit… Could we make this so my input_number of

input_number:
  alarmclock_wd_hour:
    name: Hour
    icon: mdi:timer
#    initial: 6
    min: 4
    max: 23
    step: 1

is fully utilized? Say, right now is 20:16. If I set my alarm for tomorrow 21:32, it shows

24

while it should ideally have a state so I can template the day (only 1 max), hours and secs.

Well, you’d need to add days to the format because it’s over 24 hours. It’s built into the calculation, just not the template_custom

timestamp_custom('%-d %H:%M', False)

But then you have to account for under 24 hours because %d will return the number of days in last month if it’s under 24 hours (it’s wierd). So you’d need to count the days and programatically add them:

      next_alarm:
        friendly_name: Seconds until next alarm
        entity_id:
          - sensor.date
          - input_number.alarmclock_wd_hour
          - input_number.alarmclock_wd_minute
          - input_number.alarmclock_we_hour
          - input_number.alarmclock_we_minute
        value_template: >
          {%- macro getalarm(offsetday=0) %}
          {%- set day = (now().weekday() + offsetday) % 7 %}
          {%- set offset = offsetday * 86400 %}
          {%- set fmat = 'd' if day in range(5) else 'e' %}
          {%- set hour = 'input_number.alarmclock_w{}_hour'.format(fmat) %}
          {%- set minute = 'input_number.alarmclock_w{}_minute'.format(fmat) %}
          {%- set alarm = states(hour) | float | multiply(3600) + states(minute) | float | multiply(60) %}
          {%- set time = now().hour * 3600 + now().minute * 60 %}
          {{ (offset - time) + alarm if offsetday else alarm - time }}
          {%- endmacro %}
          {% set today_alarm = getalarm() | float %}
          {% set tomorrow_alarm = getalarm(1) | float %}
          {% set alarm = tomorrow_alarm if today_alarm < 0 else today_alarm %}
          {% set days = alarm // 86400 %}
          {% if days %}
            {{ alarm | timestamp_custom('%-d day %H:%M', False) }}
          {% else %}
            {{ alarm | timestamp_custom('%H:%M', False) }}
          {% endif %}

hmm, if I use {{ alarm | timestamp_custom('%d:%H:%M', False) }} it is correct when the alarm is +24 hours from now, but incorrect when not. It simply always shows 01:

update

I can see it jump to the correct timing today (being Friday, calculating the time until the alarm on Saturday)

Misses the extra day still, because I havent changed that yet, not sure if the addition of %d was the correct way to do that?)

Did you see the new template I posted in the last post? It properly accounts for the day…

IM really sorry, but I must have missed that, otherwise I wouldn’t have asked…
changed set up, but can’t test right now, because my input_number won’t allow to go far enough now.
Thanks! and I’ll report back.

SInce you’re into it now… (I hope):

what would happen if the weekend alarm was switched off, and the next alarm would ship over the weekend…

A test for input_boolean.alarmclock_we_enabled == ‘off’ is available:

would be way cool if at all possible.

14

44

50

something must be incorrect, because it shows 2 days, when in fact it should be 1, going from a weekend day (sunday) to weekday, first monday:

strange thing is, it doesn’t really matter what is entered in the {% set days = alarm // 86400 %} line. each number or quoted string causes days to exist and display day in the output?

That’s odd, it increments for me. Unfortunately I won’t be able to debug because I’m not at home

ok, cool, no worries. I will be patient :wink:

it does however check with my earlier finding that when adding &d it always showed 1 for day, even when the alarm was set to the same day.
It now adds 1 correctly, so always shows 2… if the alarm is set to more than 24 hours from now.

please let me ask a related question.
I’ve changed the output of the sensor to:

         {% if days %}
            {{ alarm | timestamp_custom('%-d:%-H:%-M', False) }}
          {% else %}
            {{ alarm | timestamp_custom('%-H:%-M', False) }}
          {% endif %}

so I can use that for my tts.google_say script:

  say_sleep_left:
    alias: Say Sleep Left
    sequence:
      - service: tts.google_say
        data_template:
          entity_id:
            - >
              {{states('sensor.intercom')}}
            - >
              {{states('sensor.sleep_radio')}}
          message: >
            Good {{states('sensor.day_phase')|lower}},
            {% set time = states('sensor.time_until_next_alarm') %}
            {% if time == 'Not set, relax' %} alarm is {{time|lower}}.
            
            {% elif time|length > 5%}
               next alarm will be in 
                 {% if time.split(':')[1] == '0' %} {{time.split(':')[0] }} days {{time.split(':')[2] }} minutes.
                 {% elif time.split(':')[2] == '0' %} {{time.split(':')[0] }} days and {{time.split(':')[1] }} hours exactly.
                 {% else %} {{time.split(':')[0] }} days, {{ time.split(':')[1] }} and {{time.split(':')[2] }} minutes.
                 {% endif%}
                 
            {% else %}
               next alarm will be in 
                 {% if time.split(':')[0] == '0' %} {{time.split(':')[1] }} minutes.
                 {% elif time.split(':')[1] == '0' %} {{time.split(':')[0] }} hours exactly.
                 {% else %} {{time.split(':')[0] }} hours and {{time.split(':')[1] }} minutes.
                 {% endif%}
            {% endif %}

            {% set phase = states('sensor.day_phase') %}
            {% if phase in ['Morning','Day']  %} Have a nice {{phase|lower}}.
            {% elif phase == 'Evening' %} Enjoy your {{phase|lower}}
            {% else %} Sleep well.
            {% endif %}

this does work but might not be as intelligent as it could be (since it is hard coded), using a |length filter in the template to decide which message is spoken.
Would you change that?

Also, it now shows:

35

so I changed the customize template for the unit_of_measurement in to

    sensor.time_until_next_alarm:
      templates:
        icon: >
          if (entities['input_boolean.alarmclock_wd_enabled'].state === 'on' || 
              entities['input_boolean.alarmclock_we_enabled'].state === 'on') return 'mdi:clock-end';
          return 'mdi:alarm-off';
        unit_of_measurement: >
          if (state ==='Not set, relax') return null;
          if (state.length > 5) return 'D:H:M';
          return 'H:M';

building on this, and this might be a bit of a challenge, I was wondering it it could be perfected and take into account the booleans for enabling alarm on weekdays and weekend days:
input_boolean.alarmclock_wd_enabled
input_boolean.alarmclock_we_enabled

scenario:

  • if both ‘on’, the current template would be perfect (only check the strange day count of 2)
  • if both 'off, simply state ‘Not set, relax’
  • if only weekend enabled, have it skip the weekdays and on sunday add the 6 days to the next saterday
  • if only weekday enabled, have it skip the weekend days and on friday add the 3 days to jump to monday

carefully expanding like this:

        value_template: >
          {% if is_state('input_boolean.alarmclock_wd_enabled','on') or
                is_state('input_boolean.alarmclock_we_enabled','on') %}
            {%- macro getalarm(offsetday=0) %}
            {%- set day = (now().weekday() + offsetday) % 7 %}
            {%- set offset = offsetday * 86400 %}
            {%- set fmat = 'd' if day in range(5) else 'e' %}
            {%- set hour = 'input_number.alarmclock_w{}_hour'.format(fmat) %}
            {%- set minute = 'input_number.alarmclock_w{}_minute'.format(fmat) %}
            {%- set alarm = states(hour) | float | multiply(3600) + states(minute) | float | multiply(60) %}
            {%- set time = now().hour * 3600 + now().minute * 60 %}
            {{ (offset - time) + alarm if offsetday else alarm - time }}
            {%- endmacro %}
            {% set today_alarm = getalarm() | float %}
            {% set tomorrow_alarm = getalarm(1) | float %}
            {% set alarm = tomorrow_alarm if today_alarm < 0 else today_alarm %}
            {% set days = alarm // 86400 %}
            {% if days %}
              {{ alarm | timestamp_custom('%-d:%-H:%-M', False) }}
            {% else %}
              {{ alarm | timestamp_custom('%-H:%-M', False) }}
            {% endif %}
          {% elif is_state('input_boolean.alarmclock_wd_enabled','off') and 
                is_state('input_boolean.alarmclock_we_enabled','off') %} Not set, relax
          {% endif %}

but the big challenge lies ahead…(still using the ‘or’ in the first template, which has to be changed obviously )

HI @petro,

not to be harassing you, but did you ever get a chance to have a look? Hope you can find a moment, because I really need you…
would real really be cool if the template could take into account the input_booleans, so it can show the correct time at all times
(right now it calculates the weekend time, while boolean is set to off, and it should ideally jump to the weekday time, adding a day…

the other thing was the current template is always showing 2 for days when it in fact is 1. have a look, ive added {{days}} and {{alarm}} in the if section of the template:

the numbers seem to be correct, but days is incorrectly calculated?
88088/86400 should lead to 1 and not 2. how can this be happening in the forst place.

Of course %d is the weekday as a decimal number, and we don’t want the weekday here, but the number of days. Wouldn’t this then be the correct was of calculating that:

{% if days %}
              {{days|int}}:{{ alarm | timestamp_custom('%-H:%-M', False) }}

Yes, I’d probably make a sensor that just contains the timestamp instead of the parsed values. Then use that for messages and make a separate sensor that displays it for the interface.

This would require a total rework of the current sensors you have. It’s possible, but wouldn’t be easy in Jinja.

a ok.
Will have a look if I can conjure that up.
in the mean time have been looking at this template made earlier in coalition between @klogg and yourself, to which I added the getalarm(). It seems to work…:

            {%- macro getalarm(offsetday=0) %}
            {%- set day = (now().weekday() + offsetday) % 7 %}
            {%- set offset = offsetday * 86400 %}
            {%- set fmat = 'd' if day in range(5) else 'e' %}
            {%- set hour = 'input_number.alarmclock_w{}_hour'.format(fmat) %}
            {%- set minute = 'input_number.alarmclock_w{}_minute'.format(fmat) %}
            {%- set alarm = states(hour) | float | multiply(3600) + states(minute) | float | multiply(60) %}
            {%- set time = now().hour * 3600 + now().minute * 60 %}
            {{ (offset - time) + alarm if offsetday else alarm - time }}
            {%- endmacro %}          
{% set up_time = getalarm(1) | float %}
          {% set minutes = (up_time // 60) | int %}
          {% 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 | int %}
                    {%- set end = 's' if value > 1 else '' %}
                    {{- '{} {}{}'.format(value, name, end) if value | int > 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(',') %}
          {% set text = text[:last_comma] + ' and' + text[last_comma + 1:] %}

          {{ text }}

maybe that could also be used to check for my endgoal, jumping over the weekdays/weekend days if necessary?

would you understand why this is happening:

would this be in any way useful, at least it counts the number of days until the next alarm (provided it isn’t within the next 24 hours)

number_of_days:
  value_template: >
    {% if is_state('input_boolean.alarmclock_wd_enabled','on') %}
      {% if now().weekday() in [0,1,2,3,6] %} 1
      {% elif now().weekday() == 4 %}
        {% if is_state('input_boolean.alarmclock_we_enabled','on') %} 1
        {% else %} 3
        {% endif %}
      {% elif now().weekday()  == 5 %}
        {% if is_state('input_boolean.alarmclock_we_enabled','on') %} 1
        {% else %} 2
        {% endif %}
      {% endif %}
    {% elif is_state('input_boolean.alarmclock_we_enabled','on') %}
        {% if now().weekday() in [0,1,2,3] %} {{5 - now().weekday()}}
        {% elif now().weekday() in [4,5] %} 1
        {% elif now().weekday() == 6 %} 6
        {% endif %}
    {% else %} Not set
    {% endif %}

Not sure if this is the optimal approach, but would have thought this to be more efficient than checking per day. Maybe this could be used for the offset in your template?

this seems to work alright:

            {%- macro getalarm(offsetday=0) %}
            {%- set day = (now().weekday() + offsetday) % 7 %}
            {%- set offset = offsetday * 86400 %}
            {%- set fmat = 'd' if day in range(5) else 'e' %}
            {%- set hour = 'input_number.alarmclock_w{}_hour'.format(fmat) %}
            {%- set minute = 'input_number.alarmclock_w{}_minute'.format(fmat) %}
            {%- set alarm = states(hour) | float | multiply(3600) + states(minute) | float | multiply(60) %}
            {%- set time = now().hour * 3600 + now().minute * 60 %}
            {{ (offset - time) + alarm if offsetday else alarm - time }}
            {%- endmacro %}

            {% set in_days = states('sensor.number_of_days_next_alarm') %}
            {% set today_alarm = getalarm() | float %}
            {% set next_alarm = getalarm(in_days|int) | float %}
            {% set alarm = next_alarm if today_alarm < 0 else today_alarm %}
            {% set days = alarm // 86400 %}
            {% if days %}
              {{days|int}}:{{ alarm | timestamp_custom('%-H:%-M', False) }}
            {% else %}
              {{ alarm | timestamp_custom('%-H:%-M', False) }}
            {% endif %}

so my setup now is:

            {% macro getalarm(offsetday=0) %}
            {% set day = (now().weekday() + offsetday) % 7 %}
            {% set offset = offsetday * 86400 %}
            {% set fmat = 'd' if day in range(5) and states('input_boolean.alarmclock_wd_enabled') == 'on' 
                     else 'e' %}

            {% set hour = 'input_number.alarmclock_w{}_hour'.format(fmat) %}
            {% set minute = 'input_number.alarmclock_w{}_minute'.format(fmat) %}
            {% set alarm = states(hour) | float | multiply(3600) + states(minute) | float | multiply(60) %}
            {% set time = now().hour * 3600 + now().minute * 60 %}
            {{ (offset - time) + alarm if offsetday else alarm - time }}
            {% endmacro %}

            {% if is_state('input_boolean.alarmclock_wd_enabled','on') or
                  is_state('input_boolean.alarmclock_we_enabled','on') %}
            {% set in_days = states('sensor.number_of_days_next_alarm') %}
            {% set today_alarm = getalarm() | float %}
            {% set next_alarm = getalarm(in_days|int) | float %}
            {% set alarm = next_alarm if today_alarm < 0 else today_alarm %}
            {% set days = alarm // 86400 %}

              {% if days %} {{days|int}}:{{ next_alarm | timestamp_custom('%-H:%-M', False) }}
              {% else %} {{ alarm | timestamp_custom('%-H:%-M', False) }}
              {% endif %}

            {% elif is_state('input_boolean.alarmclock_wd_enabled','off') and 
                    is_state('input_boolean.alarmclock_we_enabled','off') %} Not set, relax
            {% endif %}

of course this also still uses my little ‘hack’ using days|int since the %-d still is always 1 day off… wish I could spot the error there…

–edit—

timing issue had disappeared using the extra condition, and now working perfectly.

(2things I know to be incorrect, but not bothering now: day count is not in hours but counting to the day the alarm is set . So if tomorrow, day count is 1 even if less than 24 hours. Also, the original %-d is still off… )