Skip to content

Instantly share code, notes, and snippets.

@tyrann0us
Last active December 13, 2024 19:45
Show Gist options
  • Save tyrann0us/af4a553d116d00e783fff5404fcb2b97 to your computer and use it in GitHub Desktop.
Save tyrann0us/af4a553d116d00e783fff5404fcb2b97 to your computer and use it in GitHub Desktop.
Generate a free/busy ICS file from your Google Calendar using this Google Apps Script, which securely saves the file to your Google Drive. Share your availability without exposing event details. All data processing and storage occur within your Google account.
/**
* Generates an ICS file containing free/busy information from your Google Calendar
* and saves it to Google Drive.
*/
function generateICS() {
// Configuration
var calendarId = 'primary'; // Calendar ID to read events from
var fileName = 'busy.ics'; // Name of the ICS file to be saved in Google Drive
var eventTitle = 'Busy'; // Title for the events in the ICS file
var organizationName = 'Acme Corporation'; // Name for the PRODID property
var pastMonths = 2; // Number of months to include past events
var futureMonths = 6; // Number of months to include future events
// Specify which event statuses to include
var includedStatuses = [
CalendarApp.GuestStatus.OWNER, // Include events you created
CalendarApp.GuestStatus.YES, // Include events you accepted
// Add more statuses if needed (e.g., MAYBE, INVITED)
];
// Get the date range (from current date minus pastMonths to current date plus futureMonths)
var now = new Date();
var startDate = addMonths(now, -pastMonths);
var endDate = addMonths(now, futureMonths);
// Fetch the events we want to include
var events = getEvents(calendarId, startDate, endDate, includedStatuses);
// Generate ICS content
var icsContent = generateICSContent(events, eventTitle, organizationName);
// Save ICS file to Google Drive
saveICSToDrive(icsContent, fileName);
}
/**
* Retrieves events from the specified calendar within the given date range
* and filters them based on the specified statuses and conditions.
*
* @param {string} calendarId - The ID of the calendar.
* @param {Date} startDate - The start date of the range.
* @param {Date} endDate - The end date of the range.
* @param {Array} includedStatuses - Array of CalendarApp.GuestStatus constants to include.
* @return {Array} Array of event objects containing start and end times.
*/
function getEvents(calendarId, startDate, endDate, includedStatuses) {
var calendar = CalendarApp.getCalendarById(calendarId);
var events = calendar.getEvents(startDate, endDate);
var includedEvents = [];
events.forEach(function(event) {
// Check the event's guest status
var myStatus = event.getMyStatus();
// Include the event only if its status is in the includedStatuses array
if (includedStatuses.indexOf(myStatus) === -1) {
return; // Skip this event
}
// Skip all-day events
if (event.isAllDayEvent()) {
return;
}
// Include event
includedEvents.push({
start: event.getStartTime(),
end: event.getEndTime()
});
});
return includedEvents;
}
/**
* Generates ICS file content from the events.
*
* @param {Array} events - Array of event objects containing start and end times.
* @param {string} eventTitle - The title to use for the events in the ICS file.
* @param {string} organizationName - The organization name for the PRODID property.
* @return {string} The ICS file content as a string.
*/
function generateICSContent(events, eventTitle, organizationName) {
var uidDomain = CalendarApp.getDefaultCalendar().getId();
var icsLines = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//' + organizationName + '//FreeBusy Calendar//EN',
'CALSCALE:GREGORIAN',
'METHOD:PUBLISH'
];
events.forEach(function(ev, index) {
var eventUID = 'event-' + index + '@' + uidDomain;
var dtStamp = formatDate(new Date());
var dtStart = formatDate(ev.start);
var dtEnd = formatDate(ev.end);
icsLines.push('BEGIN:VEVENT');
icsLines.push('UID:' + eventUID);
icsLines.push('DTSTAMP:' + dtStamp);
icsLines.push('DTSTART:' + dtStart);
icsLines.push('DTEND:' + dtEnd);
icsLines.push('SUMMARY:' + eventTitle);
icsLines.push('CLASS:PRIVATE');
icsLines.push('END:VEVENT');
});
icsLines.push('END:VCALENDAR');
return icsLines.join('\r\n');
}
/**
* Saves the ICS content to a file in Google Drive.
*
* @param {string} icsContent - The ICS file content.
* @param {string} fileName - The name of the file to save.
*/
function saveICSToDrive(icsContent, fileName) {
// Search for existing file
var files = DriveApp.getFilesByName(fileName);
var file;
if (files.hasNext()) {
file = files.next();
file.setContent(icsContent);
} else {
file = DriveApp.createFile(fileName, icsContent, MimeType.PLAIN_TEXT);
}
// Set sharing permissions to "Anyone with the link can view"
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
Logger.log('ICS file has been saved and is accessible at: ' + file.getDownloadUrl());
}
/**
* Formats a Date object into a string suitable for ICS files (UTC time).
*
* @param {Date} date - The date to format.
* @return {string} The formatted date string in 'yyyyMMddTHHmmssZ' format.
*/
function formatDate(date) {
return Utilities.formatDate(date, 'UTC', 'yyyyMMdd\'T\'HHmmss\'Z\'');
}
/**
* Adds a specified number of months to a date.
*
* @param {Date} date - The original date.
* @param {number} months - Number of months to add (can be negative).
* @return {Date} The new date.
*/
function addMonths(date, months) {
var d = new Date(date);
var day = d.getDate();
d.setMonth(d.getMonth() + months);
// Handle end-of-month issues
if (d.getDate() < day) {
d.setDate(0);
}
return d;
}
@tyrann0us
Copy link
Author

tyrann0us commented Oct 8, 2024

Instructions for Use:

  1. Copy and Paste the Script:

    • Open Google Apps Script and create a new project.
    • Paste the entire script above into the code editor.
  2. Adjust Configuration Variables:

    • calendarId:
      • Set to your calendar's ID. Use 'primary' for your primary calendar or find the ID in Google Calendar settings under "Integrate calendar".
    • fileName:
      • Choose a name for your ICS file (e.g., 'my-freebusy.ics').
    • eventTitle:
      • Set the title that will appear for busy events (e.g., 'Busy').
    • organizationName:
      • Set your organization's name for the PRODID property.
    • pastMonths and futureMonths:
      • Adjust the number of months to include past and future events (default is 2 months in the past and 6 months in the future).
    • includedStatuses:
      • Specify which event statuses to include by modifying the array. By default, only accepted events are included.
      • Available statuses (see GuestStatus documentation):
        • CalendarApp.GuestStatus.YES - Accepted
        • CalendarApp.GuestStatus.NO - Declined
        • CalendarApp.GuestStatus.MAYBE - Tentative
        • CalendarApp.GuestStatus.INVITED - Invited but not yet responded
        • CalendarApp.GuestStatus.OWNER - You are the organizer of the event
  3. Authorize the Script:

    • Click on the Run menu and select generateFreeBusyICS.
    • Follow the prompts to authorize the script to access your Google Calendar and Drive.
  4. Set Up Triggers:

    • To automatically update the ICS file, set up a trigger to listen for calendar updates:
      • Click on the Triggers icon (clock symbol) on the left sidebar.
      • Click Add Trigger.
      • Choose generateFreeBusyICS as the function to run.
      • Choose From calendar as the event source.
      • Enter the calendar owner email.
  5. Access the ICS File:

    • After running the script, check the Logs (View > Logs) to find the direct download URL of the ICS file.
    • The URL will look like: https://drive.google.com/uc?export=download&id=FILE_ID.
    • Share this URL with those who need to subscribe to your free/busy calendar.
  6. Subscribe to the Calendar:

    • Recipients can use the URL to subscribe to your free/busy calendar in their calendar applications by adding a new calendar via URL.

@tyrann0us
Copy link
Author

If you find this helpful, please consider buying me a coffee ☕ or beer 🍻 at https://paypal.me/pbammes. Thank you so much! 🙇

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment