Add time to running timer

This converts a timer’s remaining attribute, which is a string representing hours, minutes, and seconds, into a single integer value representing total seconds. It adds 15 seconds to the total then converts it back into string format.

  • The first line gets remaining and converts it into a list with three items (hours, minutes, seconds).
  • The second line takes each one of the three list items and converts it into seconds, adds all three values together, then adds 15 seconds.
  • The third line converts the total seconds into hours, minutes, and seconds and formats them to appear as HH:MM:SS
{% set t = state_attr('timer.my_timer', 'remaining').split(':') %}
{% set ts = (t[0]|int * 3600) + (t[1]|int * 60) + t[2]|int + 15 %}
{{ '{:02d}:{:02d}:{:02d}'.format(ts // 3600, (ts % 3600) // 60, (ts % 3600) % 60) }}

HOWEVER, my preference is to convert the remaining attribute into a datetime object and take advantage of its capabilities. The resulting code is neater and easier to read than my previous example.

  • The first line gets remaining and converts it into a datetime object.
  • The second line create a ‘zero-time’ datetime object.
  • The third line subtracts the two datetime objects, then uses the datetime object’s total_seconds method to get the result in seconds, then adds 15 seconds to it.
  • The fourth line converts the total seconds into hours, minutes, and seconds and formats them to appear as HH:MM:SS
{% set x = strptime(state_attr('timer.my_timer', 'remaining'), '%H:%M:%S') %}
{% set y = strptime('00:00:00', '%H:%M:%S') %}
{% set ts = ((x - y).total_seconds() + 15) | int %}
{{ '{:02d}:{:02d}:{:02d}'.format(ts // 3600, (ts % 3600) // 60, (ts % 3600) % 60) }}
1 Like

I tried that but I kept getting very strange results that I couldn’t do anything with (However I never tried just creating a 0 timestamp and then subtracting the raw datetime objects).

in the example I was using with a remaining time of 0:03:15 it gave me a result of 1900-01-01 00:03:15.

then when I tried converting it to a timestamp_custom using %H:M:%S I ended up with 23:38:15 and I had no idea where that number came from. If I added “false” to the end of the timestamp_custom I got 05:03:15 so it was 5 hours off because of the timezone shift. I couldn’t figure out a way to correct it.

It didn’t make sense in my limited knowledge.

Ditto.

You’d think it would provide 1970-01-01 which is the starting date for Unix timestamps. Instead it uses the start of the previous century which, when converted into a timestamp, results in a large negative number. Not sure why it uses that particular date.

The technique I used was to subtract two datetime objects where one is basically ‘zero-time’. The result of the subtraction is a timedelta object which has a method called total_seconds(). All of this avoids the issue you encountered with timestamp_custom.

1 Like

Here’s yet another way to do it. I think I like this one best now. :slight_smile:

This technique appends UTC timezone to the timer’s remaining attribute. Now when strptime converts it into a datetime object, the result is no longer ‘timezone naive’.

  • First line appends UTC timezone offset (-0000 hours) to remaining.
  • Second line converts the string into a (timezone-aware) datetime object.
  • Third line converts the datetime object to a timestamp, adds 15 seconds, then converts it into a string formatted as HH:MM:SS.
{% set x = state_attr('timer.my_timer', 'remaining') ~ '-0000' %}
{% set y = strptime(x, '%H:%M:%S%z') %}
{{ (as_timestamp(y) + 15) | timestamp_custom('%H:%M:%S', false) }}

It looks like there’s a few ways to do this. Now… The other problem is that you can’t “restart” a timer. You have to cancel or finish it, then start it with the new duration. How would you accomplish this in a script?

If you call the timer.start service with a duration attribute and the timer is already running, it actually resets the remaining time to (new duration - elapsed time). So if you set the timer for 10 minutes and started it, let it run for 1 minute, then started it again with a 20 minute duration, the timer would now have 19 minutes left.

So you need to cancel the timer first, the start it with the new calculated time. But if you cancel the timer, the remaining attribute becomes 0:00:00 and we have nothing to calculate off of.

you could do the following in the script:

pause the timer
calculate the new set value from the remaining attribute
cancel the timer
start the timer with the new set value

seems kind of cumbersome but it would work.

As per @klogg’s advice, you must pause the running timer first and then its remaining attribute will contain the actual remaining time. Use that value to calculate the desired new duration. Then start the timer using the new duration.


EDIT

ninja’d by finity


I just noticed that when you pause a running timer, its remaining attribute contains a floating point number, not an integer. In other words, it shows fractional seconds. For example, here’s the result of pausing a 4-minute timer:

0:03:18.687535

That means my third and favorite solution won’t work because it assumes that seconds is an integer value (i.e. strptime fails to convert the string). There’s simple solution and that’s to simply add .%f to strptime so it can digest the decimal portion of the seconds:

{% set y = strptime(x, '%H:%M:%S.%f%z') %}

However, the problem with this technique is that when the timer’s state is idle, it displays the seconds in remaining as an integer value … and then the .%f causes strptime to fail. Bummer. Might need to use the first suggestion which doesn’t employ strptime.

:laughing:

I noticed that yesterday when I was playing around with this but I don’t think it matters since your strptime conversion ignores the fractions of a second.

If it does mess it up somehow you can format the strptime to pull the fraction using “%f” in the conversion then force the timestamp to an int before proceeding.

I was modifying my previous post when you replied. .%f isn’t a panacea for the reason I outlined (seconds is float when timer is paused and integer when idle).

A test could be added to check the timer’s state to decide if the conversion should, or should not, use .%f but that just bloats the template and negates the efficiency of using datetime objects.

Here we go. This is what I have for a script now and it’s working exactly like I want. Thanks a ton for the help!

add_screentime_calli_script:
alias: Calli Screentime - Add 10 minutes
sequence:

  • data:
    entity_id: timer.screentime_calli
    service: timer.pause
  • data_template:
    duration: >-
    {% set t = state_attr(‘timer.screentime_calli’, ‘remaining’).split(’:’) %}
    {% set ts = (t[0]|int * 3600) + (t[1]|int * 60) + t[2]|int + 600 %}
    {{ ‘{:02d}:{:02d}:{:02d}’.format(ts // 3600, (ts % 3600) // 60, (ts % 3600) % 60) }}
    entity_id: timer.screentime_calli
    service: timer.start

I don’t think that matters as long as you ensure the template will only be evaluated when the timer is paused and it will then only have fractions included.

Here is the new working template:

{% set x = state_attr('timer.garage_lights_timer', 'remaining') ~ '-0000' %}
{% set y = strptime(x, '%H:%M:%S.%f%z') %}
{{ (as_timestamp(y)|int + 15) | timestamp_custom('%H:%M:%S', false) }}

Glad to hear it.

Perhaps not quite the way you envisioned it would work (need to pause the timer first then do some gymnastics with the remaining attribute). The end-result isn’t super-pretty but, most importantly, gets the job done.

Not trying to be negative but we might as well get this out of the way here…

In the future you really need to post your code in the correct format.

It helps us see if you have syntax errors.

Here is one way to do it:

Just trying to be helpful (again…:grinning:) because someone will tell you that next time if you don’t do it correctly.

Here’s the script but formatted and using the third offered solution for doing the time arithmetic:

add_screentime_calli_script:
  alias: 'Calli Screentime - Add 10 minutes'
  sequence:
  - service: timer.pause
    entity_id: timer.screentime_calli
  - service: timer.start
    data_template:
      entity_id: timer.screentime_calli
      duration: >-
        {% set r = state_attr('timer.screentime_calli', 'remaining') ~ '-0000' %}
        {% set t = strptime(r, '%H:%M:%S.%f%z') %}
        {{ (as_timestamp(t) + 600) | timestamp_custom('%H:%M:%S', false) }}

I tested it using one of my own timers and it works well.

2 Likes

Hi Taras, I know this is a bit old, but I appreciate what you’ve done here. I’m trying to do the same thing. I have it set up, but when I run the script I get the following error:

[140520736539168] Error rendering data template: TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 389, in async_render
    render_result = _render_with_context(self.template, compiled, **kwargs)
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 1358, in _render_with_context
    return template.render(**kwargs)
  File "/usr/local/lib/python3.8/site-packages/jinja2/environment.py", line 1304, in render
    self.environment.handle_exception()
  File "/usr/local/lib/python3.8/site-packages/jinja2/environment.py", line 925, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "<template>", line 1, in top-level template code
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

My code is:

  increment_goodnight_timer_up:
    sequence:
      - service: timer.pause
        entity_id: timer.goodnight_countdown

      - service: timer.start
        data_template:
          entity_id: timer.goodnight_countdown
          duration: >-
            {% set r = state_attr('timer.goodnight_countdown', 'remaining') ~ '-0000' %}
            {% set t = strptime(r, '%H:%M:%S.%f%z') %}
            {{ (as_timestamp(t) + 60) | timestamp_custom('%H:%M:%S', false) }}

Any idea what I’m missing? My guess is it’s related to the float issue, but I will admit I don’t know where to start with resolving it.

I know this is old, but I came across this while searching and thought I’d update with how I got this done (without having to pause the timer).

 - if:
  - condition: state
    entity_id: timer.pb3_timer
    state: active
then:
  - service: timer.start
    data_template:
      entity_id: timer.pb3_timer
      duration: >-
        {% set f = state_attr('timer.pb3_timer', 'finishes_at') %}
        {% set x = (as_datetime(f) - now()).total_seconds() + (30*60) %} 
        {{ x  | timestamp_custom('%H:%M:%S', false)}}
else:
  - service: timer.start
    data:
      duration: "00:30:00"
    target:
      entity_id: timer.pb3_timer
6 Likes

Thanks for this, worked a treat :slight_smile:

Hi everyone,

Again, I know this is an old thread but though I’d share my “UI only” solution, that didn’t require any templating. My use case is a bathroom fan, and I want to have it turn on when you press a button, running for 10 minutes, and add 10 minutes every time you press the button (up to a maximum of 1 hour).

  1. Timer helper. Nothing special here - it doesn’t actually matter what I set the count to as I will change it in the script. My entity ID is timer.bathroom_fan_timer

  2. Automation to have the fan be switched on/off based on the timer start, finish or cancel.

  3. Script to run when the button is pressed (it’s set to Queue mode). The key here is in the conditional, it first starts the timer with the max of 1 hour then immediately reduces it by 50 minutes, to get to 10 minutes - but allowing subsequent presses to add 10 minutes up to the max of 1 hour.

The only thing that isn’t quite how I would like it is that the script leaves the timer default set to 1 hour. This means if you activate the timer from the UI, it will leave the fan on for an hour (i’d prefer it to default to 10 minutes). I could probably get around this by doing something like upon finish/cancel, start the timer briefly at 10minutes then cancel it, but I decided not to bother.

Screenshots below.

Hope this helps others!

James

Image of Automation step 2:

image of Script step 3:

Expanded image of the first conditional step in the Script

1 Like

Hmm. I can’t extend a timer beyond it’s current “duration”. So I can’t take a 1 minute timer and add 3 minutes to it. I’d have to stop and restart.

That’s right, when you first start the timer you specify the duration - you can’t adjut it to higher than that initial value in subsequent calls. That’s why I start it first at 60 minutes then reduce it by 50 minutes: that gives mea timer set to 10 minutes that i can increase up to 60