Prevent CalendarApp from creating duplicates - google-apps-script

---EDITED---
I think I found a way to solve this without saving IDs anywhere.
Thanks for your replies. Full code below:
// -------------- EDIT CODE BELOW FOR NEW CLIENT ------------
//EDIT CODE IN BETWEEN THESE LINES ------------------- ( more code to be edited below) //
var client = "CLIENTNAME";
var email = "EMAIL";
var calId = "c_7esq0q0d1n06ao5bc7ueqt7dp8#group.calendar.google.com";
//EDIT CODE IN BETWEEN THESE LINES ------------------- ( more code to be edited below)
//Common data for all triggers
// getting data from spreadsheet
var ss = SpreadsheetApp.getActive();
var ssurl = s.getUrl();
var sheet = SpreadsheetApp.getActive().getSheetByName('Projects');
var startRow = 2; // First row of data to process
var numRows = sheet.getLastRow(); //get last row with data
var numCols = sheet.getLastColumn();
var dataRange = sheet.getRange(startRow, 1, numRows, numCols);
var data = dataRange.getValues();
// Triggers below -- Triggers below -- Triggers below -- Triggers below
function AddProjDeadlinesToCalendar() {
//RFQ event creator
//looping through all of the rows
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var rfqColumn = row[10]; // ENTER THE COLUMN NUMBER, STARTING FROM 0, FOR THIS DEADLINE
var rfqTitle = row[0] + ' ready for RFQ.'; // ENTER THE TITLE OF THE EVENT
var rfqDescript = row[0] + ' design files should be ready for RFQs by this date.'; // ENTER THE DESCRIPTION OF THE EVENT
var rfqExpireDateDay = new Date(rfqColumn).getDate();
Logger.log(rfqColumn);
//-------------- Add event to Calendar ----------
if (!isNaN(parseFloat(rfqExpireDateDay)) && isFinite(rfqExpireDateDay)){
// Determines how many events are present in this calendar, from now till the next 365 days, that contains a keyword
var now = new Date();
var inAYear = new Date(now.getTime() + (365 * 24 * 60 * 60 * 1000));
var events = CalendarApp.getCalendarById(calId).getEvents(now, inAYear,
{search: rfqTitle});
Logger.log('Events with same title already present: ' + events.length);
// If there is more than 0, get event's ID and do not create this event.
if (events.length > 0){
for (i=0; i<events.length; i+1){
var eventIDold = events[i];
Logger.log('Event ID already present : ' + eventIDold.getId());
return;
}
}else{
//Creates an all-day event and logs the ID.
var rfqEvent = CalendarApp
.getCalendarById(calId)
.createAllDayEvent(rfqTitle, new Date(rfqColumn),
{description: rfqDescript});
Logger.log('Newly created Event ID: ' + rfqEvent.getId());
}
}
}
}
//-------------- Add event to Calendar ---------- ENDS HERE
It works, but it stops at the first event that is already present on the calendar (return) and will not check for all other rows in the column's array.
Both break and continue lead to an endless loop. My basic knowledge of coding doesn't let me see the problem. I bet it's in plain sight.
-----END EDIT----
I'm need of help for my script that feature the CalendarApp.
I am already using the script below to create new events with a template for the description and title.
---------- Add event to Calendar ----------
if (!isNaN(parseFloat(expireDateDay)) && isFinite(expireDateDay)){
//Creates an all-day event and logs the ID.
var event = CalendarApp
.getCalendarById('calendarIDhere')
.createAllDayEvent(row[0] + ' ready for RFQ.', new Date(rfqColumn),
{description: row[0] + ' exampletexthere'});
Logger.log('Event ID: ' + event.getId());
}
//-------------- Add event to Calendar ----------
The script gets some data from a previously created array (row[0]) and gets the event's date from, again, a previous variable (rfqColumn).
The main script loops through all rows in a specific column (rfqColumn) and creates events for each date it finds in that column. Title and description are gathered from the same row as where the date is found.
I have ready many threads about the CalendarApp and how to prevent duplicates. None of them use this feature as I do.
If possible, I would like to avoid having a sheet with all the calendar's events IDs.
Is it possible to:
Every time a new event is created, check if there's already an event with same title and update it if so?
Thanks in advance to anyone who will spend a minute sharing his expertise.

Solved. Code edit below in case it could help someone.
In my specific case, there could be only one instance with same title, so the for loop can end after first occurrence. (j<1).
if (events.length > 0){
for (j=0; j<1; j++){
var eventIDold = events[j];
Logger.log('Event ID already present : ' + eventIDold.getId());
}

Related

How to use Google App Script to locate unique names and add up their valued numbers

So I used an importGoogleCalendar code to pull my workers' names and hours from Google Calendar to Google Sheets. However, I pulled EVEYRTHING. The names are duplicated a numerous amount of times in multiple rows for each day with their corresponding hours. How can I get one unique name for each worker along with their added up hours for each time their name appears on the sheet to another sheet so that it looks cleaner and easier to look at?
Example: I would like
Name
Hours
Sam
5
Sam
7
Bob
3
Sam
5
Sam
7
Bob
6
Joe
4
To look like
Name
Hours
Sam
24
Bob
9
Joe
4
Here is the code:
function importGoogleCalendar() {
var sheet = SpreadsheetApp.getActiveSheet();
var calendarId = sheet.getRange('B1').getValue().toString();
var calendar = CalendarApp.getCalendarById(calendarId);
// Set filters
var startDate = sheet.getRange('B2').getValue();
var endDate = sheet.getRange('B3').getValue();
var searchText = '';
// Print header
var header = [["Title", "Description", "Start", "End", "Duration"]];
var range = sheet.getRange("A6:E6");
range.setValues(header);
range.setFontWeight("bold")
// Get events based on filters
var events = (searchText == '') ? calendar.getEvents(startDate, endDate) : calendar.getEvents(startDate, endDate, {search: searchText});
// Display events
for (var i=0; i<events.length; i++) {
var row = i+7;
var details = [[events[i].getTitle(), events[i].getDescription(), events[i].getStartTime(), events[i].getEndTime(), '']];
range = sheet.getRange(row,1,1,5);
range.setValues(details);
// Format the Start and End columns
var cell = sheet.getRange(row, 3);
cell.setNumberFormat('mm/dd/yyyy hh:mm');
cell = sheet.getRange(row, 4);
cell.setNumberFormat('mm/dd/yyyy hh:mm');
// Fill the Duration column
cell = sheet.getRange(row, 5);
cell.setFormula('=(HOUR(D' + row + ')+(MINUTE(D' +row+ ')/60))-(HOUR(C' +row+ ')+(MINUTE(C' +row+ ')/60))');
cell.setNumberFormat('0.00');
}
}
I am willing to make another function if need be
Thank You and Stay Safe
I like the approach of using a pivot table (since they are perfect for handling such data). You get totals, and other features for free.
But if you only want to write summary data to your spreadsheet, you can use the following approach:
My starting point follows on from this line in your existing script:
// Get events based on filters
var events = (searchText == '') ? calendar.getEvents(startDate, endDate) : calendar.getEvents(startDate, endDate, {search: searchText});
From there I pass your events array to a new function:
function summarize(events) {
var totalsByName = new Map();
events.forEach((event) => {
let name = event.getTitle();
// duration in seconds (from milliseconds / 1000):
let duration = Math.abs(event.getEndTime() - event.getStartTime()) / 1000.0;
if (totalsByName.has(name)) {
// increment the existing duration for this person:
totalsByName.set(name, totalsByName.get(name) + duration);
} else {
// add the first entry for this person:
totalsByName.set(name, duration);
}
} );
// iterate over each entry in the map:
for (let [name, duration] of totalsByName) {
console.log(name + ' = ' + (duration / 3600.0));
}
}
The function populates a map of results - one entry per person's name.
In my example, all I do is print the data to the console.
console.log(name + ' = ' + (duration / 3600.0)); // convert seconds to hours
But you can instead adapt all your existing code to write this data to the spreadsheet instead, using my name and duration values.
You can apply additional logic to sort by names, if you wish, and round the numeric data to a preferred number of decimal places.

Create Recurring All Day Calendar Event from List of Dates in Google Sheet

I have a list of dates in column A (starting at A2) paired with text for a title in column B (starting at B2). I have my calendar ID listed in cell E2.
I would like to send this data to Google Calendar to create recurring, all-day calendar events. This calendar should update when the spreadsheet is changed.
Here is what I found for you. it's based on my understanding, may be I'm wrong. Just want to help you.
https://www.quora.com/How-do-I-automatically-add-events-to-a-Google-Calendar-from-a-Google-Sheet
or you can use app to do this task for you here is step by step guide
https://zapier.com/apps/google-calendar/tutorials/how-to-create-calendar-events-from-spreadsheet-tutorial
I wrote this small piece of code that creates recurring events using the data in sheets.
I didn't write this in a trigger, so you would have to run this manually. It could be written in an onEdit trigger, but I don't think it would be the best idea, since you would soon end up having mountains of duplicate events, even though this could be avoided by adding some condition that checks whether an event with those characteristics already exists:
function createEvents() {
var sheet = SpreadsheetApp.getActiveSheet();
var lastRow = sheet.getLastRow();
var firstRow = 2;
var firstCol = 1;
var numRows = lastRow - firstRow + 1;
var numCols = 2;
var data = sheet.getRange(firstRow, firstCol, numRows, numCols).getValues();
var calId = sheet.getRange("E2").getValue();
var cal = CalendarApp.getCalendarById(calId);
var recurrence = CalendarApp.newRecurrence().addYearlyRule();
for(var i = 0; i < data.length; i++) {
var title = data[i][1];
var date = new Date(data[i][0]);
var event = cal.createAllDayEventSeries(title, date, recurrence);
}
}
Also, if you wanted to delete previously created events when you create new events, you should keep track of all old events and edit this code a bit, but I'm not sure you want to delete them.
Update:
In case you want to create events when the sheet is edited, without having to run the function manually, I'd recommend using an onEdit trigger that creates an event corresponding to the row that has been written. Additionally, a condition can be added to create the event only if the data in the row is valid (columns A and B are not empty, and the value in column A is a valid Date).
The following function accomplishes all previous actions:
function createEvent(e) {
var sheet = e.source.getActiveSheet();
var range = e.range; // Edited range
var rowIndex = range.getRow(); // Edited row index
var firstCol = 1;
var numCols = 2;
var data = sheet.getRange(rowIndex, firstCol, 1, numCols).getValues()[0];
var title = data[1];
var date = data[0];
// Check whether column A is a valid Date and column B is not empty:
if(Object.prototype.toString.call(date) === '[object Date]' && title != "") {
var calId = sheet.getRange("E2").getValue(); // Get calendar id from cell 'E2'
var cal = CalendarApp.getCalendarById(calId);
var recurrence = CalendarApp.newRecurrence().addYearlyRule();
var event = cal.createAllDayEventSeries(title, date, recurrence); // Create event
}
}
In order to run on edit, this function needs an onEdit trigger. This trigger has to be installed, because a simple trigger cannot access services that require authorization.
You can install this trigger manually by following these steps (check this screenshot if you have problems when configuring the type of trigger).
You can also install this trigger programmatically, as explained here.
Please let me know if that works for you now. I hope this is of any help.

Google Script for Sheets - Skip rows that have incorrect Date formats when adding to Calendar

I need help with this script I use to add data from a Sheet to a Google Calendar. I admit, I found it online, and I edited it to fit my sheet. It would work perfectly, the only problem is the sheet is populated from data entered in a JotForm that we use at my job for people who need to notify us of upcoming events that we need to provide support for.
That being said, not everyone is careful enough to make sure that their event End Time is after their event Start Time. This causes the code to break and not execute due to the incorrect times (you cannot add an event to Google Calendar if your End Time is before your Start Time.
Here is the code I have so far:
function createCalendarEvent() {
var sheet = SpreadsheetApp.getActiveSheet();
var calendar = CalendarApp.getCalendarById('CalendarIDHere');
var startRow = 2; // First row of data to process - 2 exempts my header row
var numRows = sheet.getLastRow(); // Number of rows to process
var numColumns = sheet.getLastColumn();
var dataRange = sheet.getRange(startRow, 1, numRows-1, numColumns);
var data = dataRange.getValues();
var complete = "Done";
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var name = row[1]; //Event Name
var sDate = new Date(row[2]); //Start date/time
var eDate = new Date(row[3]); //End date/time
var attendees = row[4]; //Number of Guests
var location = row[5]; //Event Location
var organizer = row[6]; //Event Organizer
var contact = row[7]; //Organizer e-mail
var department = row[8]; //Department
var setup = row[9]; //Room Set-up Needed
var config = row[10]; //Room Configuration Requested
var rSetup = row[11]; //Details about Room Set-up
var food = row[12]; //Food Services Needed
var additional = row[13]; //Additional Info
var eventID = row[14]; //event marked Done
if (eventID != complete) {
var currentCell = sheet.getRange(startRow + i, numColumns);
calendar.createEvent(name, sDate, eDate,
{
description: 'Department: ' + department + '\r' + 'Organizer: ' + organizer + '\r' + 'Organizer E-mail: ' + contact + '\r' + '\r' + 'Attendees: ' + attendees + '\r' + 'Room Configuration Type: ' + config + '\r' + '\r' + 'Room Set Up, Security, Cleaning details: ' + '\r' + rSetup + '\r' + '\r' + 'Food Services?: ' + food + '\r' + '\r' + 'Additional Information: ' + additional
});
currentCell.setValue(complete);
}
}
}
What can I add to this that will cause the script to check the data and skip any rows that have date/time in the eDate cell before the date/time in the sDate cell?
The essence of the questioner's problem is that "not everyone is careful enough to make sure that their event End Time is after their event Start Time. This causes the code to break and not execute due to the incorrect times (you cannot add an event to Google Calendar if your End Time is before your Start Time."
At face value, this is not an issue about Google Sheets, but rather about conditional logic in Jotform. Fortunately JotForm can manage this. Conditions-for-start-and-end-dates explains exactly how to edit JotForm to ensure the start date and end date are in sync. However, it is possible that the questioner doesn't have the ability to edit JotForm so, until then, we'll focus on response data coming through in Google Sheets logic.
The code below is an extract to be inserted after the loop begins and before initialising the rest of event variables. This allows the code to evaluate the start and end date, and proceed with processing ONLY if the start date is earlier than the end date.
I have added three if statements and a variable mismatch.
mismatch is initialised at the start of each new event row with a value of zero. In the event that a date comparison highlights a problem, the variable is given a value of 1. This enables the code to test for various errors in the start and end dates. If, after all this, mismatch doesn't have a value of 1 then the processing can continue "as usual", otherwise, the code can loop to the next event.
if #1 - tests whether the start date is greater than the end date; if yes then give a warning, set a new background, and give mismatch a value of 1 (one).
if #2 - tests whether the start date is equal to the end date; if yes then give a warning, set a new background, and give mismatch a value of 1 (one).
if #3 - tests whether mismatch has a value of 1; if no, then continue processing ; if yes, loop back for next event.
for (var i = 0; i < data.length; ++i) {
var mismatch = 0;
var row = data[i];
var sDate = new Date(row[2]); //Start date/time
var eDate = new Date(row[3]); //End date/time
Logger.log("Start = "+sDate+", end = "+eDate);
if ((sDate - eDate) >0){
// "start date is later than end data");
SpreadsheetApp.getUi().alert("AUDIT WARNING: Start date > end date. Script will NOT stop but this request marked in Red. ");
sheet.getRange(i+2,1,1,6).setBackground("red");
mismatch = 1;
}
if ((sDate - eDate) == 0){
// start date and end date are the same");
SpreadsheetApp.getUi().alert("AUDIT WARNING: Start date and end date are the same. Script will NOT stop but this request marked in Orange. ");
sheet.getRange(i+2,1,1,6).setBackground("darkorange");
mismatch = 1;
}
if (mismatch != 1){
//by definition, start date is less than end date.
// insert code to process
}
}
This screenshot shows the effect of processing

Form submission handler creates duplicate Google Calendar events

I modified code from this question to suit my needs. I use Google Forms that fill into Google Sheets, and then the script inserts an event into Google Calendar. I have installed a "form submit" trigger to execute the script. The issue I have is that my code starts with the data in the first row of the sheet, adding the same events every time a new form response is added.
Here is my version:
//push new events to calendar
function pushToCalendar() {
//spreadsheet variables
var sheet = SpreadsheetApp.getActiveSheet();
var lastRow = sheet.getLastRow();
var range = sheet.getRange(2,1,lastRow,16);
var values = range.getValues();
//calendar variables
var defaultCalendar = CalendarApp.getDefaultCalendar()
var numValues = 0;
for (var i = 0; i < values.length; i++) {
//check to see if Start DateTime and End DateTime are filled out
if ((values[i][3]) && (values[i][4])) {
//check if it's been entered before
if (values[i][6] != 'y') {
//create event https://developers.google.com/apps-script/class_calendarapp#createEvent
var newEventTitle = values[i][1] + ' - ' + values[i][2];
var startDay = Utilities.formatDate(new Date(values[i][3]), "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'");
var endDay = Utilities.formatDate(new Date(values[i][4]), "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'");
var newEvent = defaultCalendar.createEvent(newEventTitle, new Date(startDay), new Date(endDay), {location: values[i][2]});
//get ID
var newEventId = newEvent.getId();
//mark as entered, enter ID
sheet.getRange(i + 2, 6).setValue('y');
sheet.getRange(i + 2, 7).setValue(newEventId);
}
}
numValues++;
}
}
Found the solution by myself: on row 20 i changed the code
if (values[i][6] != 'y') {
to
if (!values[i][6]) {
No need to write that 'y' sign. I also changed
sheet.getRange(i+2,6).setValue('y');
to
sheet.getRange(i+2,6).setValue('');
And no duplicates anymore. The code works perfect!
The issue with the original code is that you are reading the wrong column - JavaScript arrays are 0-base, while the spreadsheet columns are 1-base. Column 6 (from getRange(i + 2, 6) is in the array index 5, i.e. values[i][5], not values[i][6]. So you were comparing the newEventId with the string 'y', which was never going to be the same.
As your solution indicates, removing the condition that values[i][6] (aka column 7, where your script writes the created event's ID) is not equal to 'y' (a condition that was always true), and instead testing for any value will appropriately guard the event creation code. Given the presence of the event ID column, the column in which 'y' was written is entirely unnecessary.
If you remove that column from your form response sheet, the code guard would then be:
//check to see if Start DateTime and End DateTime are filled out
if ((values[i][3]) && (values[i][4])) {
//check if it's been entered before, by looking in Column F:
var existingEventId = values[i][5]; // Will be "" (falsy) if not yet added
if (!existingEventId) {
...
// Log the event's ID so we don't make a duplicate:
sheet.getRange(i + 2, 6).setValue(newEventId);
...

Create events in multiple Google Calendars from Spreadsheet

I'm trying to create a sheet that creates events into multiple google calendars from a single google sheet. I am using a sheet modified from the fantastic solution on this post Create Google Calendar Events from Spreadsheet but prevent duplicates from Mogsdad. However I have been triplicating my work to go into 3 different calendars and would like to have my first go at programming. My idea is I would like to go one step further and add a drop down column (labeled status) containing either (Unconfirmed, Save the date, Confirmed) which would then create an even in one or all three calendars named the same as the conditional drop down.
My sheet is arranged as :-
Date | Title | Start Time | End Time | Location | Description | Even ID | Status | Confirmed details | Confirmed Start time | confirmed end time |
As you can see my idea is to have slightly different info in the confirmed calendar than the other two.
The existing code i'm using is
/**
* Adds a custom menu to the active spreadsheet, containing a single menu item
* for invoking the exportEvents() function.
* The onOpen() function, when defined, is automatically invoked whenever the
* spreadsheet is opened.
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Export Events",
functionName : "exportEvents"
}];
sheet.addMenu("Calendar Actions", entries);
};
/**
* Export events from spreadsheet to calendar
*/
function exportEvents() {
var sheet = SpreadsheetApp.getActiveSheet();
var headerRows = 1; // Number of rows of header info (to skip)
var range = sheet.getDataRange();
var data = range.getValues();
var calId = "30kpfnt5jlnooo688qte6ladnk#group.calendar.google.com";
var cal = CalendarApp.getCalendarById(calId);
for (i=0; i<data.length; i++) {
if (i < headerRows) continue; // Skip header row(s)
var row = data[i];
var date = new Date(row[0]); // First column
var title = row[1]; // Second column
var tstart = new Date(row[2]);
tstart.setDate(date.getDate());
tstart.setMonth(date.getMonth());
tstart.setYear(date.getYear());
var tstop = new Date(row[3]);
tstop.setDate(date.getDate());
tstop.setMonth(date.getMonth());
tstop.setYear(date.getYear());
var loc = row[4];
var desc = row[5];
var id = row[6]; // Sixth column == eventId
// Check if event already exists, delete it if it does
try {
var event = cal.getEventSeriesById(id);
event.deleteEventSeries();
row[6] = ''; // Remove event ID
}
catch (e) {
// do nothing - we just want to avoid the exception when event doesn't exist
}
//cal.createEvent(title, new Date("March 3, 2010 08:00:00"), new Date("March 3, 2010 09:00:00"), {description:desc,location:loc});
var newEvent = cal.createEvent(title, tstart, tstop, {description:desc,location:loc}).getId();
row[6] = newEvent; // Update the data array with event ID
debugger;
}
// Record all event IDs to spreadsheet
range.setValues(data);
}
So I realize I need to define the new info to go into the "confirmed" calendar as well as the 2 additional calendars. My issue is I don't know how to fit in a series of if loops to direct events to the 3 calendars. I would also like the calendars to be additive e.g. all events appear in "unconfirmed calendar" events get added to save the date when uprated to that status and then finally appear in "confirmed" when set to that. So a confirmed event appears in all 3 calendars but an unconfirmed only appears there.
I'm virtually brand new to programming so please be nice and excuse my blatant plagarism of others work (thanks Mogsdad) and I appreciate any help!
Welcome to programming! Once you get the hang of it, you'll want to script every Google product you use. :)
If I understand your question correctly, you would like to be able to choose which calendar an event goes into when you run the function exportEvents(). There are several ways to do this, and you don't need any additional loops! You can make use of objects and refer to them by name.
What I would do first, where you currently define cal and calId, is create an object that defines the three calendars like this:
var cal1 = "30kpfnt5jlnooo688qte6ladnk#group.calendar.google.com";
var cal2 = "string url for second calendar";
var cal3 = "string url for third calendar";
var calendars = {
Unconfirmed: CalendarApp.getCalendarById(cal1),
SaveTheDate: CalendarApp.getCalendarById(cal2),
Confirmed: CalendarApp.getCalendarById(cal3)
}
The object calendars now contains the calendar objects for the three calendars such that the key is the status and the value is the object. Then, when you're grabbing the data for each row, add
var cal = row[7];
Now, cal contains the string indicating the status. You can make great use of chaining by making one change to your newEvent definition:
var newEvent = calendars[cal].createEvent(title, tstart, tstop, {description:desc,location:loc}).getId();
What's happening here is calendars[cal] gets the calendar object corresponding to the string in the table, to which you can then add the new event. This does require making a change to your sheet - change the label in your status column from 'Save the Date' to 'SaveTheDate' so it matches the variable name. That should do it!
EDIT
To add the event to multiple calendars, I would use if statements, but you don't need a loop. Something like the following would work:
calendars['Unconfirmed'].createEvent(title... // Add to unconfirmed no matter what
if (cal != 'Unconfirmed'){
calendars['SaveTheDate'].createEvent(title... // Add to SaveTheDate only if not Unconfirmed
}
if (cal == 'Confirmed'){
calendars['Confirmed'].createEvent(title... // Only add to Confirmed if Confirmed
}