Populate a team vacation calendar

Coding level: Beginner
Duration: 15 min
Project type: Automation with a time-driven trigger

Objectives

  • Understand what the solution does.
  • Understand what the Apps Script services do within the solution.
  • Set up the script.
  • Run the script.

About this solution

A shared vacation calendar is a great tool for helping your team collaborate; anyone can determine who's out of the office at a glance. This solution lets you see when your colleagues are out of the office, with no manual entry required.

vacation calendar example

How it works

This solution populates a shared vacation calendar based on the individual calendars of each person in a Google Group. When someone books time off, they add an event to their personal Google Calendar using a keyword like "Vacation" or "Out of office."

Every hour, the script scans the calendars of the members in the group and syncs appropriate events to the shared calendar. You can change how often the script scans for new events .

This solution only accesses Calendar events that your colleagues have made visible to you via their privacy settings.

Apps Script services

This solution uses the following services:

Prerequisites

To use this sample, you need the following prerequisites:

  • A Google Account (Google Workspace accounts might require administrator approval).
  • A web browser with access to the internet.

Set up the script

Create a team vacation calendar

  1. Open Google Calendar .
  2. Create a new calendar called "Team Vacations."
  3. In the calendar's settings, under Integrate calendar, copy the Calendar ID.

Create the Apps Script project

  1. Click the following button to open the Vacation CalendarApps Script project.
    Open the project
  2. Click Overview .
  3. On the overview page, click Make a copyThe icon for making a copy.
  4. In your copied Apps Script project, set the variable TEAM_CALENDAR_ID to the ID of the calendar you created earlier.
  5. Set the variable GROUP_EMAIL to the email address of a Google Group containing your team members.
  6. Next to Services, click Add a service .
  7. Select Google Calendar APIand click Add.

Run the script

  1. In your copied Apps Script project, in the function dropdown, select setup.
  2. Click Run.
  3. When prompted, authorize the script. If the OAuth consent screen displays the warning, This app isn't verified, continue by selecting Advanced > Go to {Project Name} (unsafe).

  4. When complete, return to Calendar to confirm the Team Vacations calendar is populated with events.

Review the code

To review the Apps Script code for this solution, click View source codebelow:

View source code

Code.gs

solutions/automations/vacation-calendar/Code.js
 // To learn how to use this script, refer to the documentation: 
 // https://developers.google.com/apps-script/samples/automations/vacation-calendar 
 /* 
 Copyright 2022 Google LLC 
 Licensed under the Apache License, Version 2.0 (the "License"); 
 you may not use this file except in compliance with the License. 
 You may obtain a copy of the License at 
 https://www.apache.org/licenses/LICENSE-2.0 
 Unless required by applicable law or agreed to in writing, software 
 distributed under the License is distributed on an "AS IS" BASIS, 
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 See the License for the specific language governing permissions and 
 limitations under the License. 
 */ 
 // Set the ID of the team calendar to add events to. You can find the calendar's 
 // ID on the settings page. 
 let 
  
 TEAM_CALENDAR_ID 
  
 = 
  
 'ENTER_TEAM_CALENDAR_ID_HERE' 
 ; 
 // Set the email address of the Google Group that contains everyone in the team. 
 // Ensure the group has less than 500 members to avoid timeouts. 
 // Change to an array in order to add indirect members frrm multiple groups, for example: 
 // let GROUP_EMAIL = ['ENTER_GOOGLE_GROUP_EMAIL_HERE', 'ENTER_ANOTHER_GOOGLE_GROUP_EMAIL_HERE']; 
 let 
  
 GROUP_EMAIL 
  
 = 
  
 'ENTER_GOOGLE_GROUP_EMAIL_HERE' 
 ; 
 let 
  
 ONLY_DIRECT_MEMBERS 
  
 = 
  
 false 
 ; 
 let 
  
 KEYWORDS 
  
 = 
  
 [ 
 'vacation' 
 , 
  
 'ooo' 
 , 
  
 'out of office' 
 , 
  
 'offline' 
 ]; 
 let 
  
 MONTHS_IN_ADVANCE 
  
 = 
  
 3 
 ; 
 /** 
 * Sets up the script to run automatically every hour. 
 */ 
 function 
  
 setup 
 () 
  
 { 
  
 let 
  
 triggers 
  
 = 
  
 ScriptApp 
 . 
 getProjectTriggers 
 (); 
  
 if 
  
 ( 
 triggers 
 . 
 length 
 > 
 0 
 ) 
  
 { 
  
 throw 
  
 new 
  
 Error 
 ( 
 'Triggers are already setup.' 
 ); 
  
 } 
  
 ScriptApp 
 . 
 newTrigger 
 ( 
 'sync' 
 ). 
 timeBased 
 (). 
 everyHours 
 ( 
 1 
 ). 
 create 
 (); 
  
 // Runs the first sync immediately. 
  
 sync 
 (); 
 } 
 /** 
 * Looks through the group members' public calendars and adds any 
 * 'vacation' or 'out of office' events to the team calendar. 
 */ 
 function 
  
 sync 
 () 
  
 { 
  
 // Defines the calendar event date range to search. 
  
 let 
  
 today 
  
 = 
  
 new 
  
 Date 
 (); 
  
 let 
  
 maxDate 
  
 = 
  
 new 
  
 Date 
 (); 
  
 maxDate 
 . 
 setMonth 
 ( 
 maxDate 
 . 
 getMonth 
 () 
  
 + 
  
 MONTHS_IN_ADVANCE 
 ); 
  
 // Determines the time the the script was last run. 
  
 let 
  
 lastRun 
  
 = 
  
 PropertiesService 
 . 
 getScriptProperties 
 (). 
 getProperty 
 ( 
 'lastRun' 
 ); 
  
 lastRun 
  
 = 
  
 lastRun 
  
 ? 
  
 new 
  
 Date 
 ( 
 lastRun 
 ) 
  
 : 
  
 null 
 ; 
  
 // Gets the list of users in the Google Group. 
  
 let 
  
 users 
  
 = 
  
 getAllMembers 
 ( 
 GROUP_EMAIL 
 ); 
  
 if 
  
 ( 
 ONLY_DIRECT_MEMBERS 
 ){ 
  
 users 
  
 = 
  
 GroupsApp 
 . 
 getGroupByEmail 
 ( 
 GROUP_EMAIL 
 ). 
 getUsers 
 (); 
  
 } 
  
 else 
  
 if 
  
 ( 
 Array 
 . 
 isArray 
 ( 
 GROUP_EMAIL 
 )) 
  
 { 
  
 users 
  
 = 
  
 getUsersFromGroups 
 ( 
 GROUP_EMAIL 
 ); 
  
 } 
  
 // For each user, finds events having one or more of the keywords in the event 
  
 // summary in the specified date range. Imports each of those to the team 
  
 // calendar. 
  
 let 
  
 count 
  
 = 
  
 0 
 ; 
  
 users 
 . 
 forEach 
 ( 
 function 
 ( 
 user 
 ) 
  
 { 
  
 let 
  
 username 
  
 = 
  
 user 
 . 
 getEmail 
 (). 
 split 
 ( 
 '@' 
 )[ 
 0 
 ]; 
  
 KEYWORDS 
 . 
 forEach 
 ( 
 function 
 ( 
 keyword 
 ) 
  
 { 
  
 let 
  
 events 
  
 = 
  
 findEvents 
 ( 
 user 
 , 
  
 keyword 
 , 
  
 today 
 , 
  
 maxDate 
 , 
  
 lastRun 
 ); 
  
 events 
 . 
 forEach 
 ( 
 function 
 ( 
 event 
 ) 
  
 { 
  
 importEvent 
 ( 
 username 
 , 
  
 event 
 ); 
  
 count 
 ++ 
 ; 
  
 }); 
  
 // End foreach event. 
  
 }); 
  
 // End foreach keyword. 
  
 }); 
  
 // End foreach user. 
  
 PropertiesService 
 . 
 getScriptProperties 
 (). 
 setProperty 
 ( 
 'lastRun' 
 , 
  
 today 
 ); 
  
 console 
 . 
 log 
 ( 
 'Imported ' 
  
 + 
  
 count 
  
 + 
  
 ' events' 
 ); 
 } 
 /** 
 * Imports the given event from the user's calendar into the shared team 
 * calendar. 
 * @param {string} username The team member that is attending the event. 
 * @param {Calendar.Event} event The event to import. 
 */ 
 function 
  
 importEvent 
 ( 
 username 
 , 
  
 event 
 ) 
  
 { 
  
 event 
 . 
 summary 
  
 = 
  
 '[' 
  
 + 
  
 username 
  
 + 
  
 '] ' 
  
 + 
  
 event 
 . 
 summary 
 ; 
  
 event 
 . 
 organizer 
  
 = 
  
 { 
  
 id 
 : 
  
 TEAM_CALENDAR_ID 
 , 
  
 }; 
  
 event 
 . 
 attendees 
  
 = 
  
 []; 
  
 // If the event is not of type 'default', it can't be imported, so it needs 
  
 // to be changed. 
  
 if 
  
 ( 
 event 
 . 
 eventType 
  
 != 
  
 'default' 
 ) 
  
 { 
  
 event 
 . 
 eventType 
  
 = 
  
 'default' 
 ; 
  
 delete 
  
 event 
 . 
 outOfOfficeProperties 
 ; 
  
 delete 
  
 event 
 . 
 focusTimeProperties 
 ; 
  
 } 
  
 console 
 . 
 log 
 ( 
 'Importing: %s' 
 , 
  
 event 
 . 
 summary 
 ); 
  
 try 
  
 { 
  
 Calendar 
 . 
 Events 
 . 
 import 
 ( 
 event 
 , 
  
 TEAM_CALENDAR_ID 
 ); 
  
 } 
  
 catch 
  
 ( 
 e 
 ) 
  
 { 
  
 console 
 . 
 error 
 ( 
 'Error attempting to import event: %s. Skipping.' 
 , 
  
 e 
 . 
 toString 
 ()); 
  
 } 
 } 
 /** 
 * In a given user's calendar, looks for occurrences of the given keyword 
 * in events within the specified date range and returns any such events 
 * found. 
 * @param {Session.User} user The user to retrieve events for. 
 * @param {string} keyword The keyword 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} optSince A date indicating the last time this script was run. 
 * @return {Calendar.Event[]} An array of calendar events. 
 */ 
 function 
  
 findEvents 
 ( 
 user 
 , 
  
 keyword 
 , 
  
 start 
 , 
  
 end 
 , 
  
 optSince 
 ) 
  
 { 
  
 let 
  
 params 
  
 = 
  
 { 
  
 q 
 : 
  
 keyword 
 , 
  
 timeMin 
 : 
  
 formatDateAsRFC3339 
 ( 
 start 
 ), 
  
 timeMax 
 : 
  
 formatDateAsRFC3339 
 ( 
 end 
 ), 
  
 showDeleted 
 : 
  
 true 
 , 
  
 }; 
  
 if 
  
 ( 
 optSince 
 ) 
  
 { 
  
 // 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 
  
 = 
  
 formatDateAsRFC3339 
 ( 
 optSince 
 ); 
  
 } 
  
 let 
  
 pageToken 
  
 = 
  
 null 
 ; 
  
 let 
  
 events 
  
 = 
  
 []; 
  
 do 
  
 { 
  
 params 
 . 
 pageToken 
  
 = 
  
 pageToken 
 ; 
  
 let 
  
 response 
 ; 
  
 try 
  
 { 
  
 response 
  
 = 
  
 Calendar 
 . 
 Events 
 . 
 list 
 ( 
 user 
 . 
 getEmail 
 (), 
  
 params 
 ); 
  
 } 
  
 catch 
  
 ( 
 e 
 ) 
  
 { 
  
 console 
 . 
 error 
 ( 
 'Error retriving events for %s, %s: %s; skipping' 
 , 
  
 user 
 , 
  
 keyword 
 , 
  
 e 
 . 
 toString 
 ()); 
  
 continue 
 ; 
  
 } 
  
 events 
  
 = 
  
 events 
 . 
 concat 
 ( 
 response 
 . 
 items 
 . 
 filter 
 ( 
 function 
 ( 
 item 
 ) 
  
 { 
  
 return 
  
 shouldImportEvent 
 ( 
 user 
 , 
  
 keyword 
 , 
  
 item 
 ); 
  
 })); 
  
 pageToken 
  
 = 
  
 response 
 . 
 nextPageToken 
 ; 
  
 } 
  
 while 
  
 ( 
 pageToken 
 ); 
  
 return 
  
 events 
 ; 
 } 
 /** 
 * Determines if the given event should be imported into the shared team 
 * calendar. 
 * @param {Session.User} user The user that is attending the event. 
 * @param {string} keyword The keyword being searched for. 
 * @param {Calendar.Event} event The event being considered. 
 * @return {boolean} True if the event should be imported. 
 */ 
 function 
  
 shouldImportEvent 
 ( 
 user 
 , 
  
 keyword 
 , 
  
 event 
 ) 
  
 { 
  
 // Filters 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 
  
 ( 
 event 
 . 
 summary 
 . 
 toLowerCase 
 (). 
 indexOf 
 ( 
 keyword 
 ) 
 < 
 0 
 ) 
  
 { 
  
 return 
  
 false 
 ; 
  
 } 
  
 if 
  
 ( 
 ! 
 event 
 . 
 organizer 
  
 || 
  
 event 
 . 
 organizer 
 . 
 email 
  
 == 
  
 user 
 . 
 getEmail 
 ()) 
  
 { 
  
 // If the user is the creator of the event, always imports it. 
  
 return 
  
 true 
 ; 
  
 } 
  
 // Only imports events the user has accepted. 
  
 if 
  
 ( 
 ! 
 event 
 . 
 attendees 
 ) 
  
 return 
  
 false 
 ; 
  
 let 
  
 matching 
  
 = 
  
 event 
 . 
 attendees 
 . 
 filter 
 ( 
 function 
 ( 
 attendee 
 ) 
  
 { 
  
 return 
  
 attendee 
 . 
 self 
 ; 
  
 }); 
  
 return 
  
 matching 
 . 
 length 
 > 
 0 
 && 
 matching 
 [ 
 0 
 ]. 
 responseStatus 
  
 == 
  
 'accepted' 
 ; 
 } 
 /** 
 * Returns an RFC3339 formated date String corresponding to the given 
 * Date object. 
 * @param {Date} date a Date. 
 * @return {string} a formatted date string. 
 */ 
 function 
  
 formatDateAsRFC3339 
 ( 
 date 
 ) 
  
 { 
  
 return 
  
 Utilities 
 . 
 formatDate 
 ( 
 date 
 , 
  
 'UTC' 
 , 
  
 'yyyy-MM-dd\'T\'HH:mm:ssZ' 
 ); 
 } 
 /** 
 * Get both direct and indirect members (and delete duplicates). 
 * @param {string} the e-mail address of the group. 
 * @return {object} direct and indirect members. 
 */ 
 function 
  
 getAllMembers 
 ( 
 groupEmail 
 ) 
  
 { 
  
 var 
  
 group 
  
 = 
  
 GroupsApp 
 . 
 getGroupByEmail 
 ( 
 groupEmail 
 ); 
  
 var 
  
 users 
  
 = 
  
 group 
 . 
 getUsers 
 (); 
  
 var 
  
 childGroups 
  
 = 
  
 group 
 . 
 getGroups 
 (); 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 childGroups 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 var 
  
 childGroup 
  
 = 
  
 childGroups 
 [ 
 i 
 ]; 
  
 users 
  
 = 
  
 users 
 . 
 concat 
 ( 
 getAllMembers 
 ( 
 childGroup 
 . 
 getEmail 
 ())); 
  
 } 
  
 // Remove duplicate members 
  
 var 
  
 uniqueUsers 
  
 = 
  
 []; 
  
 var 
  
 userEmails 
  
 = 
  
 {}; 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 users 
 . 
 length 
 ; 
  
 i 
 ++ 
 ) 
  
 { 
  
 var 
  
 user 
  
 = 
  
 users 
 [ 
 i 
 ]; 
  
 if 
  
 ( 
 ! 
 userEmails 
 [ 
 user 
 . 
 getEmail 
 ()]) 
  
 { 
  
 uniqueUsers 
 . 
 push 
 ( 
 user 
 ); 
  
 userEmails 
 [ 
 user 
 . 
 getEmail 
 ()] 
  
 = 
  
 true 
 ; 
  
 } 
  
 } 
  
 return 
  
 uniqueUsers 
 ; 
 } 
 /** 
 * Get indirect members from multiple groups (and delete duplicates). 
 * @param {array} the e-mail addresses of multiple groups. 
 * @return {object} indirect members of multiple groups. 
 */ 
 function 
  
 getUsersFromGroups 
 ( 
 groupEmails 
 ) 
  
 { 
  
 let 
  
 users 
  
 = 
  
 []; 
  
 for 
  
 ( 
 let 
  
 groupEmail 
  
 of 
  
 groupEmails 
 ) 
  
 { 
  
 let 
  
 groupUsers 
  
 = 
  
 GroupsApp 
 . 
 getGroupByEmail 
 ( 
 groupEmail 
 ). 
 getUsers 
 (); 
  
 for 
  
 ( 
 let 
  
 user 
  
 of 
  
 groupUsers 
 ) 
  
 { 
  
 if 
  
 ( 
 ! 
 users 
 . 
 some 
 ( 
 u 
  
 = 
>  
 u 
 . 
 getEmail 
 () 
  
 === 
  
 user 
 . 
 getEmail 
 ())) 
  
 { 
  
 users 
 . 
 push 
 ( 
 user 
 ); 
  
 } 
  
 } 
  
 } 
  
 return 
  
 users 
 ; 
 } 

Modifications

You can edit the team vacation calendar automation as much as you'd like to fit your needs. Below is an optional change to modify the trigger.

Change how often the script scans for new events

To change how often the script runs, take the following steps:

  1. In the Apps Script project, click Triggers .
  2. Next to the trigger, click Edit Trigger .
  3. Select your changes and click Save .

Contributors

This sample is maintained by Google with the help of Google Developer Experts.

Next steps

Create a Mobile Website
View Site in Mobile | Classic
Share by: