Google Apps Script for finding empty days on multiple calendars I need help with a simple Google Apps Script I've written. The script finds days on which no all-day events have been scheduled, across multiple calendars.

I'm fairly certain the main issue is the fact that I'm making an API call to get all owned calendars, then I'm making 5 API calls on each of these calendars to retrieve each day's (Mon - Fri) events.

Currently, it takes about 60 seconds to run on an account with 11 owned calendars. Is there anything that can be done to optimize?

var one_day = 86400000; //24 * 60 * 60 * 1000
var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
var startDay = nextMonday(new Date());

// Script-as-app template.
function doGet() {
  var app = UiApp.createApplication();

  var button = app.createButton('Find Blanks').setId('button');
  var loader = app.createImage('http://preloaders.net/preloaders/496/Flip%20Flop.gif').setVisible(false).setId('loader');
  var blanks = app.createHTML().setId('blanksArea').setHTML('Click button to find calendar blanks for week of ' + startDay);

  app.add(button);
  app.add(app.createHTML('&lt;br/&gt;'));
  app.add(loader);
  app.add(blanks);

  var clientHandler = app.createClientHandler().forEventSource().setEnabled(false).forTargets(loader).setVisible(true).forTargets(blanks).setHTML('Processing for week of ' + startDay);
  var handler = app.createServerHandler('serverHandler');

  button.addClickHandler(clientHandler);
  button.addClickHandler(handler);

  return app;
}

function serverHandler(e) {
  var app = UiApp.getActiveApplication();

  var button = app.getElementById('button');
  var blanks = app.getElementById('blanksArea');
  var calendarsWithBlanks = getCalendarsWithBlanks();

  blanks.setHTML(calendarsWithBlanks.length ? calendarsWithBlanks.join('&lt;br/&gt;') : 'There are no blanks');

  app.getElementById('loader').setVisible(false);
  button.setEnabled(true);

  app.close();
  return app;
}

function getCalendarsWithBlanks()
{
  var calendars = CalendarApp.getAllOwnedCalendars();
  var calendarsWithBlanks = [];
  for(var i = 0; i &lt; calendars.length; i++)
  {
    var calendar = calendars[i];
    var calendarName = calendar.getName();
    var blankDays = checkCalendarForBlanks(calendar, startDay);
    if(blankDays.length)
    {
      calendarsWithBlanks.push(calendarName + ' has ' + blankDays.join(', ') + ' blank')
    }
  }
  return calendarsWithBlanks;
}

function checkCalendarForBlanks(calendar, startDay)
{
  var blankDays = [];
  for(var d = 0; d &lt; 5; d++)
  {
    var currDay = new Date(startDay.getTime() + (d * one_day));
    var events = getEventsForDay(calendar, currDay);
    var foundAllDayEvent = false;
    for(var e = 0; e &lt; events.length; e++)
    {
      if(events[e].isAllDayEvent())
      {
        foundAllDayEvent = true;
        break;
      }
    }
    if(!foundAllDayEvent)
    {
      blankDays.push(days[currDay.getDay()]);
    }
  }
  return blankDays;
}

function nextMonday(date)
{
  var today = date;
  today.setUTCHours(0);
  today.setMinutes(0);
  today.setSeconds(0);
  today.setMilliseconds(0);
  var day = today.getDay();
  var offset = 8 - day
  var next_monday = new Date(today.getFullYear(), today.getMonth(), today.getDate()+offset);
  return next_monday;
}

function getEventsForDay(calendar, date)
{
  /* !!!KLUDGE ALERT!!!
   * This is a kludge. The API method getEventsForDay is wonky: http://stackoverflow.com/questions/11870304/google-apps-script-geteventsforday-returns-invalid-recurring-events
   */

  var startOfDay = new Date(date.getTime());
  startOfDay.setUTCHours(0);
  startOfDay.setMinutes(0);
  startOfDay.setSeconds(0);
  startOfDay.setMilliseconds(0);  
  var endOfDay = new Date(startOfDay.getTime() + one_day);

  return calendar.getEvents(startOfDay, endOfDay)
}
I flipped your logic a bit in the example below.  I only do one API call per calendar and then do all of the processing in the client.  It seems a bit faster for me, but I'm not seeing the 60 second processing time that you are.  So your results my vary.

var one_day = 86400000; //24 * 60 * 60 * 1000
var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
var startDay = nextMonday(new Date());

// Script-as-app template.
function doGet() {
  var app = UiApp.createApplication();

  var button = app.createButton('Find Blanks').setId('button');
  var loader = app.createImage('http://preloaders.net/preloaders/496/Flip%20Flop.gif').setVisible(false).setId('loader');
  var blanks = app.createHTML().setId('blanksArea').setHTML('Click button to find calendar blanks for week of ' + startDay);

  app.add(button);
  app.add(app.createHTML('&lt;br/&gt;'));
  app.add(loader);
  app.add(blanks);

  var clientHandler = app.createClientHandler().forEventSource().setEnabled(false).forTargets(loader).setVisible(true).forTargets(blanks).setHTML('Processing for week of ' + startDay);
  var handler = app.createServerHandler('serverHandler');

  button.addClickHandler(clientHandler);
  button.addClickHandler(handler);

  return app;
}

function serverHandler(e) {
  var app = UiApp.getActiveApplication();

  var button = app.getElementById('button');
  var blanks = app.getElementById('blanksArea');
  var calendarsWithBlanks = getCalendarsWithBlanks();

  blanks.setHTML(calendarsWithBlanks.length ? calendarsWithBlanks.join('&lt;br/&gt;') : 'There are no blanks');

  app.getElementById('loader').setVisible(false);
  button.setEnabled(true);

  app.close();
  return app;
}

function test_getCalendarsWithBlanks() {
  getCalendarsWithBlanks().forEach(function(calendarResult) {
    Logger.log(calendarResult);
  });
 }

function getCalendarsWithBlanks()
{
  var calendars = CalendarApp.getAllOwnedCalendars();
  var calendarsWithBlanks = [];
  for(var i = 0; i &lt; calendars.length; i++)
  {
    var calendar = calendars[i];
    var calendarName = calendar.getName();
    var blankDays = checkCalendarForBlanks(calendar, startDay);
    if(blankDays.length )
    {
      calendarsWithBlanks.push(calendarName + ' has ' + blankDays.join(', ') + ' blank')
    }
  }
  return calendarsWithBlanks;
}

function checkCalendarForBlanks(calendar, startDay)
{
  var blankDays = [],
      allDayEventsPerDay = [0,0,0,0,0,0,0],
      endDate = new Date(startDay.getTime() + (4 * one_day)),
        // get all events for the next work week
      allEventsForNextWorkWeek = calendar.getEvents(startDay, endDate);

  //count number of all day events on each day
  allEventsForNextWorkWeek.forEach(function(event){
    if(event.isAllDayEvent()) {
      //count each day the event is on
      determineDaysOnWhichEventTakesPlace(event,startDay,endDate).forEach(function(eventDate){
        allDayEventsPerDay[eventDate.getDay()]++;  
      });
    }
  });

  //figure out which ones have 0 (ie blank days)
  allDayEventsPerDay.forEach(function(count, offset){
    if(count === 0) {
      blankDays.push(days[offset]);
    }
  });

  return blankDays;
}

function determineDaysOnWhichEventTakesPlace(event, lowerLimit, upperLimit) {
  var startDate = new Date(Math.max(event.getAllDayStartDate().getTime(), lowerLimit.getTime())),
      endDate = new Date(Math.min(event.getAllDayEndDate().getTime(), upperLimit.getTime())),
      dates =[],
      eventDate;

    for(eventDate = startDate; eventDate&lt; endDate; eventDate = new Date(eventDate.getTime() + (one_day))) {
      dates.push(eventDate);
    }
  return dates;
}

function nextMonday(date)
{
  var today = date;
  today.setUTCHours(0);
  today.setMinutes(0);
  today.setSeconds(0);
  today.setMilliseconds(0);
  var day = today.getDay();
  var offset = 8 - day
  var next_monday = new Date(today.getFullYear(), today.getMonth(), today.getDate()+offset);
  return next_monday;
}