Populate a Team Vacation Calendar

The Vacation Calendar script presented here allows a user in a domain to automatically populate a team vacation calendar. The script goes through each user in the domain, examines their primary calendar for the next three months, and imports discovered "vacation" events into a specified team calendar. "Vacation" events are defined as those having certain keywords in the event summary. The script will automatically update modified and deleted events when run, and can be executed via a time-based trigger to keep the team calendar in sync.


Before you can install and run this sample, you will need the following:

  1. A Google Workspace domain with API access enabled. Users in this domain should have Google Calendar enabled, and should have events in the primary calendar that the domain account that is running the script can view.
  2. A domain account in that domain from which to run the script. This account does not need to be an administrator account.
  3. Access to a target Google Calendar to import events into. The domain account must have permission to edit this calendar.

You will also need to know the ID of the target calendar. You can find this ID by:

  1. Opening Google Calendar in a web browser, using the domain account.
  2. Click the gear icon on the right, and select Settings.
  3. On the new screen, click Calendars at the top. Find the target calendar you want to use, then click its name.
  4. The Calendar ID will be listed under Calendar Address on this page.

Step 1: Create the script

  1. Open Google Drive in your web browser, using the domain account.
  2. Click New > More > Google Apps Script. If Google Apps Script isn't listed, you can install it by clicking the Connect more apps option instead. Search for "Google Apps Script" and click the +CONNECT button. Click the OK button in the popup, click the X icon to close the dialog, and repeat the step above.
  3. If you are presented with a welcome screen, click Blank Project.
  4. Replace the contents of the script editor with the following code:

    var KEYWORDS = ['vacation', 'ooo', 'out of office'];
    var MONTHS_IN_ADVANCE = 3;
    // 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 domain users' public calendars and add any
     * 'vacation' or 'out of office' events to the team calendar.
    function syncTeamVacationCalendar() {
      // Define the calendar event date range to search.
      var today = new Date();
      var futureDate = new Date();
      futureDate.setMonth(futureDate.getMonth() + MONTHS_IN_ADVANCE);
      var lastRun = PropertiesService.getScriptProperties().getProperty('lastRun');
      lastRun = lastRun ? new Date(lastRun) : null;
      // Get the list of users in the domain.
      var users = getDomainUsers();
      // For each user, find events having one or more of the keywords in the event
      // summary in the specified date range. Import each of those to the team
      // calendar.
      var count = 0;
      var timeout = false;
      for (var i = 0; i < users.length; i++) {
        if (isTimeUp(today, new Date())) {
          timeout = true;
        var user = users[i];
        var username = user.split('@')[0];
        KEYWORDS.forEach(function(keyword) {
          var events = findEvents(user, keyword, today, futureDate, lastRun);
          events.forEach(function(event) {
            event.summary = '[' + username + '] ' + event.summary;
            event.organizer = {
              id: TEAM_CALENDAR_ID
            event.attendees = [];
            Logger.log('Importing: %s', event.summary);
            try {
              Calendar.Events.import(event, TEAM_CALENDAR_ID);
            } catch (e) {
                    'Error attempting to import event: %s. Skipping.', e.toString());
      PropertiesService.getScriptProperties().setProperty('lastRun', today);
      Logger.log('Imported ' + count + ' events');
      if (timeout) {
        Logger.log('Execution time about to hit quota limit; execution stopped.');
      var executionTime = ((new Date()).getTime() - today.getTime()) / 1000.0;
      Logger.log('Total execution time (s) : ' + executionTime); ;
     * In a given user's calendar, look for occurrences of the given keyword
     * in events within the specified date range and return any such events
     * found.
     * @param {string} user the user's primary email String.
     * @param {string} keyword the keyword String to look for.
     * @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(user, keyword, start, end, opt_since) {
      var params = {
        q: keyword,
        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(user, params);
        results = response.items.filter(function(item) {
          // Filter out events where the keyword did not appear in the summary
          // (that is, the keyword appeared in a different field, and are thus
          // is not likely to be relevant).
          if (item.summary.toLowerCase().indexOf(keyword) < 0) {
            return false;
          // If the event was created by someone other than the user, only include
          // it if the user has marked it as 'accepted'.
          if (item.organizer && item.organizer.email != user) {
            if (!item.attendees) {
              return false;
            var matching = item.attendees.filter(function(attendee) {
              return attendee.self;
            return matching.length > 0 && matching[0].status == 'accepted';
          return true;
      } catch (e) {
        Logger.log('Error retriving events for %s, %s: %s; skipping',
            user, keyword, e.toString());
        results = [];
      return results;
     * Return a list of the primary emails of users in this domain.
     * @return {string[]} An array of user email strings.
    function getDomainUsers() {
      var pageToken;
      var page;
      var userEmails = [];
      do {
        page = AdminDirectory.Users.list({
          customer: 'my_customer',
          orderBy: 'givenName',
          maxResults: 100,
          pageToken: pageToken,
          viewType: 'domain_public'
        var users = page.users;
        if (users) {
          userEmails = userEmails.concat(users.map(function(user) {
            return user.primaryEmail;
        } else {
          Logger.log('No domain users found.');
        pageToken = page.nextPageToken;
      } while (pageToken);
      return userEmails;
     * 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;
  5. Replace the ENTER_TEAM_CALENDAR_ID_HERE placeholder with the ID of the calendar you wish to import events into.

  6. Click File > Save, name your project "Vacation Calendar", and click OK.

Step 2: Enable the APIs

This sample uses two of the Apps Script Advanced Services, so you will need to enable both of them in the Apps Script editor and Developers Console:

  1. Click Resources > Advanced Google Services.
  2. In the dialog that appears, find and click the on/off switch for the Admin Directory API and Calendar API.
  3. At the bottom of the dialog, click the link for the Google Developers Console.
  4. In the console, click into the filter box and type Calendar API, then click the name once you see it.
  5. On the next screen, click Enable API.
  6. In the left sidebar, click APIs.
  7. Again, click into the filter box and type Admin SDK, then click the name.
  8. On the next script, click Enable API.
  9. Close the Developers Console and return to the script editor. Click OK in the dialog.

Step 3: Run the sample

In the Apps Script editor, click Run > syncTeamVacationCalendar.

The first time you run the sample, it will prompt you to authorize access:

  1. Click the Continue button.
  2. Click the Accept button.

To view the script's output, click View > Logs. You should also be able to see the imported events appear in the target calendar as the script is running.

If the domain has a large number of users (or the users have a large number of events), the script may take some time to finish. The script will stop itself prior to hitting the execution time limit in order to log the work it has completed. Subsequent executions of the script will avoid importing events that have not been modified since the last time the script was run.

Further reading