0

I have multiple staff who input their day into a goggle calendar and I take the data and run stats. At the moment I can only take data from one calendar at a time. How can I take this script and make it work on several calendars?

Note that each calendar has its own target sheet. I've tried duplicating the script but only one version seems to work at a time.

function export_gcal_to_gsheet() {
  var mycal = "person email";
  var cal = CalendarApp.getCalendarById(mycal);
  var events = cal.getEvents(new Date("July 01, 2022 00:00:00 UTC"), new Date());
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("PersonCalendar");
  sheet.clearContents();
  var calColor = cal.getColor();
  var colors = Calendar.Colors.get().calendar;
  var header = ["Calendar Address", "Event Title", "Event Description", "Event Location", "Event Start", "Event End", "Calculated Duration", "Visibility", "Date Created", "Last Updated", "MyStatus", "Created By", "All Day Event", "Recurring Event", "Color"];
  var offset = 6;
  var { v, c } = events.reverse().reduce((o, e, i) => {
    var color = e.getColor();
    var row = offset + i + 1;
    var c = colors[color] ? colors[color].background : calColor;
    var f = `=(HOUR(F${row})+(MINUTE(F${row})/60))-(HOUR(E${row})+(MINUTE(E${row})/60))`;
    o.v.push([mycal, events[i].getTitle(), events[i].getDescription(), events[i].getLocation(), events[i].getStartTime(), events[i].getEndTime(), f, ('' + events[i].getVisibility()), events[i].getDateCreated(), events[i].getLastUpdated(), events[i].getMyStatus(), events[i].getCreators(), events[i].isAllDayEvent(), events[i].isRecurringEvent(), c]);
    o.c.push([c]);
    return o;
  }, { v: [], c: [] });
  var values = [header, ...v];
  sheet.getRange(6, 1, values.length, values[0].length).setValues(values);
  sheet.getRange(7, 7, v.length - 1).setNumberFormat('.00');
  sheet.getRange(7, 15, c.length).setBackgrounds(c);
}
Cooper
  • 59,616
  • 6
  • 23
  • 54

2 Answers2

2

In your script, only one Calendar ID is used. In order to use multiple Calendar IDs, when your showing script is modified for using multiple Calendar IDs, how about the following modification?

Modified script 1:

In this modification, all values are put to "PersonCalendar" sheet.

function export_gcal_to_gsheet() {
  var calendarIds = ["person email1", "person email2",,,]; // Please set your Calendar IDs.

  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("PersonCalendar");
  sheet.clearContents();
  var header = ["Calendar Address", "Event Title", "Event Description", "Event Location", "Event Start", "Event End", "Calculated Duration", "Visibility", "Date Created", "Last Updated", "MyStatus", "Created By", "All Day Event", "Recurring Event", "Color"];
  var colors = Calendar.Colors.get().calendar;
  var { vv, cc } = calendarIds.reduce((oo, mycal) => {
    var cal = CalendarApp.getCalendarById(mycal);
    var events = cal.getEvents(new Date("July 01, 2022 00:00:00 UTC"), new Date());
    var calColor = cal.getColor();
    var offset = 6;
    var { v, c } = events.reverse().reduce((o, e, i) => {
      var color = e.getColor();
      var row = offset + i + 1;
      var c = colors[color] ? colors[color].background : calColor;
      var f = `=(HOUR(F${row})+(MINUTE(F${row})/60))-(HOUR(E${row})+(MINUTE(E${row})/60))`;
      o.v.push([mycal, events[i].getTitle(), events[i].getDescription(), events[i].getLocation(), events[i].getStartTime(), events[i].getEndTime(), f, ('' + events[i].getVisibility()), events[i].getDateCreated(), events[i].getLastUpdated(), events[i].getMyStatus(), events[i].getCreators(), events[i].isAllDayEvent(), events[i].isRecurringEvent(), c]);
      o.c.push([c]);
      return o;
    }, { v: [], c: [] });
    oo.vv = [...oo.vv, ...v];
    oo.cc = [...oo.cc, ...c];
    return oo;
  }, { vv: [], cc: [] });
  var values = [header, ...vv];
  sheet.getRange(6, 1, values.length, values[0].length).setValues(values);
  sheet.getRange(7, 7, vv.length - 1).setNumberFormat('.00');
  sheet.getRange(7, 15, cc.length).setBackgrounds(cc);
}
  • In this modification, by retrieving the values from multiple Calendar IDs, all retrieved values are put to the Spreadsheet.

Modified script 2:

In this modification, the values of each Calendar are put to each sheet.

function export_gcal_to_gsheet() {
  var obj = [{ mycal: "person email1", sheetName: "PersonCalendar" }];
  var colors = Calendar.Colors.get().calendar;
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  obj.forEach(({ mycal, sheetName }) => {
    var cal = CalendarApp.getCalendarById(mycal);
    var events = cal.getEvents(new Date("July 01, 2022 00:00:00 UTC"), new Date());
    var sheet = ss.getSheetByName(sheetName);
    sheet.clear(); // or sheet.clearContents();
    var calColor = cal.getColor();
    var header = ["Calendar Address", "Event Title", "Event Description", "Event Location", "Event Start", "Event End", "Calculated Duration", "Visibility", "Date Created", "Last Updated", "MyStatus", "Created By", "All Day Event", "Recurring Event", "Color"];
    var offset = 6;
    var { v, c } = events.reverse().reduce((o, e, i) => {
      var color = e.getColor();
      var row = offset + i + 1;
      var c = colors[color] ? colors[color].background : calColor;
      var f = `=(HOUR(F${row})+(MINUTE(F${row})/60))-(HOUR(E${row})+(MINUTE(E${row})/60))`;
      o.v.push([mycal, events[i].getTitle(), events[i].getDescription(), events[i].getLocation(), events[i].getStartTime(), events[i].getEndTime(), f, ('' + events[i].getVisibility()), events[i].getDateCreated(), events[i].getLastUpdated(), events[i].getMyStatus(), events[i].getCreators(), events[i].isAllDayEvent(), events[i].isRecurringEvent(), c]);
      o.c.push([c]);
      return o;
    }, { v: [], c: [] });
    var values = [header, ...v];
    sheet.getRange(6, 1, values.length, values[0].length).setValues(values);
    sheet.getRange(7, 7, v.length - 1).setNumberFormat('.00');
    sheet.getRange(7, 15, c.length).setBackgrounds(c);
  });
}

Modified script 3:

In this modification, the values of each Calendar are put on each sheet. And, by using Sheets API, the process cost is reduced a little from that of "Modified script 2". So, please enable Sheets API at Advanced Google services.

function export_gcal_to_gsheet() {
  var obj = [{ mycal: "person email1", sheetName: "PersonCalendar" },,,]; // Please set calendar IDs and sheet names.

  var colors = Calendar.Colors.get().calendar;
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var { valuesBatchUpdate, batchUpdate } = obj.reduce((oo, { mycal, sheetName }) => {
    var cal = CalendarApp.getCalendarById(mycal);
    var calColor = cal.getColor();
    var events = cal.getEvents(new Date("July 01, 2022 00:00:00 UTC"), new Date());
    var header = ["Calendar Address", "Event Title", "Event Description", "Event Location", "Event Start", "Event End", "Calculated Duration", "Visibility", "Date Created", "Last Updated", "MyStatus", "Created By", "All Day Event", "Recurring Event", "Color"];
    var offset = 6;
    var { v, c } = events.reverse().reduce((o, e, i) => {
      var color = e.getColor();
      var row = offset + i + 1;
      var c = colors[color] ? colors[color].background : calColor;
      var f = `=(HOUR(F${row})+(MINUTE(F${row})/60))-(HOUR(E${row})+(MINUTE(E${row})/60))`;
      o.v.push([mycal, events[i].getTitle(), events[i].getDescription(), events[i].getLocation(), (events[i].getStartTime().getTime() / 1000 / 86400) + 25569, (events[i].getEndTime().getTime() / 1000 / 86400) + 25569, f, ('' + events[i].getVisibility()), (events[i].getDateCreated().getTime() / 1000 / 86400) + 25569, (events[i].getLastUpdated().getTime() / 1000 / 86400) + 25569, events[i].getMyStatus().toString(), events[i].getCreators().join(","), events[i].isAllDayEvent(), events[i].isRecurringEvent(), c]);
      o.c.push([c]);
      return o;
    }, { v: [], c: [] });
    var sheetId = ss.getSheetByName(sheetName).getSheetId();
    var hexToRgb = hex => { // Ref: https://stackoverflow.com/a/11508164
      var bigint = parseInt(hex, 16);
      var red = ((bigint >> 16) & 255) / 255;
      var green = ((bigint >> 8) & 255) / 255;
      var blue = (bigint & 255) / 255;
      return { red, green, blue }
    };
    oo.valuesBatchUpdate.push({ range: `'${sheetName}'!A6`, values: [header, ...v] });
    oo.batchUpdate.push(
      { updateCells: { fields: "*", range: { sheetId } } },
      { repeatCell: { range: { sheetId, startRowIndex: 6, endRowIndex: 6 + v.length, startColumnIndex: 6, endColumnIndex: 7 }, cell: { userEnteredFormat: { numberFormat: { pattern: '.00', type: "NUMBER" } } }, fields: "userEnteredFormat.numberFormat" } },
      { repeatCell: { range: { sheetId, startRowIndex: 6, endRowIndex: 6 + v.length, startColumnIndex: 4, endColumnIndex: 6 }, cell: { userEnteredFormat: { numberFormat: { pattern: 'yyyy/MM/dd', type: "DATE" } } }, fields: "userEnteredFormat.numberFormat" } },
      { repeatCell: { range: { sheetId, startRowIndex: 6, endRowIndex: 6 + v.length, startColumnIndex: 8, endColumnIndex: 10 }, cell: { userEnteredFormat: { numberFormat: { pattern: 'yyyy/MM/dd', type: "DATE" } } }, fields: "userEnteredFormat.numberFormat" } },
      { updateCells: { rows: c.map(([hex]) => ({ values: [{ userEnteredFormat: { backgroundColor: hexToRgb(hex.replace("#", "")) } }] })), range: { sheetId, startRowIndex: 6, endRowIndex: 6 + c.length, startColumnIndex: 14, endColumnIndex: 15 }, fields: "userEnteredFormat.backgroundColor" } }
    );
    return oo;
  }, { valuesBatchUpdate: [], batchUpdate: [] });
  var ssId = ss.getId();
  Sheets.Spreadsheets.batchUpdate({ requests: batchUpdate }, ssId);
  Sheets.Spreadsheets.Values.batchUpdate({ data: valuesBatchUpdate, valueInputOption: "USER_ENTERED" }, ssId);
}

Note:

  • In this case, when you have no permissions for accessing the Calendar IDs, an error occurs. Please be careful about this.
Tanaike
  • 181,128
  • 11
  • 97
  • 165
  • Thanks, Tanaike as always. Let me implement it on my end and see how it all works. Process cost is a concern for me so I will defintely lean towards mod 3 at this stage. – RoosterMagic22 Oct 03 '22 at 06:40
  • HI Tanaike, I'm getting this error on Mod 1: 6:19:29 AM Error TypeError: Cannot read property 'getEvents' of null calendarIds.reduce.vv @ Untitled.gs:10 export_gcal_to_gsheet @ Untitled.gs:8 – RoosterMagic22 Oct 03 '22 at 20:25
  • Mod 2 works perfectly, thankyou you. I will test the other mods, but I only give myself 1 hour every morning to work on coding so bear with me and I will get back to you on the other mods. – RoosterMagic22 Oct 03 '22 at 22:13
  • @RoosterMagic22 Thank you for replying. About `Mod 2 works perfectly`, the result of "Modified script 3" is the same as "Modified script 2". Only the difference is the process cost. – Tanaike Oct 03 '22 at 22:16
  • I know I can't work out what I'm doing wrong, It annoys me a bit actually. I'm sure it's something simple. – RoosterMagic22 Oct 03 '22 at 22:22
  • @RoosterMagic22 Thank you for replying. I deeply apologize for it. – Tanaike Oct 03 '22 at 22:23
  • sorry mate I don't know what I'm doing, Im still getting the following erros on mod 3 9:05:57 AM Error TypeError: Cannot read property 'toString' of null events.reverse.reduce.v @ Code.gs:17 obj.reduce.valuesBatchUpdate @ Code.gs:12 export_gcal_to_gsheet @ Code.gs:6 – RoosterMagic22 Oct 04 '22 at 23:36
  • @RoosterMagic22 Thank you for replying. I have to apologize for my poor English skill. Unfortunately, I cannot understand `I don't know what I'm doing, Im still getting the following erros on mod 3 9:05:57 AM Error TypeError: Cannot read property 'toString' of null events.reverse.reduce.v @ Code.gs:17 obj.reduce.valuesBatchUpdate @ Code.gs:12 export_gcal_to_gsheet @ Code.gs:6 `. Can I ask you about the detail of it? – Tanaike Oct 05 '22 at 00:47
1

Both of the following techniques distribute the output to separate sheets and leave most of the original code intact.

function exportcalenders() {
  const calids = [{ id: "calid", shnam: "Sheet Name"}];
  calids.forEach(obj => {
    var cal = CalendarApp.getCalendarById(obj.id);
    var events = cal.getEvents(new Date(new Date().getFullYear(),newDate().getMonth(),1),new Date());
    var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(obj.shnam);
    sheet.clearContents();
    var calColor = cal.getColor();
    var colors = Calendar.Colors.get().calendar;
    var header = ["Calendar Address", "Event Title", "Event Description", "Event Location", "Event Start", "Event End", "Calculated Duration", "Visibility", "Date Created", "Last Updated", "MyStatus", "Created By", "All Day Event", "Recurring Event", "Color"];
    var offset = 6;
    var { v, c } = events.reverse().reduce((o, e, i) => {
      var color = e.getColor();
      var row = offset + i + 1;
      var c = colors[color] ? colors[color].background : calColor;
      var f = `=(HOUR(F${row})+(MINUTE(F${row})/60))-(HOUR(E${row})+(MINUTE(E${row})/60))`;
      o.v.push([mycal, events[i].getTitle(), events[i].getDescription(), events[i].getLocation(), events[i].getStartTime(), events[i].getEndTime(), f, ('' + events[i].getVisibility()), events[i].getDateCreated(), events[i].getLastUpdated(), events[i].getMyStatus(), events[i].getCreators(), events[i].isAllDayEvent(), events[i].isRecurringEvent(), c]);
      o.c.push([c]);
      return o;
    }, { v: [], c: [] });
    var values = [header, ...v];
    sheet.getRange(6, 1, values.length, values[0].length).setValues(values);
    sheet.getRange(7, 7, v.length - 1).setNumberFormat('.00');
    sheet.getRange(7, 15, c.length).setBackgrounds(c);
  });

}

Here's another way using recursion:

This version assumes that all of the calendars are shared with you and thus accessible via getAllCalendars. The inclCals variable allows you to only include certain calendars from getAllCalendars list and snames is a list of sheets that you wish each calendar to be assigned two. The variable obj transfers all of that information to main cal via cacheService.

function xportcals() {
  let inclCals = ["CalName1", "CalName2", "CalName3", "CalName4"];
  let snames = ["Sheet1","Sheet2","Sheet3","Sheet4"]
  let obj = CalendarApp.getAllCalendars().reduce((a, c, i) => {
    let idx = inclCals.indexOf(c.getName());
    if (~idx) {
      a["cal"].push({ id: c.getId(), name: c.getName(),sheetname:snames[idx]});
    }
    return a;
  }, { cal: [], proc: { level: 0, som: 1, sod: 1 } });
  CacheService.getScriptCache().put("cals", JSON.stringify(obj), 300);
  maincals();
}

    function maincals() {
      let obj = JSON.parse(CacheService.getScriptCache().get("cals"));
      var cal = CalendarApp.getCalendarById(obj.cal[obj.proc.level]);
      var events = cal.getEvents(new Date(new Date().getFullYear(), newDate().getMonth() - obj.proc.som , obj.proc.sod), new Date());
      var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(obj.cal.sheetname);
      sheet.clearContents();
      var calColor = cal.getColor();
      var colors = Calendar.Colors.get().calendar;
      var header = ["Calendar Address", "Event Title", "Event Description", "Event Location", "Event Start", "Event End", "Calculated Duration", "Visibility", "Date Created", "Last Updated", "MyStatus", "Created By", "All Day Event", "Recurring Event", "Color"];
      var offset = 6;
      var { v, c } = events.reverse().reduce((o, e, i) => {
        var color = e.getColor();
        var row = offset + i + 1;
        var c = colors[color] ? colors[color].background : calColor;
        var f = `=(HOUR(F${row})+(MINUTE(F${row})/60))-(HOUR(E${row})+(MINUTE(E${row})/60))`;
        o.v.push([mycal, events[i].getTitle(), events[i].getDescription(), events[i].getLocation(), events[i].getStartTime(), events[i].getEndTime(), f, ('' + events[i].getVisibility()), events[i].getDateCreated(), events[i].getLastUpdated(), events[i].getMyStatus(), events[i].getCreators(), events[i].isAllDayEvent(), events[i].isRecurringEvent(), c]);
        o.c.push([c]);
        return o;
      }, { v: [], c: [] });
      var values = [header, ...v];
      sheet.getRange(6, 1, values.length, values[0].length).setValues(values);
      sheet.getRange(7, 7, v.length - 1).setNumberFormat('.00');
      sheet.getRange(7, 15, c.length).setBackgrounds(c);
      obj.proc.level += 1;//increment the level counter
      CacheService.getScriptCache().put("cals", JSON.stringify(obj), 60);//save obj in cacheService
      if (obj.proc.level < obj.cal.length) {
        maincals();
      }
      return;
    }

I did not test this particular version but I did test a simpler version and most of the parts that are not common to what I tested are your original code which presumably works.

Cooper
  • 59,616
  • 6
  • 23
  • 54
  • Oh, that recursion one looks interesting and I can see how that would work - let me implement it on my end and get back to you. – RoosterMagic22 Oct 03 '22 at 06:35
  • Hi Cooper - I'm trying the reversion coding and getting the following errors, any ideas? 6:07:49 AM Error TypeError: Cannot read property 'getEvents' of null maincals @ Code.gs:18 xportcals @ Code.gs:12 – RoosterMagic22 Oct 03 '22 at 20:09
  • Well then the cal must be null so what does `obj` look like. – Cooper Oct 03 '22 at 20:24
  • Are all the calendars you work with shared with you. If not then this `let obj = CalendarApp.getAllCalendars().reduce((a, c, i) => {` may not work for you. – Cooper Oct 03 '22 at 20:30
  • What do you mean when you ask what does OBJ look like? Does that mean the final script? – RoosterMagic22 Oct 03 '22 at 20:32
  • Yep the calendars are definitely shared with me, I can see them in goggle calendar and when I try an individual script it works fine. – RoosterMagic22 Oct 03 '22 at 20:33
  • Do you understand how the reduce function works? – Cooper Oct 03 '22 at 20:38
  • I understand it at a very basic level mate, I will keep testing and also test the other mods, but I only give myself 1 hour every morning to work on coding so bear with me and I will get back to you. You problably have better things to do but I thought you might be interested as to how it goes - either way thankyou for your help – RoosterMagic22 Oct 03 '22 at 22:14