Show next day d/m/Y based on odd/even days and weeks

I’ve 7 input_boolean for every day of the week:
Sunday can be true/false
Monday can be true/false
Etc…

I’ve also an input_select where I can specify the week:
Even / odd / all

This system is used for me to configure the wastebin collector.

Actually i’ve a sensor based on those variable that show me if TODAY is wastebin day? Yes / No

I’d like also to add another sensor to show me NEXT DAY FOR WASTE COLLECTOR.

for example, today is 2019-05-09 and i’ll check monday to yes and all week.
Then my sensor must show me: NEXT DAY WASTE BIN COLLECTOR IS 2019-05-13

If i check even week, i must check the number of the week… If I check multiple days i need to consider only the first occur.

Any idea how create this complicated calculations?

1 Like

Man, this was quite the challenge to get correct. I’m still not convinced it will work when crossing december 31st, but it appears to.

First things first. You have to make your even/odd/all input_booleans mutually exclusive. Meaning you cannot have even and all on at the same time. Unfortunately, this is a pain in the ass when dealing with input booleans so I took the liberty to change these to an input_select (which you will hide) and 3 template switches. Those will look like this:

input_select

input_select:
  waste_collection_week:
    name: Waste Collection Week Selector
    options:
      - Even
      - Odd
      - All
      - None
    initial: All
    icon: mdi:calendar

template_switches

switch:
  - platform: template
    switches:
      waste_collection_week_all:
        value_template: "{{ is_state('input_select.waste_collection_week', 'All') }}"
        turn_on:
          service: input_select.select_option
          data:
            entity_id: input_select.waste_collection_week
            option: 'All'
        turn_off:
          service: input_select.select_option
          data:
            entity_id: input_select.waste_collection_week
            option: 'None'
            
      waste_collection_week_even:
        value_template: "{{ is_state('input_select.waste_collection_week', 'Even') }}"
        turn_on:
          service: input_select.select_option
          data:
            entity_id: input_select.waste_collection_week
            option: 'Even'
        turn_off:
          service: input_select.select_option
          data:
            entity_id: input_select.waste_collection_week
            option: 'None'
            
      waste_collection_week_odd:
        value_template: "{{ is_state('input_select.waste_collection_week', 'Odd') }}"
        turn_on:
          service: input_select.select_option
          data:
            entity_id: input_select.waste_collection_week
            option: 'Odd'
        turn_off:
          service: input_select.select_option
          data:
            entity_id: input_select.waste_collection_week
            option: 'None'

Next we need a template sensor that does the calculation. We also need to implement the date_time sensor. We will only need date out of this sensor, so that’s all we will use. We also need a template sensor to do the calculation and display the next date. Now, i’m going to assume that your input_booleans are named the following:

input_boolean.sunday
input_boolean.monday
input_boolean.tuesday
input_boolean.wednesday
input_boolean.thursday
input_boolean.friday
input_boolean.saturday

With those assumptions, your sensor section should have the following platforms

sensor:
  - platform: time_date
    display_options:
      - 'date'

  - platform: template
    sensors:
      next_garbage_collection:
        friendly_name: Next Garbage Collection Date
        entity_id:
          - sensor.date
        value_template: >
          {% set timezone = '-04:00' %}
          {% set mon = is_state('input_boolean.monday','on') %}
          {% set tue = is_state('input_boolean.tuesday','on') %}
          {% set wed = is_state('input_boolean.wednesday','on') %}
          {% set thur = is_state('input_boolean.thursday','on') %}
          {% set fri = is_state('input_boolean.friday','on') %}
          {% set sat = is_state('input_boolean.saturday','on') %}
          {% set sun = is_state('input_boolean.sunday','on') %}
          {% set even = is_state('switch.waste_collection_week_even','on') %}
          {% set odd = is_state('switch.waste_collection_week_odd','on') %}
          {% set all = is_state('switch.waste_collection_week_all','on') %}
          {% set weekday = now().strftime('%w') | int %}
          {% set days = [ (0, sun), (1, mon), (2, tue), (3, wed), (4, thur), (5, fri), (6, sat) ] %}
          {% set days_after = days | selectattr('1','eq',True) | selectattr('0','>', weekday) | list %}
          {% set days_before = days | selectattr('1','eq',True) | selectattr('0','<=', weekday) | list %}
          {% if days_after | length >= 1 %}
            {% set next_day = days_after[0][0] %}
            {% set is_this_week = True %}
          {% elif days_before | length >= 1 %}
            {% set next_day = days_before[0][0] %}
            {% set is_this_week = False %}
          {% else %}
            {% set next_day = None %}
          {% endif %}
          {% if next_day %}
            {% set iso_week = now().isocalendar()[1] %}
            {% if all %}
              {% set diff = 1 %}
            {% elif even %}
              {% set diff = 1 if iso_week % 2 else 2 %}
            {% elif odd %}
              {% set diff = 2 if iso_week % 2 else 1 %}
            {% else %}
              {% set diff = None %}
            {% endif %}
            {% if diff %}
              {% set this_week = now().strftime("%W") | int %}
              {% if not is_this_week %}
                {% set next_week = this_week + diff %}
              {% else %}
                {% if (iso_week % 2 and odd) or (not iso_week % 2 and even ) or all %}
                  {% set next_week = this_week %}
                {% else %}
                  {% set next_week = this_week + diff %}
                {% endif %}
              {% endif %}
              {% set next_week = next_week if next_week <= 52 else next_week-52 %}
              {% set year = now().year + 1 if next_week in [ 1, 2 ] else now().year %}
              {% set ts = '{}-{}-{}T00:00:00'.format(year, next_week, next_day)+timezone %}
              {{ as_timestamp(strptime(ts, '%Y-%U-%wT%H:%M:%S%z')) | timestamp_custom('%d-%m-%Y') }}
            {% else %}
              Undefined
            {% endif %}
          {% else %}
            Undefined
          {% endif %}

You will need to set your correct timezone. In order to do that, simply go to the template editor and paste this line of code and copy the timezone (including the positive or negative sign):

{{ now() }}

paste it into the following line in the template, it’s the first line.

          {% set timezone = '-04:00' %}
paste between quotes -------^------^  including the + or -.

Hi, thanks for the very very very detailed answer!! I’m going to try it this evening!!
Btw yes my even odd and all weeks already was an input select :slight_smile:

I see some commands I never seen before, like selectattr(‘1’,‘eq’,True)… What does this is mean?

It’s complicated. Selectattr allows us to select items based on an attributes value. In this case our object is a list of length 2 tuples. When pulling out information from a tuple, we need to use an index. 0, being the first item, 1 being the second and so on. So the selectattr is iterating the list and looking at the tuple value where index=1. Then selectattr only selects the object, if the value of index 1 equals True.

So a more clear example would be this:

{% set mylist = [
  ('a', False),
  ('b', True),
  ('c', False) ] %}

So if i only want to get a tuple that where the 2nd item is True, I would use the following selectattr

{{ mylist | selectattr('1','eq', True) | list }}

the output would be

[('b', True)]

If i only wanted a list of items that are false:

{{ mylist | selectattr('1','eq', False) | list }}

the output would be

[('a', False), ('c', False) ]

If i only wanted letters equal to ‘a’:

{{ mylist | selectattr('0','eq', 'a') | list }}

the output would be

[('a', False)]

Now you can get real fancy with select attr. What if i wanted ‘a’ and ‘b’, regardless of the True/False?

{{ mylist | selectattr('0','in', ['a','b']) | list }}

the output would be

[('a', False), ('b', True)]

Now you can expand upon this with all sorts of data types. I just chose a list of tuples because it’s the easiest to get a pair of information, like a day number and True/False flag.

This is my data structure

{% set days = [ (0, sun), (1, mon), (2, tue), (3, wed), (4, thur), (5, fri), (6, sat) ] %}

This filters days after today that are on.

{% set days_after = days | selectattr('1','eq',True) | selectattr('0','>', weekday) | list %}

This filters out days before today including today that are on

{% set days_before = days | selectattr('1','eq',True) | selectattr('0','<=', weekday) | list %}

Hi finally I tried your code. I need to modify this 2 lines to (I removed the bold text timezone):
+timezone
%z

{% set ts = '{}-{}-{}T00:00:00'.format(year, next_week, next_day) %}
{{ as_timestamp(strptime(ts, '%Y-%U-%wT%H:%M:%S')) | timestamp_custom('%d-%m-%Y') }}

And it work great. with timezone I got always NONE… do you know why?

what version of home assistant are you on?

I’m actually using 0.92.1

what timezone were you using? numerically?

The function now() response with +2

so you used '+02:00' exactly as written?

Yes i’m using +02:00