Fuzzy time variables: early evening, late evening, night

I have completed, tested, and am running a set of time sensors that let me do various automations based on the current length of day, length of night, evening hours, etc. This approach will self adjust to the seasonal lengthening/shortening of the day/night cycle.

I feel like this would be useful for the community, but don’t know where to post them. Can someone tell me if where they would best be posted? Maybe as examples in the documentation?

In these I have used the following assumptions (easily modified) to achieve the “fuzzy time”:

  1. Evening is the time between sunset and “night”
  2. “Night” is the next sunrise (following day) minus 8 hours - this is basically sleep hours
  3. Evening is split in halves - early evening and late evening

I use these to allow a group of brighter lighting during early evening and dimmer during late evening. Also I have outside lights on during evening and turn them off at bed time (“night”). It then turns on the night lights.

This will also be useful for holiday lights.

3 Likes

Just post them here. Sounds useful.

No problem.

# Time to sunrise - will adjust for "rollover" at the time of sunrise
# in other words, not affected by the current day vs next sunrise day
      hrs_to_sunrise:
        friendly_name: "Hours to Sunrise"
        value_template: "{{ ((as_timestamp(state_attr('sun.sun','next_rising')) - as_timestamp(now()))/3600) | round(3) }}"

# Calculate the length of night from next sunset to next sunrise. This will possibly be a few seconds 
# off for the current day due to the  difference in sunset between two days, but for most purposes
# this will be "lost in the round-off".
# first the if statement looks at the day for the next sunrise and the next sunset. If they are not the same, it must be before sunset
# So take the next rising and subtract the next setting. 
# Otherwise they are the same day so do the same calculation but subtract 24 hrs to adjust the day.
      hrs_of_night:
        friendly_name: "Hours of night"
        value_template: "{% if (as_timestamp(state_attr('sun.sun','next_setting')) > (as_timestamp(state_attr('sun.sun','next_rising')))) -%}
              {{ (((as_timestamp(state_attr('sun.sun','next_rising'))  - as_timestamp(state_attr('sun.sun','next_setting')))/3600) + 24) | round(3) }}
        {%- else -%}
              {{ ((as_timestamp(state_attr('sun.sun','next_rising'))  - as_timestamp(state_attr('sun.sun','next_setting')))/3600) | round(3) }}
        {%- endif %}"

# By extension the hours of daylight is 24 minus night
      hrs_of_daylight:
        friendly_name: "Hours of daylight"
        value_template: "{{ 24 - (states('sensor.hrs_of_night') | float) }}"

# Use 8 hrs until sunrise as "Night" flag and calculate mid-point of sunset until night as "late_eve"
# This will be in hours, not a specific time since the ending time is the same as the start of late_eve
# and the starting time is sunset.
      early_eve:
        friendly_name: "Hours of early evening"
        value_template: "{{ (((states('sensor.hrs_of_night') | float) - 8) / 2) | round(3) }}"
# Time of next "start of late evening". This is used as a value trigger in automations.
      late_eve_time:
        friendly_name: "Time of late evening"
        value_template: "{{ ((as_timestamp(states.sun.sun.attributes.next_setting) | float ) + ((states('sensor.early_eve')) | float) * 3600) | timestamp_custom('%m/%d %I:%M %p') }}"
# Time of next "start of night". This is used as a value trigger in automations.
      night_time:
        friendly_name: "Time of bedtime"
        value_template: "{{ ((as_timestamp(states.sun.sun.attributes.next_setting) | float ) + ((states('sensor.early_eve')) | float) * 7200) | timestamp_custom('%m/%d %I:%M %p') }}"

The above sensor code works fine, but is a bit less than pretty. If there is a better way to accomplish the same thing or to code in a more Home Assistant format, please add any helpful suggestions.

I also modified a couple of the stock template sensors to have a date-time format I prefer:

        friendly_name: 'Next Sunrise'
        value_template: "{{ as_timestamp(states.sun.sun.attributes.next_rising) | timestamp_custom('%m/%d %I:%M %p') }}"

      nextsunset:
        friendly_name: 'Next Sunset'
        value_template: "{{ as_timestamp(states.sun.sun.attributes.next_setting) | timestamp_custom('%m/%d %I:%M %p') }}"```

I’d have to say that github is the best place to post code. But that doesn’t detract from my thanks for this, great stuff.

Are you aware of schedy (a scheduler)? Might be worth checking out.

1 Like

The best category for stuff like this is to put it in the “share your projects” category.

I fixed it for you.

Definitely good stuff! thanks!

1 Like

Is there any reason why you didn’t make a single sensor with a state that changes?

I.E.

sensor.time_of_day

and the states are

night, early eve, late eve, day

No reason. I wish I could say there was some grand overriding plan, but it was that these functions (sensors) just evolved with my needs and existing automations. Plus, since I wanted the values to be dependent on sunset and sunrise I needed to develop some intermediate values to get to the end result.

I also did not want to mess with my existing automations much because I am not running a test instance plus a production one. Some of these expressions have weird “logic traps” due to having different values for sunset twice a day and sunrise twice a day. Testing it piecemeal was just easier for me.

If I were doing it in Python, I would have more likely done it the way you suggest. However I am a total noob with jinja2 syntax and it was much easier to test each calculation as I developed the various independent pieces.

Thanks for fixing the category finity!

No I wasn’t. I will look at it further. Thanks nickrout!

I think you replied to the wrong person on that one. :wink:

and you’re welcome. :slightly_smiling_face:

Sorry petro, I apparently replied to finity instead of you. If you get this look at the overall thread for your reply.
:man_facepalming:

if it’s of any use for you, have a look at this example to create 1 sensor with period_of_day:

      period_of_day:
        friendly_name: 'Period of the day'
        value_template: >
          {% if (as_timestamp(state_attr('sun.sun','next_dusk'))) - 
                (as_timestamp(state_attr('sun.sun','next_setting'))) < 0 %}
            Dusk
          {% elif (as_timestamp(state_attr('sun.sun','next_rising'))) - 
                  (as_timestamp(state_attr('sun.sun','next_dawn'))) < 0 %}
            Dawn
          {% elif (state_attr('sun.sun', 'elevation')) < 0 %}
            Night
          {% else %}
            Day
          {% endif %}
        icon_template: >
          {% set period = states('sensor.period_of_day') %}
          {% if period == 'Dusk' %} mdi:weather-sunset-down
          {% elif period == 'Dawn' %} mdi:weather-sunset-up
          {% elif period == 'Night' %}  mdi:weather-night
          {% else %} mdi:weather-sunny
          {% endif %}
1 Like

And there is always the core ‘Times of the Day Binary Sensor’ (https://www.home-assistant.io/components/tod/) usefully called platform: tod

Assuming of course you can get your head around the syntax - Times of the Day Binary Sensor

Does anyone actually use it?

not here, I’ve been using these template sensors (have a few other versions) and never understood why this tod sensor got accepted in the first place, especially considering the efforts @pnbruckner did to get his enhanced sun component accepted and was rejected/ignored, and that is of true added value to core HA

Couldn’t agree more.

Anything useful? :wink:

bit more of the same, collected various templating options here and there:

      day_phase:
        friendly_name: 'Day Phase'
        entity_id: sensor.time
        value_template: >
          {% if now() > now().replace(hour=5).replace(minute=0).replace(second=0) and
            now() < now().replace(hour=7).replace(minute=0).replace(second=0) %}
              Morning
          {% elif states('sun.sun') == 'above_horizon' or
            now() > now().replace(hour=5).replace(minute=0).replace(second=0) and
            now() < now().replace(hour=12).replace(minute=0).replace(second=0) %}
              Day
          {% elif now() < now().replace(hour=21).replace(minute=45).replace(second=0) and
            now() > now().replace(hour=5).replace(minute=0).replace(second=0) %}
              Evening
          {% else %}
              Night
          {% endif %}
        icon_template: >
          {% set phase = states('sensor.day_phase') %}
          {% if phase == 'Morning' %} mdi:weather-sunset-up
          {% elif phase =='Day' %} mdi:weather-sunny
          {% elif phase == 'Evening' %} mdi:weather-sunset-down
          {% else %} mdi:weather-night
          {% endif %}

      part_of_day:
        friendly_name: 'Part of Day'
        entity_id: sensor.time
        value_template: >
          {% if now().hour in range(0, 6) %} Midnight
          {% elif now().hour in range(6, 12) %} Morning
          {% elif now().hour in range(12, 18) %} Afternoon
          {% else %} Evening
          {% endif %}
        icon_template: >
          {% set pod = states('sensor.part_of_day') %}
          {% if pod == 'Midnight' %} mdi:weather-night
          {% elif pod == 'Morning' %} mdi:weather-sunset-up
          {% elif pod == 'Afternoon' %} mdi:weather-sunny
          {% else %} mdi:weather-sunset-down
          {% endif %}

kinda like the Dusk til Dawn sensor best :wink:

2 Likes

Ignore this post! I have a strange copy/paste issue with my computer ; ! Apologies to @Mariusthvdb

Your code has a typo at the beginning of part_of_day - it needs one space indentation and something else in icon_template but fixed it by chance so I don’t know… This works in my sensors.yaml. Thanks!
Deleted Code to avoid people using the wrong one

well, though this post is well over a year old, it is still correct… dont know what you mean by the one space indentation? the original seems completely fine as it is. Also the icon_template is without an error…

only thing you could do is take out the quotes on the friendly_name, they are not needed :wink:

Yeah, to me it looks like a copy/paste error on the part of @lkeays.

Your code is correct. His has an extra space before both “day_phase:” and “part_of_day:”.

The day_phase Template Sensor can also be written like this:

        value_template: >
          {% set t = states('sensor.time') %}
          {% if '05:00' < t < '07:00' %}
              Morning
          {% elif states('sun.sun') == 'above_horizon' or
                  '05:00' < t < '07:00' %}
              Day
          {% elif '05:00' < t < '21:45' %}
              Evening
          {% else %}
              Night
          {% endif %}

or like this:

        value_template: >
          {% set t = states('sensor.time') %}
          {% if 5 < now().hour < 7 %}
              Morning
          {% elif states('sun.sun') == 'above_horizon' or
                  5 < now().hour < 7 %}
              Day
          {% elif '05:00' < t < '21:45' %}
              Evening
          {% else %}
              Night
          {% endif %}

FWIW, I like the way the part_of_day sensor checks if the current hour is within a specified range:

now().hour in range(0, 6)

It’s the same as the following but seems neater.

0 <= now().hour < 6