7
\$\begingroup\$

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('<br/>'));
  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('<br/>') : '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 < 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 < 5; d++)
  {
    var currDay = new Date(startDay.getTime() + (d * one_day));
    var events = getEventsForDay(calendar, currDay);
    var foundAllDayEvent = false;
    for(var e = 0; e < 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)
}
\$\endgroup\$
3
\$\begingroup\$

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('<br/>'));
  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('<br/>') : '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 < 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< 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;
}
\$\endgroup\$
  • \$\begingroup\$ This is definitely faster, but something has gone hinky and it's now reporting false positives (blanks are reported where they don't exist). In any case, you've solved the performance issue. Thanks! \$\endgroup\$ – Marcela Jun 2 '14 at 16:05
  • 2
    \$\begingroup\$ There could be a bit of wonkiness in checkCalendarForBlanks() with respect to how start and end dates are defined by all day events. Perhaps adding the total number of seconds per day isn't the way to go. Maybe you need to explicitly build date objects for each day and then do a comparison. \$\endgroup\$ – pgreen2 Jun 2 '14 at 16:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.