Trigger for the last day of the month

Found this thread - it looks its still an issue to find exactly last day of a month.

I created a template sensor:

last_day_of_month:
      value_template: >-
        {{ 
        31 if now().month in (1,3,5,7,8,10,12) else 
          30 if now().month in (4,6,9,11) else 
            29 if now().month == 2 and now().year % 4 == 0 else 28
        }}

and then in trigger compare:

    condition:
      - condition: template
        value_template: "{{ now().day == states('sensor.last_day_of_month') }}"

one sensor value can be used for many triggers.

1 Like

No, this binary sensor worked, Trigger for the last day of the month - #6 by Arsenal10108

And it does not require a comparison template wherever you want to use it, just a state condition or trigger.

Like tom_I said, it was solved in post #6 a year ago.

A year is a long time in Home Assistant’s evolution. Here is the same binary_sensor but in the new ‘modern’ format with a refactored template that takes advantage of the timedelta function.

# Last Day of the Month Binary Sensor
template:
  - binary_sensor:
      - name: Last Day of the Month
        icon: mdi:calendar
        state: "{{ (now() + timedelta(days=1)).day == 1 }}"
11 Likes

This will break on 2100-02-29.

3 Likes

Right :slight_smile: For immortals it will be:

      value_template: >-
        {{ 
        31 if now().month in (1,3,5,7,8,10,12) else 
          30 if now().month in (4,6,9,11) else 
            29 if now().month == 2 and now().year % 4 == 0 and (now().year % 100 != 0 or now().year % 400 == 0) else 28
        }}

Expression {{ (now() + timedelta(days=1)).day == 1 }} looks clean and nice if you just need True or False. Thanks!

{% set early_next_month = strptime("%d-%02d" % (now().year, now().month),"%Y-%m") + timedelta(days=31) %}
{% set first_of_next_month = strptime("%d-%02d" % (early_next_month.year, early_next_month.month),"%Y-%m") %}
{{ (first_of_next_month - timedelta(days=1)).day }}

Works over year boundaries, and provided the time libraries are maintained, will cope with the change I’ll put in when I’m King that leap years are every four, except when the year divides by 128, which makes for much quicker computation (convert to binary, leap if ends in 00, except if ends in 0000000).

Future proofing at its finest :rofl:

1 Like

I did say “in binary”. 2048 is the next exception :nerd_face:

Here’s another version:

{{ ((now().replace(day=1)+timedelta(days=32)).replace(day=1)-timedelta(days=1)).day }}

Gets the first of the current month, adds 32 days to get into the next month, gets the first of that month, subtracts a day and returns the date number.

1 Like

The original goal of this topic was to trigger on the last day of the month. It amounts to knowing if the current day is or is not the last day. True/False is sufficient to trigger the automation and so that’s why it’s a Template Binary Sensor.

However, if there is a need for a Template Sensor to report the numeric value of the month’s final day, here’s something I adapted from the python code in this post:

# Last Day of the Month Sensor
template:
  - sensor:
      - name: Last Day of the Month
        icon: mdi:calendar
        state: "{{ 31 if now().month == 12 else (now().replace(month=now().month+1, day=1) - timedelta(days=1)).day }}"
4 Likes

Is it possible to get in a timestamp format? Like
2023-02-28?

{{ ((now().replace(day=1)+timedelta(days=32)).replace(day=1)-timedelta(days=1)).date() }}
2 Likes

Is it possible to get tow sensor with timestamp for the first and last day of this week. For this week I search

sensor 1 : 2023-02-13
sensor 2: 2023-02-19

And is it possible to add a time for sensor 2. Like this
2023-02-19 23:59:00

I want to add a time to the sensor is it possible? Like

2023-02-28 23:59:00

I think I found a way. Maybe not the best but I get the value that I want. My goal is to use the template value to start and end the calculation of a average value. Is it possible to add a fixed time after the date? Like I have done below.

  - platform: average
    name: 'avg_nordpool_month'
    entities:
      - sensor.avg_nordpool_day
    start: '{{ now().date().replace(day=now().day - now().day+1 ) }} 00:01:00'
    end: '{{ ((now().replace(day=1)+timedelta(days=32)).replace(day=1)-timedelta(days=1)).date() }} 23:59:00'
  - platform: average
    name: 'avg_nordpool_week'
    entities:
      - sensor.avg_nordpool_day
    start: '{{ now().date().replace(day=now().day - now().isoweekday()+1 ) }} 00:01:00'
    end: '{{ now().date().replace(day=now().day+ 7 - now().isoweekday() ) }} 23:59:00'

UPDATE
Get this error after a reboot

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 548, in async_update_ha_state
    await self.async_device_update()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 746, in async_device_update
    raise exc
  File "/config/custom_components/average/sensor.py", line 290, in async_update
    await self._async_update_state()
  File "/config/custom_components/average/sensor.py", line 413, in _async_update_state
    await self._async_update_period()
  File "/config/custom_components/average/sensor.py", line 368, in _async_update_period
    if start > now:
TypeError: can't compare offset-naive and offset-aware datetimes
{{ (now()+timedelta(days=6-now().weekday())).replace(hour=23,minute=59,second=0,microsecond=0) }}

will give you a datetime object that is 23:59 on the last day of the current week.

1 Like

Last day of the month simple template:

"{{(now() + timedelta(days=1)).strftime('%-d') == '1' }}"

and this is my template for an end-of-month trigger at 23:59:

"{{ now().hour == 23 and now().minute == 59 and (now() + timedelta(days=1)).strftime('%-d') == '1' }}"

1 Like

Nice solution to the original question, before we digressed onto “what is the last day of the month”. Even shorter version of the same idea:

{{ (now() + timedelta(days=1)).day == 1 }}
1 Like

Yes, even better!

1 Like

Very nice! Thanks!

Super helpful thread, I combined a time pattern with the date check as follows:

trigger:
  - platform: time_pattern
    hours: "9"
    minutes: "30"
condition:
  - condition: template
    value_template: "{{ (now() + timedelta(days=1)).day == 1 }}"

This uses the condition template to determine day of month, and the time pattern to control the time of the day for the triggering. This is slightly easier to check as I can manually fire the automation and check everything except the time trigger.