Time calculations with input_datetime and timedelta

I am calculating the difference in time between now and a date captured in an input_datetime field. That part is working and I’m getting a result like “days_since_new = 1 day, 15:36:00.280033”. I now need to take that and run it through a timedelta (days=x, hours=y, minutes=z) type formula but I can’t get the previous result in a format that works with timedelta. How can I format days_since_new result to use timedelta?

It would help if you would show the templates you are using, but there are a couple methods.

You can chain timedeltas:

{{ (now() + timedelta(days=5)) - states('input_datetime.ztest_time_and_date') | as_datetime | as_local }}

{{ now() - (states('input_datetime.ztest_time_and_date') | as_datetime | as_local - timedelta(days=5)) }}

If, as your post suggests, you are dealing with the result of a previous timedelta that has been returned as a string use as_timedelta:

{% set days_since_new = "1 day, 15:36:00.280033" %}
{{ days_since_new | as_timedelta - timedelta(days=2) }}

Below is the code. I’m trying to calculate the next New Moon date and time. This code may not be as efficient but it was used to help ensure that the time formats worked. The period between new moons is 29 days, 22 minutes and 44 seconds. I’m trying to calculate current time since the last new moon (which is working correctly). Then I’m trying to take that result (1 day, 15:36:00…) and subtract from the 29 days, 22 minutes and 44 seconds to tell me when the next new moon will be. That’s where I’m stumped!


{% set n = now() %}
n = {{n}}

{% set t = states('input_datetime.new_moon_calculated') | as_datetime | as_local %}
t = {{t}}

{% set days_since_new_moon = n-t %}
days_since_new = {{days_since_new_moon}} # this is where the 1 day, 15:36:00 comes in

{% set days_until_next_new_moon = ("***29 days 22 minutes 44 seconds"***) - days_since_new_moon}}

{% set date_of_next_new_moon = %}

{% set prev = states('input_datetime.new_moon_calculated') | as_datetime | as_local %}
{% set period = ("29 days, 00:22:44") | as_timedelta %}
{% set next_new = prev + period %}

Timedelta until next new: {{ next_new - now() }}
Whole days until next new: {{ (next_new - now()).days }}

That is awesome! Thank you for helping navigate through the date and time formatting. I really appreciate that.

One more question. This works great in the template editor and there are no issues creating the sensors but it does not update. With this being a time dependent sensor, how can I force it to update so it always shows current, accurate information?

The template will be rendered at the top of every minute due to the use of the now() function. If you really want to, you can force it to update faster by using a Trigger-based Template Sensor with a Time Pattern Trigger set to your desired interval, but there are very few cases where that would be recommended…

I’m getting unknown for each but no errors. I’ve attached the code for each:


- platform: template
  sensors:
    days_since_last_new_moon:
      friendly_name: "Days Since Last New Moon"
      unit_of_measurement: "Days"
      value_template: >
        {% set x = states('sensor.time') %}
        {% set n = now() %}
        {{n}}
        {% set t = states('input_datetime.new_moon_calculated') | as_datetime | as_local %}
        {{t}}
        {% set days_since_new_moon = n-t %}
        {{days_since_new_moon}}

- platform: template
  sensors:
    time_until_next_new_moon:
      friendly_name: "Time Until Next New Moon"
      unit_of_measurement: "Days"
      value_template: >
        {% set x = states('sensor.time') %}
        {% set n = now() %}
        {{n}}
        {% set t = states('input_datetime.new_moon_calculated') | as_datetime | as_local %}
        {{t}}
        {% set days_since_new_moon = n-t %}
        {% set days_until_next_new_moon =  days_since_new_moon %}
        {% set prev = states('input_datetime.new_moon_calculated') | as_datetime | as_local %}
        {% set period = ("29 days, 00:22:44") | as_timedelta %}
        {% set next_new = prev + period %}
        {{ next_new - now() }}

- platform: template
  sensors:
    time_until_next_new_moon_days:
      friendly_name: "Days Until Next New Moon"
      unit_of_measurement: "Days"
      value_template: >
        {% set x = states('sensor.time') %}
        {% set n = now() %}
        {{n}}
        {% set t = states('input_datetime.new_moon_calculated') | as_datetime | as_local %}
        {{t}}
        {% set days_since_new_moon = n-t %}
        {% set days_until_next_new_moon =  days_since_new_moon %}
        {% set prev = states('input_datetime.new_moon_calculated') | as_datetime | as_local %}
        {% set period = ("29 days, 00:22:44") | as_timedelta %}
        {% set next_new = prev + period %}
        {{ next_new - now().days }}

Sensors with unit_of_measurement will be unknown when the value of the state is non-numeric. You’ve left a lot of extraneous statements and expressions which are fine for the Template editor, but will mess up the output of your sensors.

You need to update the templates to return numbers (not timedelta objects), or get rid of the unit_of_measurement.

- platform: template
  sensors:
    days_since_last_new_moon:
      friendly_name: "Days Since Last New Moon"
      unit_of_measurement: "Days"
      value_template: >
        {% set x = states('sensor.time') %}
        {% set n = now() %}
        {% set t = states('input_datetime.new_moon_calculated') | as_datetime | as_local %}
        {{ (n-t).days }}

- platform: template
  sensors:
    time_until_next_new_moon:
      friendly_name: "Time Until Next New Moon"
      value_template: >
        {% set prev = states('input_datetime.new_moon_calculated') | as_datetime | as_local %}
        {% set period = ("29 days, 00:22:44") | as_timedelta %}
        {% set next_new = prev + period %}
        {{ next_new - now() }}

- platform: template
  sensors:
    time_until_next_new_moon_days:
      friendly_name: "Days Until Next New Moon"
      unit_of_measurement: "Days"
      value_template: >
        {{ (states('sensor.time_until_next_new_moon') | as_timedelta).days }}
1 Like

I can’t thank you enough. You have been a great help in guiding me on the time format but also cleaning up my code. The extraneous statements are my way of checking to be sure I’m getting what I expect with each step and then not cleaning up up afterwards. Thanks for point that out!

Can I impose on a similar question. I’m now trying to calculate elapsed sunlight, total sunlight, etc. I have created the sensors below for sunrise and sunset times using the learnings from above. This works in the template editor but I get unknown an unknown value for each sensor when I create the sensors. Do I need to add something to {{sunrise}} and {{sunset}} for it to be in the proper format to use for the sensor and then to for subsequent calculations?


- platform: template
  sensors:
    sunrise_today:
      friendly_name: "Sun Rise Today"
      unit_of_measurement: 'Time'
      value_template: >-
        {% if is_state("sun.sun", "above_horizon") -%}
        {% set sunrise = as_timestamp(states.sun.sun.last_changed)| as_datetime | as_local %}
        {%- else -%}
        {% set sunrise = as_timestamp(state_attr("sun.sun", "next_rising")) | as_datetime | as_local %}
        {%- endif %}
        {{sunrise}}
      icon_template: mdi:weather-sunset-up

    sunset_today:
      friendly_name: "Sun Set Today"
      unit_of_measurement: 'Time'
      value_template: >-
        {% if is_state("sun.sun", "below_horizon") -%}
        {% set sunset = as_timestamp(states.sun.sun.last_changed)| as_datetime | as_local %}
        {%- else -%}
        {% set sunset = as_timestamp(state_attr("sun.sun", "next_setting")) | as_datetime | as_local %}
        {%- endif %}
        {{sunset}}
      icon_template: mdi:weather-sunset-down

    daylight_time_today:
      friendly_name: 'Daylight Today'
      unit_of_measurement: 'Hours'
      value_template: >-
        {% set daylight = state('sensor.sunset.today') - state('sensor.sunrise_today') %}
        {{daylight}}
      icon_template: mdi:weather-sunny

Personally, I would suggest you use the Sun2 custom integration (available in HACS). All those values are available as sensors through the integration.

The cause of the unknown values is that you have defined unit_of_measurement variables, which, as discussed previously, means the output is expected to be numeric.

Also the construction of your templates is confusing… you are running multiple conversion functions just to end up at the same place you started.

Take the following from your “Sun rise Today” sensor:

as_timestamp (states.sun.sun.last_changed) | as_datetime | as_local

This calculation starts with the last_changed property, which is a datetime object in UTC.
Then that is converted with as_timestamp(), which outputs a UNIX timestamp.
Then the timestamp is converted back to a UTC datetime object with as_datetime.
Finally, it is converted to a local datetime object with as_local.

The same output can be accomplished with a single filter:

states.sun.sun.last_changed | as_local

You need to clarify what the desired outputs for each of your sensors are before we can answer that.

Sun2 does exactly what I need here. Thank you. I obviously need to understand more about the various time and date conversions. That has been the most challenging part of using and learning HA.

Alright.

This seemed to be very helpful, but it doesn’t work for me.

What I need in end the time difference between a set point in time (that’s gonna be the least problem) and the current half-hour.

My experience is extremely limited, but I made a datetime helper “input_datetime.halbestunden” and an automation triggered /30 minutes that does:

service: input_datetime.set_datetime
target:
  entity_id: input_datetime.halbestunden
data:
  timestamp: "{{ now().timestamp() }}"

This part works perfectly.

Now, to try out what I learned here, I added this to my sensors.yaml:


- platform: template
  sensors:
    halbestunden_neu:
      unique_id: noideaifIshouldcensorthat
      value_template: >
        {% set prev = "states('input_datetime.halbestunden')" | as_datetime | as_local %}
        {{ prev - now() }}

- platform: template
  sensors:
    minutes_halbestunden:
      unique_id: noideaifIshouldcensorthat
      unit_of_measurement: "Minutes"
      value_template: >
        {{ (states('sensor.halbestunden_neu') | as_timedelta).minutes }}

I’ve so far tried:

  • changing the input_datetime.halbestunden from “time only” to “Date and Time”,
  • skipping the unit_of_measurement: “Minutes” line in the minutes_halbestunden part,
  • restarting a dozen times,
  • reloading backup and starting from scratch,

all to no effect.

In entities, sensor.halbestunden_neu shows up as unavailable, minutes_halbestunden just doesn’t do anything.

If someone has a better solution: I only need a number that, based on the current time, tells me how many minutes to or from a set time of the day I am.

Ideally, this number is then divided by 30, and rounded town to an integer, while we’re at it; I’m not that far yet.

I really appreciate any help!

(If anyone is interested, what I’m trying to to is described here.

You have extraneous quote marks in halbestunden_neu, so, at best, minutes_halbestunden is trying to convert “unavailable” to a timedelta object. Correct the quotes:

    value_template: >
        {% set prev = states('input_datetime.halbestunden') | as_datetime | as_local %}
        {{ prev - now() }}

Then fix minutes_halbestunden

A timedelta object saves it’s value in seconds and days, minutes is not a valid property. If you need to cumulative minutes you need to to use the total_seconds() method then divide by 60:

{{ (as_timedelta( states('sensor.halbestunden_neu').total_seconds() /60) int(0) }}

You can replace the whole automation > datetime helper > sensor 1 > sensor 2 with:


- platform: template
  sensors:
    minutes_halbestunden:
      unique_id: YouDontNeedToCensorThat
      unit_of_measurement: "Minutes"
      value_template: >
        {% set n = now() %}
        {% set prev = n.replace(minute=0 if n.minute < 30 else 30) %}
        {{ int((prev - n).total_seconds() / 60) }}