Automatically remove all-day events from calendars

Intro

All-day events in a Calendar can be annoying since they’ll cover up any non-all-day events happening that day. Here’s a way to use Google Apps Script to make a copy of your Google calendar, but with the all-day events scrubbed out. It’s pretty annoying to set-up a script, but once you do, it’ll run automatically.

Set up Google calendar and get the calendar IDs

You’ll need to create a new Google calendar for the scrubbed events to be copied to. Go to calendar.google.com. Under “Other Calendars” near the bottom left, press the + button and choose “create new calendar”. Name it something like “Homeassistant Scrubbed Calendar” and press the “Create Calendar” button.

On the left of the main calendar.google.com page, you’ll see a list of calendars under “My Calendars”. Hover over the one you want, and on the three-dot-menu that pops up, choose “Settings and sharing”. On this screen, scroll all the way down to the heading “Integrate Calendar”. The first option there is “Calendar ID”. Copy the whole thing down, including the “@group.calendar.google.com” part.

Copy down the IDs for your google calendar that you want to copy from, and the new calendar you created where the non-all-day events will be copied to.

Create the script

In Google Drive, click the “+ New” button on the top left corner, and click New > More > Google Apps Script. This takes you to a new untitled project on script.google.com

Click on the “Untitled Project” header at the top, and rename it “Calendar Scrubber Script”.

First we need to add the Google Calendar service to the project. Under “services” in the left menu, press the “+” button to add a service. Select “Google Calendar API”, choose v3, and hit Add.

In the code editor, delete the default code, and paste the code snippet into the code.gs file. Click the little floppy disc icon on top to save the code.

var SOURCE_CALENDAR_ID = 'primary'; // OR the full calendar id including @something.calendar.google.com
var SCRUBBED_CALENDAR_ID = '[email protected]';
var WEEKS_IN_ADVANCE = 1;

// The maximum script run time under Apps Script Pro is 30 minutes; this setting
// will be used to report when the script is about to reach that limit.
var MAX_PRO_RUNTIME_MS = 29 * 60 * 1000;

/**
 * Look through the source calendar and copy all non-all-day events to the scrubbed calendar
 */
function syncCalendarAfterScrubbing() {
  // Define the calendar event date range to search.
  var today = new Date();
  var futureDate = new Date();
  futureDate.setDate(futureDate.getDate() + WEEKS_IN_ADVANCE*7);
  var lastRun = PropertiesService.getScriptProperties().getProperty('lastRun');
  lastRun = lastRun ? new Date(lastRun) : null;

  if (isTimeUp(today, new Date())) {
    Logger.log('Execution time about to hit quota limit; execution stopped.');
    return;
  }

  // find events that are not all-day in the specified date range. 
  // Import each of those to the scrubbed calendar.
  var count = 0;
  var events = findEvents(SOURCE_CALENDAR_ID, today, futureDate, lastRun);
  events.forEach(function(event) {
    event.organizer = {
          id: SCRUBBED_CALENDAR_ID
        }
    event.attendees = [];
    Logger.log('Importing: %s', event.summary);
    try {
      Calendar.Events.import(event, SCRUBBED_CALENDAR_ID);
      count++;
    } catch (e) {
      Logger.log(
            'Error attempting to import event: %s. Skipping.', e.toString());
    }
  });
  
  PropertiesService.getScriptProperties().setProperty('lastRun', today);
  Logger.log('Imported ' + count + ' events');

  var executionTime = ((new Date()).getTime() - today.getTime()) / 1000.0;
  Logger.log('Total execution time (s) : ' + executionTime); ;
}

/**
 * In a given calendar, look for non-all-day events
 * in events within the specified date range and return any such events
 * found.
 * @param {string} cal_id the ID of the source calendar
 * @param {Date} start the starting Date of the range to examine.
 * @param {Date} end the ending Date of the range to examine.
 * @param {Date} opt_since a Date indicating the last time this script was run.
 * @return {object[]} an array of calendar event Objects.
 */
function findEvents(cal_id, start, end, opt_since) {
  var params = {
    timeMin: formatDate(start),
    timeMax: formatDate(end),
    showDeleted: true
  };
  if (opt_since) {
    // This prevents the script from examining events that have not been
    // modified since the specified date (that is, the last time the
    // script was run).

    params['updatedMin'] = formatDate(opt_since);
  }
  var results = [];
  try {
    var response = Calendar.Events.list(cal_id, params);
    results = response.items.filter(function(item) {
      // Filter out events where the the event is all-day
      if (item.start.date) {
        return false;
      }
      return true;
    });
  } catch (e) {
    Logger.log('Error retriving events for %s; skipping',
        e.toString());
    results = [];
  }
  return results;
}

/**
 * Return an RFC3339 formated date String corresponding to the given
 * Date object.
 * @param {Date} date a Date.
 * @return {string} a formatted date string.
 */
function formatDate(date) {
  return Utilities.formatDate(date, 'UTC', 'yyyy-MM-dd\'T\'HH:mm:ssZ');
}

/**
 * Compares two Date objects and returns true if the difference
 * between them is more than the maximum specified run time.
 *
 * @param {Date} start the first Date object.
 * @param {Date} now the (later) Date object.
 * @return {boolean} true if the time difference is greater than
 *     MAX_PROP_RUNTIME_MS (in milliseconds).
 */
function isTimeUp(start, now) {
  return now.getTime() - start.getTime() > MAX_PRO_RUNTIME_MS;
}

Add your calendar IDs

Go to your script and paste the calendar IDs into the top part of the script and hit the save icon. SOURCE_CALENDAR_ID is the original calendar with all of your events (including all-day events), and SCRUBBED_CALENDAR_ID is the new calendar you created to hold the scrubbed list of events.

If your source calendar is the default calendar on your google account (named “Your Name” in the list, and the calendar id is just [email protected]), you can leave the ID in the script as ‘primary’.

Authorize the script to run

Now we need to authorize the script. Make sure the “syncCalendarAfterScrubbing” function is selected in the dropdown, and hit the “Run” button.

You’ll see an “authorization required” dialog. Hit the “Review permissions” button. You’ll see a google account chooser, choose the account where your script lives. Then you’ll see a “Google hasn’t verified this app” page. Hit the small grey “advanced” button. From there you’ll see another small grey button that says “Go to Calendar Scrubber Script (unsafe)”. Click that one. From there, you’ll see a page saying “Calendar scrubber script wants to access your Google Account”. Click on the “Allow” button.

Run the script manually

Now that you’ve authorized the script to run, you should end up back on the script page. Hit the “run” button again to actually run it. You’ll see a log pop up, with all the events that get imported. If you go back to calendar.google.com and look at your calendar, you’ll see that the new “scrubbed” calendar only has the non-all-day-events copied from the original calendar.

NOTE: If you run this script again, it will only choose events that were modified since you last ran the script. Don’t be worried if you don’t see the older events in the logs if you run it a second time.

Run the script automatically

Now let’s set up an Apps Script trigger so that this can run every day. On the left of the Apps Script page, there’s an alarm clock icon. Click on that to open up the triggers menu.

Click on the “+ Add Trigger” button in the bottom right corner. Use the following options:
Choose which function to run: syncCalendarAfterScrubbing
Choose which deployment should run: Head
Select event source: Time-driven
Select type of time based trigger: Day timer
Select time of day: Midnight to 1am
Scroll to the bottom of that dialog and hit the blue “Save” button.

Closing

That’s it! You can now go into HomeAssistant and use your new “Homeassistant Scrubbed Calendar” to get your events without any of those pesky all-day events covering them up.

6 Likes

Thanks for this write-up. I wasn’t aware of being able to write scripts on the G-Drive. I don’t need to remove all-day events but I learned about a new tool…thanks

Pretty cool idea. Personally, I would just create a 2nd calendar for all day events and not sync that one with HA.

This was wonderful. Thank you very much. I’ve been hitting a concrete wall to have a simple “flashing lights 10 minutes before meeting starts” automation because of the full day events and this fixed it.
:heartpulse:

I know this is late… but may be useful.
You can create a trigger for the script to run when a calendar event is added or amended (only works with primary calendar)

In the Google script editor, expand the menu on the left and select ‘Triggers’ then click 'Add Trigger at the bottom right. Choose your ‘syncCalendarAfterScrubbing’ as the function to run, leave the deployment as ‘Head’. From calendar as the event source and 'Calendar updated as the event details. Finally add your email address. (for failure notifications)

Then every time an event is added, amended or deleted in the primary calendar your ‘scrubbed’ calendar is updated.

Fantastic workup on this. it is one of those things that people are frustrated with (me) and have to just deal with. Thank you so much for sharing this and saving me the frustration :slight_smile:

Thanks for this script! It’s been superful. The one thing I’m wondering is will this will remove events in the scrubbed calendar that have also been removed from the source calendar? Thanks!

Thank you for this script, this helped me a ton! I have only one small thing… The calendar scrub script only looks to 1 week ahead, which can be frustrating as it doesn’t show events further in the future.

I tried to change this by changing that variable from 1 to 4 but it still dient pick up on events longer then a week into the future. Not sure why…

Hey all! Just like so many others have said, thank you to @hyperbole for this amazing script! I get invited to a lot of optional, all-company events in my job so I also needed to add some logic to filter out events that I haven’t accepted. Thought I’d share the code block if anyone else was interested. This is a pull&replace for the try block at line 76-ish:

try {
    var response = Calendar.Events.list(cal_id, params);
    results = response.items.filter(function(item) {
    // Filter out events where the owner of the script has not accepted 
      if (item.attendees) {
        for (person of item.attendees) {
          if (person['self'] == true && person['responseStatus'] != "accepted") {
            return false;
          }
        }
      }
      // Filter out events where the event is all-day
      if (item.start.date) {
        return false;
      }
      return true;
    });

@FGOD1983 - I just tried changing weeks in advance to greater than 1 and noticed similar behavior to you. It is because of the opt_since section, line 69-75. If you change the weeks in advance and then comment out this section, you can run it again and it to pull in all events:

  if (opt_since) {
    // This prevents the script from examining events that have not been
    // modified since the specified date (that is, the last time the
    // script was run).

    params['updatedMin'] = formatDate(opt_since);
  }

This section was just to prevent the script from pulling in events that haven’t been changed. You could leave it commented out (or removed completely) if you don’t have a crazy number of events so it just overwrites all the events on the scrubbed calendar, but it was a nice addition from OP to reduce processing time and power of the script. I hope that helps!

Oh damn, i totally missed this, thx for this! Will see what happens if I keep it commented out, and if that causes issues I at least know what to do when I have this issue. It was indeed very good that OP has included this. It is always good scripting to avoid unnecessary processing :slight_smile:

1 Like

Seems it is not too bad doing it with this part commented out :slight_smile:

1:21:19 PM	Info	Imported 25 events
1:21:19 PM	Info	Total execution time (s) : 10.921

Yep, @FGOD1983 ! This was my experience too - with it commented out it didn’t take a terribly long time or have any other mishaps. Glad it worked for you too!

1 Like

thank you for this! works perfectly for me :slight_smile:

Hopefully the google calendar integration will get an update eventually that will prevent “All Day” events overriding the attributes for current events :crossed_fingers: