Skip to content

Instantly share code, notes, and snippets.

@bruxy70
Last active January 8, 2024 11:45
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bruxy70/b55acd9d69de4b8d8880f63e6dcb44cd to your computer and use it in GitHub Desktop.
Save bruxy70/b55acd9d69de4b8d8880f63e6dcb44cd to your computer and use it in GitHub Desktop.
blueprint:
name: Create Calendar Events with Offset Holidays in Week
description: Copy Events (in the next 365 days) from one local calendar to another. Look
in the holidays calendar and shift events for each public holiday in the week
before the event.
domain: script
source_url: https://gist.github.com/bruxy70/b55acd9d69de4b8d8880f63e6dcb44cd
input: {}
fields:
source_calendar:
name: Source calendar
description: Calendar with the events to be copied from
required: true
selector:
entity:
filter:
integration: local_calendar
destination_calendar:
name: Destination calendar
description: Calendar for the events to be copied to
required: true
selector:
entity:
filter:
integration: local_calendar
public_holidays:
name: Public Holidays
description: Calendar with the public holidays to be avoided
required: true
selector:
entity:
filter:
integration: holidays
sequence:
- service: calendar.get_events
data:
duration:
days: 365
target:
entity_id: '{{ source_calendar }}'
response_variable: source
- service: calendar.get_events
data:
duration:
days: 365
target:
entity_id: '{{ destination_calendar }}'
response_variable: destination
- service: calendar.get_events
data:
duration:
days: 365
target:
entity_id: '{{ public_holidays }}'
response_variable: holidays
- variables:
holiday_dates: |-
{%- set ns = namespace(dates={}) %}
{%- if holidays[public_holidays]["events"] %}
{%- for event in holidays[public_holidays]["events"] %}
{%- set ns.dates = dict(ns.dates, **{event.start:event.summary}) %}
{%- endfor %}
{%- endif %}
{{ ns.dates}}
destination_dates: |-
{%- set ns = namespace(dates={}) %}
{%- if destination[destination_calendar]["events"]%}
{%- for event in destination[destination_calendar]["events"] %}
{%- set ns.dates = dict(ns.dates, **{event.start:event.summary}) %}
{%- endfor %}
{%- endif %}
{{ ns.dates}}
- repeat:
for_each: '{{ source[source_calendar]["events"] }}'
sequence:
- variables:
offset: >-
{%- set ns = namespace(offset=0,in_week=False,found=False) %}
{%- set collection_date = strptime(repeat.item.start,"%Y-%m-%d").date() %}
{%- for i in range(collection_date.weekday()+1) %}
{%- set d = ( collection_date + timedelta( days=-i) ).isoformat() %}
{%- if d in holiday_dates %}
{%- set ns.in_week = true %}
{%- set ns.offset = ns.offset + 1 %}
{%- endif %}
{%- endfor %}
{%- if ns.in_week %}
{# Increase offset until we find a date that is not public holiday #}
{%- for _ in range(7) if not ns.found %}
{%- set start = strptime(repeat.item.start,"%Y-%m-%d").date() %}
{%- set d = ( start + timedelta( days=ns.offset) ).isoformat() %}
{%- if d in holiday_dates %}
{%- set ns.offset = ns.offset + 1 %}
{% else %}
{%- set ns.found = true %}
{%- endif %}
{% endfor %}
{%- endif %}
{{ ns.offset }}
start_date: >-
{%- set start = strptime(repeat.item.start,"%Y-%m-%d").date() %}
{{ (start + timedelta(days=offset)).isoformat() }}
end_date: >-
{%- set end = strptime(repeat.item.end,"%Y-%m-%d").date() %}
{{ (end + timedelta(days=offset)).isoformat() }}
summary: "{{ repeat.item.summary }}"
- if:
- condition: template
value_template: "{{ start_date not in destination_dates }}"
then:
- service: calendar.create_event
data:
summary: "{{ summary }}"
start_date: "{{ start_date }}"
end_date: " {{ end_date }}"
target:
entity_id: "{{ destination_calendar }}"
mode: single
icon: mdi:calendar-blank-multiple
@gavinpbennett
Copy link

@bruxy70
I am getting the following error when running this:
Logger: homeassistant.components.script.calendar_copy_events_and_offset_holidays_in_week
Source: helpers/script.py:420
Integration: Script (documentation, issues)
First occurred: 13:13:33 (2 occurrences)
Last logged: 13:13:33

Calendar - Copy events & offset holidays in week: Repeat at step 5: Error executing script. Error rendering template for variables at pos 1: ValueError: Template error: strptime got invalid input '2023-07-13T07:00:00+12:00' when rendering template '{%- set ns = namespace(offset=0,in_week=False,found=False) %} {%- set collection_date = strptime(repeat.item.start,"%Y-%m-%d").date() %} {%- for i in range(collection_date.weekday()+1) %} {%- set d = ( collection_date + timedelta( days=-i) ).isoformat() %} {%- if d in holiday_dates %} {%- set ns.in_week = true %} {%- set ns.offset = ns.offset + 1 %} {%- endif %} {%- endfor %} {%- if ns.in_week %} {# Increase offset until we find a date that is not public holiday #} {%- for _ in range(7) if not ns.found %} {%- set start = strptime(repeat.item.start,"%Y-%m-%d").date() %} {%- set d = ( start + timedelta( days=ns.offset) ).isoformat() %} {%- if d in holiday_dates %} {%- set ns.offset = ns.offset + 1 %} {% else %} {%- set ns.found = true %} {%- endif %} {% endfor %} {%- endif %} {{ ns.offset }}' but no default was specified
Calendar - Copy events & offset holidays in week: Error executing script. Error rendering template for repeat at pos 5: ValueError: Template error: strptime got invalid input '2023-07-13T07:00:00+12:00' when rendering template '{%- set ns = namespace(offset=0,in_week=False,found=False) %} {%- set collection_date = strptime(repeat.item.start,"%Y-%m-%d").date() %} {%- for i in range(collection_date.weekday()+1) %} {%- set d = ( collection_date + timedelta( days=-i) ).isoformat() %} {%- if d in holiday_dates %} {%- set ns.in_week = true %} {%- set ns.offset = ns.offset + 1 %} {%- endif %} {%- endfor %} {%- if ns.in_week %} {# Increase offset until we find a date that is not public holiday #} {%- for _ in range(7) if not ns.found %} {%- set start = strptime(repeat.item.start,"%Y-%m-%d").date() %} {%- set d = ( start + timedelta( days=ns.offset) ).isoformat() %} {%- if d in holiday_dates %} {%- set ns.offset = ns.offset + 1 %} {% else %} {%- set ns.found = true %} {%- endif %} {% endfor %} {%- endif %} {{ ns.offset }}' but no default was specified

@gavinpbennett
Copy link

@bruxy70
It appears that when I changed my calendar events to All Day events, the script works fine. I had events set at specific times, which appears to cause the above issue.
Thanks again for this great work 👍

@bruxy70
Copy link
Author

bruxy70 commented Jul 6, 2023

Thanks.

@lambros414
Copy link

Hi. I love your blueprint and am using it to load a holiday calendar to shift my trash day. I have it set up to run on the first of the year (which it just did), and I just got an error message:

  Detected use of deprecated service `calendar.list_events`
  Use calendar.get_events instead which supports multiple entities.

  Please replace this service and adjust your automations and scripts and select submit to close this issue.

I'm going to look to see if I can fix the blueprint for my own use, but wanted to put it out there so if you are still managing the blueprint you may want to address

@bruxy70
Copy link
Author

bruxy70 commented Jan 2, 2024

Hi. I have never used this actually. If you figure it out, feel free to send the corrected version, so I can fix it for the otherts.
Also, Holidays is becoming part of Home Assistant core in January, so there will be no point maintaining a custom integration with the same functionality. So it might be worth updating the scripts to work with the native integration.

@lambros414
Copy link

I was able to modify the blueprint to get it to work, but it does require a hack that I am not happy with.

I replaced all list_event calls with get_event calls.

I then changed lines of code as follows:

  • variables:
    holiday_dates: |-
    {%- set ns = namespace(dates={}) %}
    {%- if holidays["calendar.us_holidays"]["events"] %}
    {%- for event in holidays["calendar.us_holidays"]["events"] %}
    {%- set ns.dates = dict(ns.dates, **{event.start:event.summary}) %}
    {%- endfor %}
    {%- endif %}
    {{ ns.dates}}
    destination_dates: |-
    {%- set ns = namespace(dates={}) %}
    {%- if destination["calendar.trash_days_offset_for_holidays"]["events"]%}
    {%- for event in destination["calendar.trash_days_offset_for_holidays"]["events"] %}
    {%- set ns.dates = dict(ns.dates, **{event.start:event.summary}) %}
    {%- endfor %}
    {%- endif %}
    {{ ns.dates}}
  • repeat:
    for_each: '{{ source["calendar.trash_days"]["events"] }}'

in general, instead of say source.event, you now need to use: source["calendar.trash_days"]["events"]

The hack I used is I hard coded the calendar name in the blueprint (like above "calendar.trash_days". there should be a way to use the passed variable: "{{ source_calendar }}", "{{ destination_calendar }}" and "{{ public_holidays }}" which translated to the hardcoded calendars I used, but try as I might, I could not properly modify the code to work.

bruxy70, feel free to use the above to modify your blueprint if you wish, and if you obviously know how to use the proper variables, vs the hard coded values, that would make it a true blueprint again.

@lambros414
Copy link

lambros414 commented Jan 3, 2024

Okay. sorry, totally fixed. I learned that I was nesting templates.

the proper solutions for above is to do:

{%- for event in holidays[public_holidays]["events"] %}

{%- for event in destination[destination_calendar]["events"] %}

and

for_each: '{{ source[source_calendar]["events"] }}'

The full code which appears to be working now is below:
template above

`

blueprint:
  name: Create Calendar Events with Offset Holidays in Week
  description: Copy Events (in the next 365 days) from one local calendar to another.  Look
    in the holidays calendar and shift events for each public holiday  in the week
    before the event.
  domain: script
  source_url: https://gist.github.com/bruxy70/b55acd9d69de4b8d8880f63e6dcb44cd
  input: {}
fields:
  source_calendar:
    name: Source calendar
    description: Calendar with the events to be copied from
    required: true
    selector:
      entity:
        filter:
          integration: local_calendar
  destination_calendar:
    name: Destination calendar
    description: Calendar for the events to be copied to
    required: true
    selector:
      entity:
        filter:
          integration: local_calendar
  public_holidays:
    name: Public Holidays
    description: Calendar with the public holidays to be avoided
    required: true
    selector:
      entity:
        filter:
          integration: holidays
sequence:
- service: calendar.get_events
  data:
    duration:
      days: 365
  target:
    entity_id: '{{ source_calendar }}'
  response_variable: source
- service: calendar.get_events
  data:
    duration:
      days: 365
  target:
    entity_id: '{{ destination_calendar }}'
  response_variable: destination
- service: calendar.get_events
  data:
    duration:
      days: 365
  target:
    entity_id: '{{ public_holidays }}'
  response_variable: holidays
- variables:
    holiday_dates: |-
      {%- set ns = namespace(dates={}) %}
      {%- if holidays[public_holidays]["events"] %}
        {%- for event in holidays[public_holidays]["events"] %}
          {%- set ns.dates = dict(ns.dates, **{event.start:event.summary}) %}
        {%- endfor %}
      {%- endif %}
      {{ ns.dates}}
    destination_dates: |-
      {%- set ns = namespace(dates={}) %}
      {%- if destination[destination_calendar]["events"]%}
        {%- for event in destination[destination_calendar]["events"] %}
          {%- set ns.dates = dict(ns.dates, **{event.start:event.summary}) %}
        {%- endfor %}
      {%- endif %}
      {{ ns.dates}}
- repeat:
    for_each: '{{ source[source_calendar]["events"] }}'
    sequence:
        - variables:
            offset: >-
              {%- set ns = namespace(offset=0,in_week=False,found=False) %}
              {%- set collection_date = strptime(repeat.item.start,"%Y-%m-%d").date() %}
              {%- for i in range(collection_date.weekday()+1) %}
                {%- set d = ( collection_date + timedelta( days=-i) ).isoformat() %}
                {%- if d in holiday_dates %}
                  {%- set ns.in_week = true %}
                  {%- set ns.offset = ns.offset + 1 %}
                {%- endif %}
              {%- endfor %}
              {%- if ns.in_week %}
                {# Increase offset until we find a date that is not public holiday #}
                {%- for _ in range(7) if not ns.found %}
                  {%- set start = strptime(repeat.item.start,"%Y-%m-%d").date() %}
                  {%- set d = ( start + timedelta( days=ns.offset) ).isoformat() %}
                  {%- if d in holiday_dates %}
                    {%- set ns.offset = ns.offset + 1 %}
                  {% else %}
                    {%- set ns.found = true %}
                  {%- endif %}
                {% endfor %}               
              {%- endif %} 
              {{ ns.offset }}
            start_date: >-
              {%- set start = strptime(repeat.item.start,"%Y-%m-%d").date() %}
              {{ (start + timedelta(days=offset)).isoformat() }}
            end_date: >-
              {%- set end = strptime(repeat.item.end,"%Y-%m-%d").date() %}
              {{ (end + timedelta(days=offset)).isoformat() }}
            summary: "{{ repeat.item.summary }}"
        - if:
            - condition: template
              value_template: "{{ start_date not in destination_dates }}"
          then:
            - service: calendar.create_event
              data:
                summary: "{{ summary }}"
                start_date: "{{ start_date }}"
                end_date: " {{ end_date }}"
              target:
                entity_id: "{{ destination_calendar }}"
mode: single
icon: mdi:calendar-blank-multiple

`

@bruxy70
Copy link
Author

bruxy70 commented Jan 8, 2024

Thanks for this contribution, I have updated the blueprint to this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment