Skip to content

Instantly share code, notes, and snippets.

@tomjaimz
Last active June 4, 2024 10:23
Show Gist options
  • Select an option

  • Save tomjaimz/93bb639ba2bb61f99cf5cb942edc361b to your computer and use it in GitHub Desktop.

Select an option

Save tomjaimz/93bb639ba2bb61f99cf5cb942edc361b to your computer and use it in GitHub Desktop.
Google App Script code to import events from a Calendar (.ics) file into Google Sheets.
function onOpen() {
SpreadsheetApp.getUi().createAddonMenu().addItem('Upload', 'upload').addToUi();
}
function upload() {
SpreadsheetApp.getUi().showModalDialog(HtmlService.createHtmlOutput(
'<form><input type="file" name="file" onchange="var reader = new FileReader(); reader.onloadend = (e) => google.script.run.withSuccessHandler(google.script.host.close).importICS(e.target.result); reader.readAsText(this.parentNode.file.files[0]);"></form>'
), 'Select .ics file');
}
function importICS(form) {
var inputFields = ['DTSTART', 'DTEND', 'SUMMARY'], // add to this to add more fields
data = [['Start', 'End', 'Summary']], // should correspond to inputFields
reDateForDateFunction = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/g,
lines = form.split(/\r\n|\n|\r/);
for (i in lines) {
var lineData = lines[i].split(':'), col = inputFields.indexOf(lineData[0])
if(lines[i] === "BEGIN:VEVENT") {
data[data.length] = [];
}
else if(col !== -1) { // exists in inputFields
data[data.length - 1][col] = (lineData[0].substr(0, 2) === 'DT') ? new Date(lineData[1].replace(reDateForDateFunction, "$1-$2-$3T$4:$5:$6Z")) : lineData[1]
}
}
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(1, 1, data.length, data[0].length).setValues(data)
}
@claydegruchy
Copy link
Copy Markdown

claydegruchy commented Jun 4, 2024

Another version that avoid usage of set values to allow the output to be inserted into the context of the command.

usage: =insertICS(".../basic.ics")
:


function icsToTable(icsString) {
  var inputFields = ['DTSTART', 'SUMMARY', 'DESCRIPTION'], // add to this to add more FIELDs
    data = [['Start', 'Summary', 'Description']], // should correspond to inputFields
    lines = icsString.split(/\r\n|\n|\r/);
  for (i in lines) {
    var lineData = lines[i].split(':'), col = inputFields.indexOf(lineData[0].split(';', 1)[0])
    if (lines[i] === "BEGIN:VEVENT") { //Adds a new row and initialize it to avoid issues when not found
      data[data.length] = ["", "", ""]; //If a FIELD is not present on the file, it makes an error: Better to initialize!
      // Its length should correspond to inputFields. Eg: for 7 read fiels --> = ["","","","","","",""]; 
    }
    else if (lines[i].substr(0, 1) === ' ') {
      //If row starts with just SPACE instead of a FIELD, it must be added to previous line as it belows to a multiple line FIELD
      // https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.11
      data[data.length - 1][previous_col] = data[data.length - 1][previous_col] + lines[i]
    }
    else if (col !== -1 && data.length > 1) { // exists in inputFields, and BEGIN:VEVENT found
      //Browser.msgBox("Dealing with: /////////0:" + lineData[0] + " //////////////1: " +  lineData[1] )
      // Date (YYYY,MM,DD) Thus: lineData[1].substr(0, 4) is YYYY, lineData[1].substr(4, 2)-1 is MM (from 0-11) and the other is DD. And last, lineData[1] is the text DATA to import when it is not a Date (DTStart or DTend)
      data[data.length - 1][col] = (lineData[0].substr(0, 2) === 'DT') ? new Date(lineData[1].substr(0, 4), lineData[1].substr(4, 2) - 1, lineData[1].substr(6, 2)) : lineData[1]

      for (var j = 2; j < lineData.length; j = j + 1) { //It might happen that someone added ':' to the FIELD and therefore it was split too 
        data[data.length - 1][col] = data[data.length - 1][col] + ":" + lineData[j]
      }
      var previous_col = col //To remember the last FIELD saved
    }
  }
  return data
}

async function download(fileURL) {
  let response = UrlFetchApp.fetch(fileURL, { muteHttpExceptions: true });
  let rc = await response.getResponseCode()
  console.log(rc)
  if (rc == 200) {
    return response.getContentText()
  }

}

async function insertICS(url) {
  let content = await download(url)
  return icsToTable(content)

}

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