Calculations with sun.sun and now() give wrong answer

Hi hi,

I wanted to add the time till sunset to my overview screen.
With many examples around, I thought it would be easy.
However… The calculated time is off by 1 hour.

Further investigation in the template editor:

{{ (states.sun.sun.attributes.next_setting) }}
{{ now() }}

{{ as_timestamp(states.sun.sun.attributes.next_setting) 
| timestamp_custom('%-H:%M %p') | replace(" 0", "") }}
{{ as_timestamp(now()) | timestamp_custom('%-H:%M %p') }}

{{ (as_timestamp(states.sun.sun.attributes.next_setting) 
- as_timestamp(now())) | timestamp_custom('%H:%M') }}

Calculated values:

2020-11-14T15:51:25+00:00
2020-11-14 13:36:47.077771+01:00

16:51 PM
13:36 PM

04:14

The local times are correct, but the difference is not.
What am i missing here?

I believe this is because of your timezone offset.

Yeah, that’s what I thought too.
Especially since the format in which the original times are reported is slightly different.

I am just surprised that this influences the calculation, as both times are supposedly local times.

There must be some attribute, or other function to correct this. I just can’t find it.

Most times are cast to UTC, you are an hour out so I’m assuming you are in Europe (UK specifically ???) , but we are out of DST so I’m not sure why you raise this now, where they are synchronous.
Regardless, due to the default cast to UTC, I think what you need is : -

- as_timestamp(now())) | timestamp_custom('%H:%M'), false}}

Check out finity’s EPIC time manipulation thread, to check in detail

I was able to make it work like this:

{% set ns = states.sun.sun.attributes.next_setting %}
{% set tn = now() %}

{% set nst = ns|as_timestamp|timestamp_utc|as_timestamp %}
{% set tnt = tn|as_timestamp %}

{{ ns }} = {{ nst }}
{{ tn }} = {{ tnt }}

{{ (nst-tnt) | timestamp_custom('%H:%M') }}

That will work because the sun component (by default) gives local times for sun events.
You are specifically requesting UTC times, so the comparison is direct.
But it’s a long winded way of doing it, just convert the offset to local.

The following begins by converting next_setting from a string value to an offset-aware datetime object.

The conversion is complicated by the fact that +00:00 is not understood to be a timezone offset by strptime’s %z because it prefers +0000. So we tack it on to accommodate it.

After it’s been converted, the calculation is straightforward because now we have two datetime objects, the next sunset and the current time, and both are in UTC.

{% set nr = strptime(state_attr('sun.sun', 'next_setting')~'+0000', '%Y-%m-%dT%H:%M:%S+00:00%z') %}

Next Setting (UTC): {{ nr }}
Current time (UTC): {{ utcnow() }}

Difference:
As a timedelta object: {{ nr - utcnow() }}
As a string: {{ (nr.timestamp() - utcnow().timestamp()) | timestamp_custom('%-H:%M', false) }}

Distilled down to:

{% set nr = strptime(state_attr('sun.sun', 'next_setting')~'+0000', '%Y-%m-%dT%H:%M:%S+00:00%z') %}
{{ (nr.timestamp() - utcnow().timestamp()) | timestamp_custom('%-H:%M', false) }}
1 Like

Hey Muttley,

that addition actually did the trick.
Bit unexpected, but hey, if it works, it works!

Not gonna bother with digging deeper into it. :slight_smile:

Thank you so much for your help.

Thanks for that, Taras.

Converting to UTC definitely avoids the unexpected behavior.

I had previously used the template examples above to calculate the time until the next sunset/sunrise. But a change in HA at some point has broken this. I’m guessing it was one of the more major Python version bumps.

The time format returned is no longer in '%Y-%m-%dT%H:%M:%S+00:00%z' but rather '%Y-%m-%dT%H:%M:%S.%f+00:00'. However, even making this change the delta values are no longer respecting the UTC/local difference and as a result the returned values are wrong.

Has anyone worked out how to fix this since it was broken by the update?

EDIT: I worked it out. See below for old (nr1) and new (nr2) syntax

{% set nr1 = strptime(state_attr('sun.sun', 'next_setting'), '%Y-%m-%dT%H:%M:%S.%f+00:00', 0) %}
{% set nr2 = as_datetime(state_attr('sun.sun', 'next_setting')) %}
nr1: {{ nr1 }} 
nr2: {{ nr2 }} 
utc: {{ utcnow() }}

Difference:
1. As a string: {{ (nr1.timestamp() - as_local(utcnow()).timestamp()) | timestamp_custom('%-H:%M', false) }}
2. As a string: {{ (nr2.timestamp() - as_local(utcnow()).timestamp()) | timestamp_custom('%-H:%M', false) }}

which prints out:

nr1: 2023-10-06 08:12:43.941101 
nr2: 2023-10-06 08:12:43.941101+00:00 
utc: 2023-10-06 06:54:00.151155+00:00

Difference:
1. As a string: 14:18
2. As a string: 1:18
1 Like

Interesting!
As I understand it now, this was the problem at that time:

But as I understand it now, this has been solved since Python version 3.7, and +00:00 is now understood as well, making it possible to now do it like this (nr3 added):

{% set nr1 = strptime(state_attr('sun.sun', 'next_setting'), '%Y-%m-%dT%H:%M:%S.%f+00:00', 0) %}
{% set nr2 = as_datetime(state_attr('sun.sun', 'next_setting')) %}
{% set nr3 = strptime(state_attr('sun.sun', 'next_setting'), '%Y-%m-%dT%H:%M:%S.%f%z') %}
nr1: {{ nr1 }} 
nr2: {{ nr2 }} 
nr3: {{ nr3 }} 
utc: {{ utcnow() }}

Difference:
1. As a string: {{ (nr1.timestamp() - as_local(utcnow()).timestamp()) | timestamp_custom('%-H:%M', false) }}
2. As a string: {{ (nr2.timestamp() - as_local(utcnow()).timestamp()) | timestamp_custom('%-H:%M', false) }}
3. As a string: {{ (nr3.timestamp() - as_local(utcnow()).timestamp()) | timestamp_custom('%-H:%M', false) }}

afbeelding

But your solution is more elegant and straight forward.

{{ (states('sensor.sun_next_setting') | as_datetime - now()).seconds
  | timestamp_custom('%-H:%M', false) }}
2 Likes

Thanks, this indeed is much cleaner :smiley:
You are a real template wizard!