Google Calendar event sensor with offset using input number

Hello.

I am trying to create a binary sensor that turns on and off depending on the offset entered in input_number for a Google Calendar event.

input_number:
  event_offset:
    name: Event Offset
    min: 0
    max: 120
    step: 5
    mode: slider
    unit_of_measurement: minutes

Above is the input number with slide.
And below is the binary sensor template

binary_sensor:
 - platform: template
    sensors:
      event_is_active:
        friendly_name: 'event is active'
        value_template: >-
          {% set offset = states('input_number.event_offset') | int * 60 %}
          {% set now = as_timestamp(now()) %}
          {% for event in states.calendar.my_calendar.attributes.all %}
            {% if event %}
              {% set start_time = as_timestamp(strptime(event.start_time, '%Y-%m-%d %H:%M:%S')) %}
              {% set end_time = as_timestamp(strptime(event.end_time, '%Y-%m-%d %H:%M:%S')) %}
              {% if start_time - now < offset and end_time - now > -offset %}
                true
              {% else %}
                false
              {% endif %}
            {% endif %}
          {% endfor %}
        icon_template: mdi:calendar-alert

But this doesn’t seems to be working.
Will someone please correct this?

Thank you.

If I understand the goal of your time math, you want the binary sensor to be “on” during the time period from x minutes before the event starts to x minutes after the event ends. Where x is the value of your input number helper. That can be done much simpler. However, you should be aware that this might fail to behave as you expect it to if you have overlapping events, especially all-day events, in the same calendar.

Unless you have a good reason for not doing so, you should be using the current format instead of the the legacy format for template sensors.

template:
  - binary_sensor:
      - name:  Event is Active
        icon: mdi:calendar-alert
        state: >
          {% set offset = timedelta(minutes= states('input_number.event_offset') | int(0) ) %}
          {% set event = 'calendar.my_calendar' %}
          {% set start_time = state_attr(event, 'start_time') | as_datetime | as_local %}
          {% set end_time = state_attr(event, 'end_time') | as_datetime | as_local %}
          {{ start_time - offset < now() < end_time + offset }}

Don’t rely on a calendar entity’s attributes. They aren’t guaranteed to represent the next scheduled event (notably if there’s an all-day event, because it supersedes all others, or concurrently scheduled events).

Thank you for your correct interpretation.
I just have used your code and it worked exactly as I wanted it to.

Thank you so much for your help.

Thank you for your advice. :slight_smile:

You’re welcome. It’s something I learned from the software developer who maintains the Google Calendar and Local Calendar integrations as well as the Calendar Trigger.

Initially I was using a Calendar entity’s attributes in conditions but then encountered the situation where it would only report an all-day event even though there were also hourly events scheduled on the same day.

The developer explained that’s just one example of why I should not rely on the entity’s attributes in a condition. The recommendation was to use a Calendar Trigger and then check the trigger variable’s properties.

1 Like

I tried the following code based on the topic, just to start with the event start.

- trigger:
      - platform: calendar
        event: start
        entity_id: calendar.my_calender
        offset:
          minutes: "{{ states('input_number.event_offset')|int * 1 }}"
    action:
        - service: input_boolean.turn_on
          data: {}
          target:
            entity_id: input_boolean.event

It seems not working, will you please point where I’m getting wrong?

Thank you. :slight_smile:

Check your Log because it probably contains an error message related to this automation. Based on the following test, a Calendar Trigger’s offset doesn’t currently support templates.

In addition, the Calendar Trigger’s documentation doesn’t mention, or show any examples, that its offset supports templates.

Hello,
I checked the log and I see the same error that you got in your test.
And I also see an error with the template code above even though it is working.

template:
  - binary_sensor:
      - name:  Event is Active
        icon: mdi:calendar-alert
        state: >
          {% set offset = timedelta(minutes= states('input_number.event_offset') | int(0) ) %}
          {% set event = 'calendar.my_calendar' %}
          {% set start_time = state_attr(event, 'start_time') | as_datetime | as_local %}
          {% set end_time = state_attr(event, 'end_time') | as_datetime | as_local %}
          {{ start_time - offset < now() < end_time + offset }}

for this code, I get
TemplateError(‘TypeError: float() argument must be a string or a real number, not ‘NoneType’’)

I run ChatGPT to check this code and it rewrote as it follows,

- sensor:
- name: "Event is Active"
  icon: mdi:calendar-alert
  state: >
    {% set now = now() %}
    {% set offset = states('input_number.event_offset')|int %}
    {% set event_start = as_timestamp(states.calendar.my_calendar.attributes.start_time) %}
    {% set event_end = as_timestamp(states.calendar.my_calendar.attributes.end_time) %}
    {% set event_start_with_offset = event_start - (offset * 60) %}
    {% set event_end_with_offset = event_end + (offset * 60) %}
    {% if event_start_with_offset <= now.timestamp() <= event_end_with_offset %}
      on
    {% else %}
      off
    {% endif %}

and it seems working without an error.

It will report an incorrect result when any of these situations occur:

  • On any day containing scheduled events and an All-day event.
  • On any day containing more than one event scheduled for the same time.

ChatGPT doesn’t know that but the author of the calendar integration does and that’s why it’s not recommended to employ a calendar entity’s attributes in a condition.

What would be the best way to create this sensor for now?