0

Using @Tanaike's 2nd model script from this answer, how would one restructure it such that it would fit the Excel CSV template to Google Calendar import format?

For example using this Excel CSV like format input:

Subject Start Date Start Time End Date End Time All Day Event Description Location Private Reminder Created
MyEvent1 07/19/2023 10:00:00 07/19/2023 10:03:00 FALSE MyDesc1 MyLocation1 FALSE 3
MyEvent2 07/19/2023 14:00:00 07/19/2023 14:03:00 FALSE MyDesc2 MyLocation2 TRUE
MyEvent3 07/19/2023 18:15:00 07/19/2023 18:18:00 FALSE MyDesc3 MyLocation3 FALSE 3

Tanaike's Code with the stages where I get difficulties with my modifications:


 /*
  var subj = ss.getRange("A2:A" + lr).getValues(); // data[i][1]
  var stad = ss.getRange("B2:B" + lr).getValues(); // data[i][2]
  var Stat = ss.getRange("C2:C" + lr).getValues(); // data[i][3]
  var endd = ss.getRange("D2:D" + lr).getValues(); // data[i][4]
  var endt = ss.getRange("E2:E" + lr).getValues(); // data[i][5]
  var alde = ss.getRange("F2:F" + lr).getValues(); // data[i][6]
  var desc = ss.getRange("J2:J" + lr).getValues(); // data[i][7]
  var loca = ss.getRange("H2:H" + lr).getValues(); // data[i][8]
  var priv = ss.getRange("I2:I" + lr).getValues(); // data[i][9]
  var remi = ss.getRange("J2:J" + lr).getValues(); // data[i][10]
  var crea = ss.getRange("K2:K" + lr).getValues(); // data[i][11]
 */

function addEvents() {
  var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var lr = ss.getLastRow();
  var cal = CalendarApp.getCalendarById("c_fe882662e583725f15fd4faa8c8fdf5124f3affe543778ecdcf0838d6eb17f26@group.calendar.google.com");
  var data = ss.getRange("A2:K" + lr).getValues();
  while (data[data.length - 1][0] == '') data.pop();
  var rangeList = [];
  for (var i = 0; i < data.length; i++) {
    if (data[i][11] == "created") continue;
    cal.createEvent(data[i][2]*data[i][3], data[i][4]*data[i][5], data[i][6], { location: data[i][8], description: data[i][7] }); // not sure about boolean logic use to combine date and time
    // ? cal.addPrivate(data[i][9]); // How do we add the private data?
    cal.addPopupReminder(data[i][10]);
    rangeList.push(`R${i + 3}`); // not sure about that line (It seems to add to the empty list at i +3 https://spreadsheet.dev/array-method-push-in-apps-script
  }
  if (rangeList.length == 0) return;
  ss.getRangeList(rangeList).setValue("created");
}

Google Calendar people seem not to have included the simple reminder requirement in the Excel CSV template.

How can we address that? The idea would be to have a CSV like Google sheet sheet importing the 11 Column of the example above into Google Calendar.

I found multiple similar questions, old and recent yet no workable suggestion:

Two of the most relevant questions found:

https://webapps.stackexchange.com/questions/112517/importing-reminders-to-google-calendar-using-csv-file

Add events from Google sheets to google calendar

and tutorial material:

https://wafflebytes.blogspot.com/2017/06/google-script-setting-up-reminder-dates.html

For reminders:

Google Script All Day Calendar Event with Notification On The Day

https://developers.google.com/apps-script/reference/calendar/calendar-event-series#addPopupReminder(Integer)

Prevent Duplicates:

Create Google Calendar Events from Spreadsheet but prevent duplicates

Excel CSV format:

https://www.reddit.com/r/excel/comments/k38xsf/comment/ge19zu6/?utm_source=share&utm_medium=web2x&context=3

Class Calendar:

https://developers.google.com/apps-script/reference/calendar/calendar

EDIT:

@Tanaike — Can I ask you about the detail of your input and output situation? , @SputnikDrunk2 — How do you use the Excel CSV file in your use case? Do you upload it to your Drive, or do you view it in a spreadsheet file?

Sure, thanks guys for asking for clarifications, sorry for my poor formulation.

I will import the Google Sheet Input directly from Google Sheets, using a modified version of your / @Tanaike's script, not from a CSV file, but I would like to use the same format as the default Excel CSV file sample sightly augmented as with the 11 columns from the table below:

Subject Start Date Start Time End Date End Time All Day Event Description Location Private Reminder Created
MyEvent1 07/19/2023 10:00:00 07/19/2023 10:03:00 FALSE MyDesc1 MyLocation1 FALSE 3
MyEvent2 07/19/2023 14:00:00 07/19/2023 14:03:00 FALSE MyDesc2 MyLocation2 TRUE
MyEvent3 07/19/2023 18:15:00 07/19/2023 18:18:00 FALSE MyDesc3 MyLocation3 FALSE 3

I corrected and adapted the my previous version of @Tanaike's code with Andres Duarte's suggestion help as follows:

 /*
  var subj = ss.getRange("A2:A" + lr).getValues(); // data[i][0]
  var stad = ss.getRange("B2:B" + lr).getValues(); // data[i][1]
  var stat = ss.getRange("C2:C" + lr).getValues(); // data[i][2]
  var endd = ss.getRange("D2:D" + lr).getValues(); // data[i][3]
  var endt = ss.getRange("E2:E" + lr).getValues(); // data[i][4]
  var alde = ss.getRange("F2:F" + lr).getValues(); // data[i][5]
  var desc = ss.getRange("J2:J" + lr).getValues(); // data[i][6]
  var loca = ss.getRange("H2:H" + lr).getValues(); // data[i][7]
  var priv = ss.getRange("I2:I" + lr).getValues(); // data[i][8]
  var remi = ss.getRange("J2:J" + lr).getValues(); // data[i][9]
  var crea = ss.getRange("K2:K" + lr).getValues(); // data[i][10]
 */

function addEvents() {
  var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var lr = ss.getLastRow();
  var cal = CalendarApp.getCalendarById("YOURGOOGLECALENDARIDHERE");

  var data = ss.getRange("A2:K" + lr).getValues();

  var dateANDTimeArr = ss.getRange("A2:K" + lr).getDisplayValues(); // getDisplayValues is needed for the .substring() methods below to work

  while (data[data.length - 1][0] == '') data.pop();
  var rangeList = [];
  for (var i = 0; i < data.length; i++) {
    if (data[i][11] == "created") continue;

    var theEventStartDate = dateANDTimeArr[i][1]; // Extract the Date values from Column B
    var startHour = dateANDTimeArr[i][2].substring(0, 2); // Extract The Hour values from Column C
    var startMin = dateANDTimeArr[i][2].substring(3, 5); // Extract The Minutes values from Column C
    
    var theEventEndDate = dateANDTimeArr[i][3]; // Extract the Date values in Column D
    var endHour = dateANDTimeArr[i][4].substring(0, 2); // Extract The Hour values from Column E
    var endMin = dateANDTimeArr[i][4].substring(3, 5); // Extract The Minutes values from Column E

    Logger.log("theEventStartDate : " + theEventStartDate);
    Logger.log("startHour : " + startHour);
    Logger.log("startMin : " + startMin);

    Logger.log("theEventEndDate : " + theEventEndDate);
    Logger.log("endHour : " + endHour);
    Logger.log("endMin : " + endMin);

    var startDate = new Date(theEventStartDate + " " + startHour + ":" + startMin + ":00"); // Combine the Dates and The Times values from Columns B and C into one value as needed by createEvent() method
    var endDate = new Date(theEventEndDate + " " + endHour + ":" + endMin + ":00"); // Combine the Dates and The Times from Columns D and E into one value as needed by createEvent() method

    // Logger.log("startDate : " + startDate);
    // Logger.log("endDate : " + endDate);

    cal.createEvent(data[i][0], startDate, endDate, { location: data[i][7], description: data[i][6] });
    
    cal.isAllDayEvent(data[i][5]); // need to set it according to Column 5 input
    
    // Logger.log("cal.setVisibility(CalendarApp.Visibility.PRIVATE)(data[i][8]) : " + cal.setVisibility(CalendarApp.Visibility.PRIVATE)(data[i][8]));
    
    cal.setVisibility(CalendarApp.Visibility.PRIVATE)(data[i][8]); // https://developers.google.com/apps-script/reference/calendar/calendar-event#setvisibilityvisibility https://stackoverflow.com/a/34909992/10789707

    cal.addPopupReminder(data[i][9]);

    rangeList.push(`R${i + 3}`); // not sure about that line (It seems to add to the empty list at i +3 https://spreadsheet.dev/array-method-push-in-apps-script
  }
  if (rangeList.length == 0) return;
  ss.getRangeList(rangeList).setValue("created");
}

Remaining Problems:

1st problem:

All Day Event Column:

isAllDayEvent

As you can see in the screenshot above, I found the isAllDayEvent() method but it does not accept parameters and thus I cannot feed it the premade list of hardcoded boolean values set in column 6 I prepared in the Google Sheet input. What workaround would you suggest to allow the column 6 input to set the AllDayEvent parameter? I looked for a setAllDayEvent() method in the docs but it wasn't supplied by the google calendar people.

2nd Problem:

Private / setVisibility() Column:

TypeError: cal.setVisibility is not a function

setVisibility

Same issue as before but with the setVisibility() method. How do I use a workaround to feed it the premade list of hardcoded boolean values set in column 8 I prepared in the Google Sheet input.

3rd Problem:

Private / addPopupReminder() Column:

TypeError: cal.addPopupReminder is not a function

addPopupReminder

Same issue as before but with the addPopupReminder() method. How do I use a workaround to feed it the premade list of hardcoded integers values set in column 9 I prepared in the Google Sheet input.

Lod
  • 657
  • 1
  • 9
  • 30
  • 1
    I have to apologize for my poor English skill and also apologize that my answer was not useful. In your question, you say `For example using this Excel CSV like format input:`. But, in your script, it seems that you are trying to retrieve the values from Google Spreadsheet. From this situation, unfortunately, I cannot understand your current situation. I apologize for this. Can I ask you about the detail of your input and output situation? First, I would like to correctly understand your question. – Tanaike Jul 18 '23 at 23:57
  • 1
    I would like to clarify some information from your end. How do you use the Excel CSV file in your use case? Do you upload it to your Drive, or do you view it in a spreadsheet file? And what does `Created` in your CSV file mean? How do you apply it to the created calendar event/reminder? – SputnikDrunk2 Jul 19 '23 at 02:32
  • @Tanaike I updated the question and clarified satisfactorily hopefully in the EDIT section. – Lod Jul 19 '23 at 14:22
  • @SputnikDrunk2 I updated the question and clarified satisfactorily hopefully in the EDIT section. – Lod Jul 19 '23 at 14:22
  • Thank you for replying. From your reply, I proposed an answer by supposing that your values have already been imported to the Spreadsheet and the script directly retrieves the values from the Spreadsheet. Please confirm it. If I misunderstood your situation and your expected result, I apologize. – Tanaike Jul 20 '23 at 02:10

2 Answers2

1

Although, unfortunately, I cannot understand but I would like to use the same format as the default Excel CSV file sample sightly augmented as with the 11 columns from the table below:.. If you want to use the values from the Spreadsheet that the values have already been imported, how about the following answer?

So, this answer supposes that your values have already been imported to the Spreadsheet and the script directly retrieves the values from the Spreadsheet. Please be careful about this.

Modification points:

  • About 1st problem:, 2nd Problem: and 3rd Problem:, the methods of isAllDayEvent, setVisibility, and addPopupReminder are for Class CalendarEvent. But, in your script, those are used as the methods of the Class Calendar. I thought that the reason for your current issue might be due to this.
  • And, isAllDayEvent() has no arguments.
  • From your showing table, I thought that if (data[i][11] == "created") continue; might be if (data[i][10] == "created") continue;.
  • And, from if (data[i][10] == "created") continue;, I thought that rangeList.push(R${i + 3}) might be rangeList.push(K${i + 2}).

When these points are reflected in your script, how about the following modification?

Modified script:

function addEvents() {
  var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var lr = ss.getLastRow();
  var cal = CalendarApp.getCalendarById("YOURGOOGLECALENDARIDHERE");
  var data = ss.getRange("A2:K" + lr).getValues();
  var dateANDTimeArr = ss.getRange("A2:K" + lr).getDisplayValues();
  while (data[data.length - 1][0] == '') data.pop();
  var rangeList = [];
  for (var i = 0; i < data.length; i++) {

    if (data[i][10] == "created") continue; // Modified: This is column "K".

    var theEventStartDate = dateANDTimeArr[i][1];
    var startHour = dateANDTimeArr[i][2].substring(0, 2);
    var startMin = dateANDTimeArr[i][2].substring(3, 5);
    var theEventEndDate = dateANDTimeArr[i][3];
    var endHour = dateANDTimeArr[i][4].substring(0, 2);
    var endMin = dateANDTimeArr[i][4].substring(3, 5);
    Logger.log("theEventStartDate : " + theEventStartDate);
    Logger.log("startHour : " + startHour);
    Logger.log("startMin : " + startMin);
    Logger.log("theEventEndDate : " + theEventEndDate);
    Logger.log("endHour : " + endHour);
    Logger.log("endMin : " + endMin);
    var startDate = new Date(theEventStartDate + " " + startHour + ":" + startMin + ":00");
    var endDate = new Date(theEventEndDate + " " + endHour + ":" + endMin + ":00");

    // --- I modified the below script.
    var event = cal[data[i][5] ? "createAllDayEvent" : "createEvent"](data[i][0], startDate, endDate, { location: data[i][7], description: data[i][6] });
    event.setVisibility(CalendarApp.Visibility[data[i][8] ? "PRIVATE" : "DEFAULT"]); // "DEFAULT" might be "PUBLIC"?
    if (data[i][9].toString()) event.addPopupReminder(data[i][9]);
    rangeList.push(`K${i + 2}`);
    // ---

  }
  if (rangeList.length == 0) return;
  ss.getRangeList(rangeList).setValue("created");
}
  • When I tested this modified script using your showing table (I supposes that each date value is used as the date object.), no error occurs. And, I confirmed that 3 events can be created.

Note:

  • In your showing table, if All Day Event is TRUE, please change End Date. Because an error like Exception: Event start date must be before the event end date. occurs. Please be careful about this.

References:

Tanaike
  • 181,128
  • 11
  • 97
  • 165
  • thanks for the great script as well and the important comments. I didn't know the Class Calendar vs the Class CalendarEvent was significant and overlooked that. I'll research the matter further asap thanks! Thanks for the corrections made for the number of column (10 not 11, the array starting from 0 mistake I made before logging the output in script v1). – Lod Jul 20 '23 at 11:01
  • thanks for that other correction ( (`K${i + 2}`) instead of (`R${i + 3}`) ). I wasn't sure what the R and +3 stood for. I see now the R was the last column of the original script and the corresponding K is the last column of the current script. But what does the +2 stands for/do? I'm not sure of what it means as the original question sheet access got removed from the OP https://stackoverflow.com/q/74310410/10789707 – Lod Jul 20 '23 at 11:06
  • that is also very interesting example use: `cal[data[i][5] ? "createAllDayEvent" : "createEvent"] `. Again, array notation with ternary operator statement use, but this time it checks the values in column 5, and instead of checking for `TRUE` and `FALSE` strings, it checks for `createAllDayEvent` and `createEvent` strings. So we must input `createAllDayEvent` instead of `TRUE` and `createEvent` instead of `FALSE` in column 5 for successful execution as so: https://web.archive.org/web/20230720112323/https://i.imgur.com/SFmwkwH.png Thanks a lot for this new use concept! Be well! – Lod Jul 20 '23 at 11:24
  • that is great: `.Visibility[data[i][8] ? "PRIVATE" : "DEFAULT"]` . So the enum can be expressed with the array notation squared brackets. Then the ternary operatior statement checks the column 8 values. If `TRUE`, then "PRIVATE"/index 2 of the enum is selected, else (if `FALSE`), then "DEFAULT"/index 1 of the enum is selected. Very concise and rich script. Thanks for the new way I could learn of using the array notation with the enum. – Lod Jul 20 '23 at 11:29
  • 1
    @Lod Thank you for replying. I apologize that my answer was not useful in your situation. But, I'm glad your issue was resolved. – Tanaike Jul 20 '23 at 11:40
  • Your answer was very useful and it is correct. I would also check it as accepted if there were 2 choices possible. SputnikDrunk2 answer presents the problems with different concepts and approach so it might be more useful to users for variety sake but your answer is as good. – Lod Jul 20 '23 at 11:55
  • 1
    @Lod Thank you for replying. I understood it. – Tanaike Jul 20 '23 at 11:58
  • I think I got the meaning of `+ 2` in `K${i + 2}`. It means on 1st iteration of the loop, start at cell K2 (i.e. K & ( ( `i` == `0`) + 2)). On 2nd iteration of the loop, start at cell K3 (i.e. K & ( ( `i` == `1`) + 2)). on 3rd iteration, K4 (i.e. K & ( ( `i` == `2`) + 2)) etc. until last populated row. Thanks again @Tanaike. – Lod Jul 20 '23 at 12:00
  • 1
    @Lod From your showing script, I understood that `var dateANDTimeArr = ss.getRange("A2:K" + lr).getDisplayValues();` is retrieved from row 2. So, I modified it to ``rangeList.push(`K${i + 2}`);``. – Tanaike Jul 20 '23 at 12:04
  • ah ok that was the original code piece responsible for it. Good reference use. Thanks again. – Lod Jul 20 '23 at 12:06
  • I had a format changes in the time substrings from the importrange formula data which caused the script to throw the `starttime must be before the endtime` error. I slightly modifed it to include a string length check and now it's fixed. here's the modification: https://web.archive.org/web/20230728160937/https://pastecode.io/s/8r5efs35 Thanks again! – Lod Jul 28 '23 at 16:15
1

ALTERNATIVE SUGGESTION

NOTE: If you think your question has been misinterpreted, kindly clarify it again and include examples of your data and desired results.

Based on your question, I understand that you are doing this process:

  1. Add an event or an all-day event based on the Created header from the sheet.

    • If a row has an empty value, add it as an event.
    • Otherwise, do nothing (means the row has already been created).
  2. When creating the event, it will be based on the All Day Event header from the sheet.

    • If the row has a true value, add it as an all-day event.
    • If false, add it as an event.
  3. Once the event or the all-day event has been added, update the row's value under Created header with "created".

I have refactored your script and split it into manageable pieces. You may check the tweaked script below.

Tweaked Script [UPDATED]

function addEvents() {
  var range = SpreadsheetApp.getActive().getActiveSheet().getDataRange();
  var rawData = range.getDisplayValues().filter(row => !row.join('').trim().length <= 0 ); //Filter 'rawData' & not include empty rows.

  //Add events based on the header named 'Created'. If a row has an empty value, it will call the function 'addCurrentEvent'; otherwise, it will be ignored.
  var result = rawData.map(column => {
    return !column[10].match(/created/i) ? column.map((cell, index) => index == 10 ? addCurrentEvent(column) : cell) : column
  });

  //Sets the sheet range (w/ actual rows that have data) with updated values.
  SpreadsheetApp.getActive().getActiveSheet().getRange(range.getA1Notation().replace(/(K)\d+$/, `$1${result.length}`)).setValues(result);
}

function addCurrentEvent(column) {
  var cal = CalendarApp.getCalendarById("••••••••••••••");
  var startDate = new Date(`${column[1]} ${column[2]}`);
  var endDate = new Date(`${column[3]} ${column[4]}`);
  var visibility = column[8].toLowerCase() == 'true' ? CalendarApp.Visibility.PRIVATE : CalendarApp.Visibility.PUBLIC;

  //'addEvent' will be called to run when creating an EVENT.
  const addEvent = (data) => {

    cal.createEvent(data[0], startDate, endDate, {
      location: data[7],
      description: data[6]
    }).setVisibility(visibility).addPopupReminder(parseFloat(data[9]));

    //Log for review
    console.log(`"${data[0]}" event has been added!\n\nDETAILS\n\nStart date: ${startDate}\nEnd date: ${endDate}\nLocation: ${data[7]}\nDescription: ${data[6]}\nPrivacy: ${visibility}`);

    return `created`; //returns a 'created' value once the sheet row has been added
  };

  //'addAllDayEvent' will be called to run when creating an ALL DAY EVENT.
  const addAllDayEvent = (data) => {

    cal.createAllDayEvent(data[0], startDate, {
      location: data[7],
      description: data[6]
    }).setVisibility(visibility).addPopupReminder(parseFloat(data[9]));

    //Log for review
    console.log(`"${data[0]}" all day event has been added!\n\nDETAILS\n\nStart date: ${startDate}\nEnd date: ${endDate}\nLocation: ${data[7]}\nDescription: ${data[6]}\nPrivacy: ${visibility}`);

    return `created`; //returns a 'created' value once the sheet row has been added
  };

  //Check and run the current data if it is an 'All Day Event' or an 'Event'
  return column[5].toLowerCase() == 'true' ? addAllDayEvent(column) : addEvent(column);
}

Demo

  • Dummy Sheet
    E.g. MyEvent2 will be an all day event and should be private.

enter image description here

  • After running the addEvents function:

enter image description here

  • The events have been created:

enter image description here

  • The updated dummy sheet:

enter image description here

  • The MyEvent2 was added as an all day event and it is private.

enter image description here

  • The other events.

enter image description here enter image description here

SputnikDrunk2
  • 3,398
  • 1
  • 5
  • 17
  • 2
    Thanks for the great script and detailed comments. Lots of precious takaways from the syntax and logic uses you make. `Placeholders within single quotes` to join date and time columns. Ternary operator use to check for the harcoded boolean values in the Sheet column 9 for visibility/private Calendar feature. `parseFloat` used to make `.addPopupReminder()` accept the Sheet column 10 values. Separation in 2 execution sub functions according to column 6 boolean values. Very clean and mastered script to learn from with effective comments. Work well as needed. Thanks again! Be well! – Lod Jul 20 '23 at 10:53
  • 1
    @Lod You're welcome. I'm glad this works for you. – SputnikDrunk2 Jul 20 '23 at 12:20
  • Hi @SputnikDrunk2 I got this new issue with the script adding new blank events: https://i.imgur.com/OLADMRX.png I suppose the script executes for extra blank rows at the end of the sheet too (that's strange as it did work before without that issue). If that's that, any suggestion to make it stop at the last non-blank row? I modified the script slightly like this (adding a `clearCreatedColumn()` function to auto-clear the `Created` column, and an extra reminder column `.addPopupReminder(parseFloat(data[10])`): https://pastecode.io/s/q6dfes10 Thanks for your remarks! – Lod Jul 27 '23 at 14:20
  • 1
    That behavior may be related to the `.getDataRange()` method if, by any chance, you have cells in your sheet with empty spaces, and they will be treated by the method as having data, even if they do not. I have updated a line of code on my answer. Hopefully it fixes the matter. – SputnikDrunk2 Jul 27 '23 at 23:25
  • Thanks a lot again @SputnikDrunk2 it fixed it well with Execution Completed notice. Here's the new version with the `2nd reminder` and the `clearContent` function for later reference: https://web.archive.org/web/20230728121803/https://pastecode.io/s/q8cmdfuv Be well! – Lod Jul 28 '23 at 12:23
  • sorry to come back but I noticed a new strange behavior. When the script executes the `addEvents` function it works but in addition it also removes the formulas in the sheet range `A:L`. For example, I have an importrange formula in `A2` and some other arrayformulas in `J2` and `K2` and they get removed after execution (but the data is still there (only it won't import when normally it would when updated by importrange)). Any idea why it does this and how to prevent it? Thanks again for your suggestions. – Lod Jul 28 '23 at 15:24
  • I see, this is due to the `.setValues` method, as it will override cell values with newly updated values from the script. Originally, the sample script was purely based on the sheet you provided (excluding cells with formulas). With that being said, I believe it should be restructured to accommodate the existing formulas in the sheet. – SputnikDrunk2 Aug 01 '23 at 14:19
  • I think that you would need to integrate [`getFormulasR1C1` & `getFormulasR1C1` to preserve the formulas](https://stackoverflow.com/a/61303943) – SputnikDrunk2 Aug 01 '23 at 16:16