Binary_sensor 'current time within interval': can it be better?

Hi All,

For my heating automations I needed a binary sensor that is on when current time is between times set in UI via input_datetimes from and to.
I’m using it to trigger an automation that sets night preset on my thermostat and reverts it back to normal next morning.
Initially I had this:

binary_sensor:
  - platform: template
    sensors:
      central_heating_night_time:
        value_template: >
          {% set from = strptime(states.input_datetime.central_heating_preset_night_from.state, "%H:%M:%S") %}
          {% set to = strptime(states.input_datetime.central_heating_preset_night_to.state, "%H:%M:%S") %}
          {% set cur = strptime(states.sensor.time.state, "%H:%M") %}
          {{ (cur < to or from <= cur) if from > to else from <= cur < to }}

but I didn’t like a lot of conversions and after a few transformations now it’s:

central_heating_night_time:
  # all variables below (excluding hour & minute) hold UTC times converted to timestamps (int)
  # as internally HA times are in UTC
  # nothe that you have to have sensor.time_utc configured
  value_template: >
    {% set from = states.input_datetime.central_heating_preset_night_from.attributes.timestamp %}
    {% set to = states.input_datetime.central_heating_preset_night_to.attributes.timestamp %}
    {% set hour, minute = states.sensor.time_utc.state.split(':') | map('int') %}
    {% set cur = hour*3600 + minute*60 %}
    {{ (cur < to or from <= cur) if from > to else from <= cur < to }}

It does work.
Any thought on what can be improved as it’s basically a simple task?

p.s I spent some time reading docs and this topic and even have a docs PR as it’s not as simple as it looks :wink:

In general I’d say this is pretty good.

However, I’m not sure the timestamp attribute of the input_datetime's are “UTC”, per se. I say that because I’m assuming you’ve configured them to only have time, yes? If so, the timestamp attribute is the number of seconds after midnight that the “HH:MM:SS” state of the entity represents. But that would be from local midnight, not UTC midnight. Unless you’re setting them to “UTC” values manually via the UI. So I really think you should be using sensor.time, not sensor.time_utc.

BTW, when an input_datetime does have date configured, the timestamp attribute works differently. In that case it is a Unix timestamp, which by definition, is the number of seconds since the “epoch” (1/1/1970) in UTC. But how it calculates that value depends on whether or not you’ve set the entity to a time zone. (See next…)

Overall, the input_datetime component does not use UTC (i.e., it uses a naive datetime, which is effectively local time), unless you specify a time zone suffix for its initial value or when calling the input_datetime.set_datetime service. Or you might also have issues if the time zone setting in the environment in which HA runs is not set correctly. (Unfortunately, I’ve seen that happen a lot when people use Docker or hassio – which is now called Home Assistant, as opposed to Home Assistant Core.)

1 Like

I’m lost as to why you are bothering with timestamps.
A simple text comparison should suffice as your settings are largely indifferent to date/day and that too can easily be accommodated. All these use input_datetime but no date component
Example : -
“{{ states(‘input_datetime.id1’) [0:5] <= sensor.time < states(‘input_datetime.id2’) [0:5] }}”

When I get near a workstation I’ll post some code that is aware of the midnight wrap around

yes, and no initial.
I called them “UTC” because applying timestamp_local to them gives me a wrong result but timestamp_utc does the job correctly. also, as I mentioned in comments, HA uses UTC time internally. that’s why I used sensor.time_utc.
but I completely agree that it’s a bit too much to call the number of seconds from midnight “UTC time” as there is no other information.

Interesting. I run Hass.io - how can I check that the time zone settings are correct?

because they are already there ready to use.

thanks, that’s exactly why I created this topic - to get alternative ideas :wink:

No, I’m lost again. You want it “better” I posted code that works, is both shorter and easier to understand.
Can you please define what you mean by “better” ? (BTW still working from a phone but able to post this ‘simple’ example without reference because its ‘simple’ )

If you want longer, more convoluted and obscure then we could probably do the same thing but in 300 lines of code :man_shrugging:

oh… it’s not about your code. it’s just about timing, look: when I opened this topic I a) knew timestamps were there and b) wanted to see if anything can be improved. that’s it.

Thanks for the code but I suspect it’s not midnight wrap around-aware, is it? In such a case I wouldn’t say ‘shorter’ as it’s a bit cheeky :wink:

Really and truly my ‘better’ is more or less standard - less code and easier to understand. I’m new to python and therefore keen to improve my knowledge.

2 All: any idea why this code raises “OverflowError: timestamp out of range for platform time_t”?

{{ set res = strptime(states.sensor.time.state, "%H:%M").timestamp() }}

I know the datetime object does not have all necessary information but the error message is a bit misleading…

Not exactly. Typically Python datetime objects used internally are time zone aware in UTC, that is true. But that doesn’t really apply here. Note that a true timestamp is, as I explained, a Unix epoch timestamp. However, when an input_datetime entity does not use date, the timestamp, as I explained, is no longer a Unix epoch timestamp; it’s just the number of seconds from midnight and, by itself, has no time zone information or awareness.

The reason timestamp_local didn’t give you the results you expected is because, by default, it takes a Unix epoch timestamp and converts the result to the local time zone. But since you were not feeding it a Unix epoch timestamp, you got unexpected results. And, yes, using timestamp_utc would give you the results you expected, but for a slightly different reason than you thought. It was kind of a “double negative” situation. :smile:

No idea. But if you search around the forum you’re bound to run into at least 1,237 topics talking about just this issue.

Nope, I totally give up, I don’t have the time nor the inclination to fill this particular black hole of need.
Good Luck

As I said, I totally agree that input_datetime’s timestamp is not a proper one as it lacks some significant information. It’s just a variable’s name, maybe not the best.

Ok, if we leave that “double negative” situation alone (as it does not affect this sensor directly), I take it that the main change to this code should be in using sensor.time instead of sensor.time_utc?

And apparently it should be combined with rectifying time zone issues of my setup.

Thanks for helping me!

Hope I didn’t offend you. And thanks for your input anyway.

Yes. The values you’re getting from the entity attributes are seconds since (local) midnight, so you’d need to use sensor.time to be equivalent to that.

Have you tested it the way it is to make sure it’s changing at the right time, and not offset by the UTC offset?

It would seem to me that if it’s working (using sensor.time_utc), then environment and/or HA doesn’t have the correct time zone setting.

if I get the code right it just assigns the time_val to datetime object (and that object doesn’t get any information about anything else so is pretty much just hour, minute and second) and we can safely compare it with local time.

Yes, I tested the code before posting here and it works without offset.
The funny thing is it works using both time sensors as they give the same results.
In configuration.yaml I have

time_zone: Europe/London

But as you said, it’s better to use local time.

UPDATE: as I said, I’m new to python and didn’t know how exactly strings being compared. Went to read docs and it turned out that the only thing I need to change is my data sources:

{% set from = states.input_datetime.central_heating_preset_night_from.state %}
{% set to = states.input_datetime.central_heating_preset_night_to.state %}
{% set cur = states.sensor.time.state ~ ':00' %}
{{ (cur < to or from <= cur) if from > to else from <= cur < to }}

It does the job. Thanks @Mutt