Automation last_triggered attribute changed in 0.95?

As part of my Welcome Home automation I use the following template

 condition:
      condition: template
      value_template: >
        {% if states.automation.welcome_home.attributes.last_triggered != none %}
          {{ (now() - states.automation.welcome_home.attributes.last_triggered).total_seconds() | int > 3600}}
        {% else %}
          true
        {% endif %}

This used to work until 0.95, now it doesn’t because
(now() - states.automation.welcome_home.attributes.last_triggered)
fails with an unknown error.

There is mention in the Breaking Changes about last_triggered:

Restore automation last_triggered with initial_state override

but I am not sure if this is connected.

Anyone knows how to make this work again?

  • Prior to 0.95, if your automation used the initial_state option, after a restart the automation’s last_triggered attribute would be reset. In other words, upon startup the datetime value in last_triggered would be lost.

  • Prior to 0.95, if you did not use the initial_state option, an automation’s last_triggered attribute would survive a restart.

Starting from 0.95, the value held by last_triggered always survives a restart. With initial_state or without it, last_attribute maintains its value (if any) after a restart.

Now that’s squared away, I don’t believe the change should affect your template (even though something is clearly not working correctly for you). What does this look like in the Template Editor? Is it a properly formatted datetime object?

{{ states.automation.welcome_home.attributes.last_triggered }}

For example, this is a sample from an automation that ran a few days ago:

2019-06-26 03:39:10.774378+00:00

I get a slightly different format
{{states.automation.welcome_home.attributes.last_triggered}}
gives
2019-06-27T23:07:11.734137+00:00

{{now()}}
however results in
2019-06-30 22:23:54.848187-04:00

Normally last_triggered is a Python datetime. And since now() also returns a Python datetime, they can be compared, subtracted, etc.

However, when last_triggered is restored after a restart, it is unfortunately restored as a string. To me this is a bug.

One way to tell the difference is if you look at them on the States page, or via a template in the Template editor, the string version with have a ‘T’ between the date & time.

For now, do the arithmetic using the as_timestamp() function. E.g., this should work whether it is a restored value in string format, or in datetime format from a trigger that happened since HA started:

condition:
      condition: template
      value_template: >
        {% if states.automation.welcome_home.attributes.last_triggered != none %}
          {{ (as_timestamp(now()) -
              as_timestamp(states.automation.welcome_home.attributes.last_triggered)) | int > 3600}}
        {% else %}
          true
        {% endif %}

And, BTW, you can simplify that to:

condition:
      condition: template
      value_template: >
        {% set last_triggered = states.automation.welcome_home.attributes.last_triggered %}
        {{ last_triggered is none or
           (as_timestamp(now()) - as_timestamp(last_triggered )) | int > 3600 }}
1 Like

That’s interesting!

But the T version is actually the correct ISO date/time.

If you display a date/time on an iOS device, it will incorrectly render any date time! It will convert it as UTC and apply the system time offset… so for instance it will then show relative dates (like in 10 hours for instance). I had a LOT of trouble getting my ISP usage meter values showing correctly on iOS because of this. They would work fine on my PC in chrome but iOS was wrong. It wasn’t until I used the correct ISO date/time that everything works properly. So it may be some kind of bug but I’m happy if it’s one they don’t fix… (although in this example it doesn’t really matter)

Sorry, not following you. This is what I’m referring to:

Python 3.6.8 (default, Jan 14 2019, 11:02:34)
[GCC 8.0.1 20180414 (experimental) [trunk revision 259383]] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from datetime import datetime
>>> print(datetime.now())
2019-06-30 22:41:01.751460
>>> print(datetime.now().isoformat())
2019-06-30T22:41:07.335347

By default Python prints a datetime with a space between the date & time, whereas when its isoformat() method is used you get the ‘T’.

My point is, if a datetime is saved, when it is restored, it should still be a datetime. It should not be converted to some other type.

1 Like

This thing is giving me déjà vu; this came up in some other thread as well. It seems to me this is par for the restoration process. It always restores a datetime object as a string. Only after the automation is triggered again will last_triggered contain a proper datetime object. It’s this quirk that prevented the use of arithmetic with datetime objects in that other thread (that I can’t find right now) because, after a restart, the attribute contains is no longer a datetime object but a string.

Yep, that’s how I know about it. I’m sure I either read that discussion or took part in it. :slight_smile:

FWIW, I think this line of code:

            self._last_triggered = state.attributes.get('last_triggered')

should be this:

            self._last_triggered = state.attributes.get('last_triggered')
            if self._last_triggered:
                self._last_triggered = dt_util.parse_datetime(self._last_triggered)
1 Like

I realize you already have many irons in the fire but can we impose on you to create a PR to patch this bug?

If I recall correctly, the guy who wrote the code was adamant against this change. I vaguely remember a ‘half assed argument’ between people about this and nothing getting done.

Given that the first time the automation runs, its last_triggered attribute is assigned a datetime object, not a string, I’m going to have to agree that the argument against the change was ‘half-assed’.

I don’t understand how changing the attribute’s type, on restart, can be considered anything else other than a bug. Any date arithmetic now needs to first test if it’s dealing with a string or datetime object. :man_facepalming:

well, they use the timestamp string for a lot of things and they account for it in many places. So I think the choice for this was by design. Which is why they don’t want to change it.

If this was “by design”, then it was a poor design indeed. Not only does it not make sense, and make the system more difficult to use, I can site other locations in the code where a datetime that is saved as a string (because it is “JSON serialized”) is properly converted back to a datetime when restored.

I just tried a quick search in the issues & PRs and couldn’t find the discussion to which you referred. If you could find it that might be helpful. But in any case, at some point I may submit a PR anyway. In my mind it is, at best, a deficiency.

I found the thread. It would appear all of us discussing this issue today were discussing it back in April. :slight_smile: Small wonder this topic gave me a sense of déjà vu! The discussion begins at (approximately) this post.

Somewhere along the way I said I’d raise it as an Issue but, clearly, it ‘slipped my mind’ …

@123 @petro

Thanks for the link, but I still don’t see a reference to the author claiming not converting back to a datetime is the correct behavior. I did see this:

Does that mean there wasn’t such a discussion???

It’s possible. I talk so much on this that everything in the past tends to mesh together. I’ve had so many conversations about this and other startup oddities that I honestly can’t be 100% certain the convo took place. I just recalled a similar convo. But it might have been about last_changed / last_updated?

EDIT: Also if this PR does go through, it might screw people’s automations up. I know as_timestamp works with last_triggered as a string. We’d want to verify that the behavior stays the same if it changes to a date time. I think it will but you never know.

No problem. I can certainly understand that. :slight_smile: If/when I do submit a PR, and if there was such a conversation, it would be nice to reference it, but no biggie either way.

@nanobra1n

So the problem is that upon restart, last_triggered's value is a string and that causes the template’s date arithmetic to fail (it needs two datetime objects to work).

I believe you’ll need to enhance your automation and have it test if last_triggered is a string. If it is, you can use strptime to first convert it to a datetime object. The resulting datetime object can then be used with your existing date arithmetic.

if you do, remember the last_triggered in its current implementation is only set when the action block is executed. And not, as the name might suggest, immediately when the automation is triggered.

It needs to go past the condition block, and actually fire the action.

Reason enough, at least for some automations, to have conditions in the action block and not in the condition block.