I have a GAS script run from a Google Sheet with two functions. The first function downloads calendar event details in a named calendar between two dates. The name of the calendar, the start date and the end date are read in from cells A2, B2, and C2 respectively. This function is working fine:
//Function to list certain events in your GCalendar.
function listMyEvents() {
var sheet = SpreadsheetApp.getActiveSheet();
var calid = sheet.getRange("A2").getValue();
if(calid == null) {
SpreadsheetApp.getActiveSpreadsheet().toast('Please enter a valid calendar name!');
return;
}
var startTime = sheet.getRange("B2").getValue();
if(startTime == 0) {
SpreadsheetApp.getActiveSpreadsheet().toast('Please enter a start date!');
return;
}
if(isNaN(startTime)) {
SpreadsheetApp.getActiveSpreadsheet().toast('Please enter a valid start date!');
return;
}
var endTime = sheet.getRange("C2").getValue();
if(endTime == 0) { //If the cell doesn't contain anything send an error prompt
SpreadsheetApp.getActiveSpreadsheet().toast('Please enter an end date');
return;
}
if(isNaN(endTime)) { //If the cell holds text rather than a number then send an error prompt
SpreadsheetApp.getActiveSpreadsheet().toast('Please enter a valid end date!');
return;
}
var calendar = CalendarApp.getCalendarsByName(calid)[0]; //get the calendar name
var events = calendar.getEvents(startTime, endTime);
var data; //declare a variable to hold all the information.
for(var i = 0; i < events.length;i++){
var event = events[i];
data = [
event.getTitle(),
event.getDescription(),
event.getStartTime(),
event.getEndTime(),
event.getLocation(),
event.getId(),
"N"
];
sheet.appendRow(data);
}
sheet.autoResizeColumn(1);
sheet.autoResizeColumn(2);
sheet.autoResizeColumn(3);
sheet.autoResizeColumn(4);
sheet.autoResizeColumn(5);
sheet.autoResizeColumn(6);
sheet.autoResizeColumn(7);
}
The second function tries to read in the event Id from column F, in order to allow individual events to be amended within the sheet before being written back to Google Calendar. However, the script errors on the line calendarEvent.getId() with the error Cannot call method "getId" of undefined. (line 118, file "Code").
I did read that you need to use the .getEventSeriesById() method - but the events aren't in a series! Is it possible to amend individual events with a script or do I need to delete all events and recreate them with amendments? My code for the second function is here:
function amendCalEvents() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 6; // First row of data to process
var numRows = sheet.getLastRow(); // Number of rows to process
//var maxRows = Math.min(numRows,200); //Limit the number of rows to prevent enormous number of event creations
var calid = sheet.getRange("A2").getValue(); //putting the calendar name in double quotes creates double entries. Who knew?
var dataRange = sheet.getRange(startRow, 1, numRows, 7); //startRow, startCol, endRow, endCol
var data = dataRange.getValues();
var cal = CalendarApp.getCalendarsByName(calid)[0]; //get the calendar name
for (i in data) {
var row = data[i];
var title = row[0]; // column A
var desc = row[1]; // column B
var tstart = row[2]; // column C
var tstop = row[3]; // column D
var loc = row[4]; // column E
var evId = row[5]; // column F
var evDelFlag = row[6]; // column G
var event = cal.calendarEvent.getId(evId);
if (evDelFlag == "N" || evDelFlag == "n") {
event.setTitle(title);
event.setDescription(desc);
event.setTime(tstart, tstop);
event.setLocation(loc);
} else {
cal.getId(evId).deleteEvent;
}
}
}
Any help or pointers are most gratefully accepted.
There are some changes you can do in the function amendCalEvents
1.- numRows variable, you are setting the value of getLasRow but you are not subtracting the rows used for the first information (e.g. if you have just 1 calendar and this calendar has one event, when you run the first function it'll add a new row and this variable will have 3 as value.
so when you u call the function getRange, it will bring the information from 3 rows and not just the one that has the information.
2.- a workaround of using the getEventSeriesById, you can probably call again the function getEvents as you did in the first fucntion.
As you are already looping in the for with a similar amount of data you could access the element inside the array o events and set the desired values.
var event = events[i];
3.- to delete the event now you can call:
event.deleteEvent();
Hope this helps
Related
How to auto send email based Column D "Today"
to Email on Column A with Subject of COlumn B and Body of Columnn C
I found a script quite similar to my condition, but it only send to a static email
Script Source
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Email"); // To only handle the trigger sheet
var startRow = 2; // First row of data to process
var numRows = 2; // Number of rows to process
// Fetch the range of cells A2:B3
var dataRange = sheet.getRange(startRow, 1, numRows, 2)
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
if (row[2] === "Today") { // Trigger only if Column C is "Yes"
var emailAddress = row[0]; // First column
var message = row[1]; // Second column
var subject = "Bday ==" + row[2]; // Add "Yes" although by your trigger logic it will always say yes in the email
MailApp.sendEmail(emailAddress, subject, message);
}
}
}
And is it possile to make it compatible with my previous script
Link Source
this script about Dynamic Dependent Drop Down Lists
function onEdit(event)
{
var maxRows = false;
// Change Settings:
//--------------------------------------------------------------------------------------
var TargetSheet = 'Main'; // name of sheet with data validation
var LogSheet = 'Data1'; // name of sheet with data
var NumOfLevels = 4; // number of levels of data validation
var lcol = 2; // number of column where validation starts; A = 1, B = 2, etc.
var lrow = 2; // number of row where validation starts
var offsets = [1,1,1,2]; // offsets for levels
// ^ means offset column #4 on one position right.
// var maxRows = 500; // to set the last row of validation; delete this row if not needed
// =====================================================================================
SmartDataValidation(event, TargetSheet, LogSheet, NumOfLevels, lcol, lrow, offsets, maxRows);
// Change Settings:
//--------------------------------------------------------------------------------------
var TargetSheet = 'Main'; // name of sheet with data validation
var LogSheet = 'Data2'; // name of sheet with data
var NumOfLevels = 7; // number of levels of data validation
var lcol = 9; // number of column where validation starts; A = 1, B = 2, etc.
var lrow = 2; // number of row where validation starts
var offsets = [1,1,1,1,1,1,1]; // offsets for levels
// var maxRows = 500; // to set the last row of validation, delete this row if not needed
// =====================================================================================
SmartDataValidation(event, TargetSheet, LogSheet, NumOfLevels, lcol, lrow, offsets, maxRows);
}
function SmartDataValidation(event, TargetSheet, LogSheet, NumOfLevels, lcol, lrow, offsets, maxRows)
..... Etc etc
Sorry the script is so long, i got warning "your post mostly code"
i just post some of it here and you can check full script onLink Source
You can change the script as such to check the rows and send email based on value of column D:
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Email"); // To only handle the trigger sheet
var startRow = 2; // First row of data to process
var numRows = sheet.getLastRow()-1; // Number of rows to process
// Fetch the range of cells A2:D
var dataRange = sheet.getRange(startRow, 1, numRows, 4)
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
if (row[3] === "Today") { // Trigger only if Column D is "Today"
var emailAddress = row[0];
var subject = row[1];
var message = row[2];
MailApp.sendEmail(emailAddress, subject, message);
}
}
}
To use this in a trigger similar to your second script you need to create an Installable Trigger and specify the function name. For example, if you want to trigger the sending every 24 hours:
function createTimeDrivenTriggers() {
// Trigger every 24 hours.
ScriptApp.newTrigger('sendEmails')
.timeBased()
.everyHours(24)
.create();
}
I'm currently trying to get this piece of code to send events from a google sheet to a google calendar (Credit to Adam McFarland on this post).
My sheet is currently around 300 rows & growing so to speed things up I've set the range to start at row 248. But this then seems to throw off the part that notes the event as 'done'. It sets value of "In 2 calendar" to rows 2, 3, 4 & 5?!?
Easy solution would be just to set the range to the whole sheet again but I'm still learning. I'd like to learn what exactly here isn't working correctly, and also a bit more about how iteration works.
//mark as entered, enter ID
sheet.getRange(i+2, 32).setValue('In 2 calendar');
Complete code below:
function pushToCalendar() {
//spreadsheet variables
var sheet = SpreadsheetApp.getActiveSheet();
var lastRow = sheet.getLastRow();
var range = sheet.getRange(248,1,lastRow,40);
var values = range.getValues();
var updateRange = sheet.getRange('G1');
var numValues = 0;
for (var i = 0; i < values.length; i++) {
//check to see if name and type are filled out - date is left off because length is "undefined"
if ((values[i][0].length > 0) && (values[i][2].length > 0)) {
//check if it's been entered before
if (values[i][30] != 'In calendar') {
//Declare which calendar ID to use (IGNORE THIS FOR NOW)
var calendar = CalendarApp.getCalendarById('calendarID')
// if (values [i][3] != 'Tropical 2450 Pontoon'){
// var calendar = CalendarApp.getCalendarById('calendarID')
//create event https://developers.google.com/apps-script/class_calendarapp#createEvent
var newEventTitle = values[i][3]+'. '+values[i][2]+'. '+values[i][13]
+'. '+values[i][5]+'/'+values[i][6]+'/'+values[i][7]
+'. '+values[i][18]+' total, '+values[i][25]+' to pay. '+values[i][0];
// var newEvent = calendar.createEvent('hello', Date[i][1], Date[i][5]);
var newEvent = calendar.createEvent(newEventTitle,
//new Date(values[i][6]),
new Date(values[i][32]),
new Date(values[i][33]));
//{guests:'tures.com.au', sendInvites: true});
//mark as entered, enter ID
sheet.getRange(i+2, 32).setValue('In 2 calendar');
} //could edit here with an else statement
}
numValues++;
}
}
This is my first attempt at using scripting in Google Sheets. I'm trying to create a script that will create calendar events based on my data in my spreadsheet. I pulled from an example posted here.
This code will pull the data from my sheet, check if the event exists, if it doesn't it will create the event, if it does it will delete the event and create a new one (i needed to be able to edit the times of an event) this is all based on the event id that is recorded to the sheet upon event creation.
This works great, but when it records the event id the code records all data back to the spreadsheet which then overwrites cells with data instead of the formulas i'm using.
how can I update this code so it only updates the eventID and not everything?
/**
* 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 lc = sheet.getLastColumn()
var data = range.getValues();
var calId = "715rn8uj1trqc31e6mepgsnk7k#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[13]; // Fourteenth 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 = "1117 w 24th Street, Los Angeles, CA 90007";
var desc = row[14];
var id = row[15]; // Sisteenth column == eventId
// Check if event already exists, delete it if it does
try {
var event = cal.getEventById(id)
event.deleteEvent();
row[15] = ''; // 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[15] = newEvent; // Update the data array with event ID
debugger;
// Record all event IDs to spreadsheet
idrange.setValues(data);
}
}
function testRange () {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getDataRange();
logger.log(data);
}
Since range is range = sheet.getDataRange() you keep reading and overwriting everything on the sheet. Let's only write the Ids. First, make a smaller array of data, which only has the Id column. Then put it in the spreadsheet where it belongs: in 16th column (was 15 in 0-based index, but 16 in 1-based index).
The following should be placed after the for loop, not within it.
// Record all event IDs to spreadsheet
var id_data = data.map(function (row) {
return [row[15]]; // keep only that column
});
sheet.getRange(1, 16, id_data.length, 1).setValues(id_data); // write it in the sheet
I'm getting this error while running this sheet.
Cell reference out of range (line 81, file "genreportSE")
I don't know why it says it's 'out of range'.
I tried to used 'copyvalues'. I saw a script where you can't really "print" a range, but you can create another spreadsheet, copy that range, then print that sheet and delete it.
How should I accomplish this?
function genreportSE() { // This function let us read the value of a cell from a sheet and change the value of another cell in a different sheet
var ss = SpreadsheetApp.getActive(); //ss stands for spreadsheet, this is the active spreadsheet
var clientsheet = ss.getSheetByName('Clientes SE');
var gensheet = ss.getSheetByName('Generador SE');
var clienttable = clientsheet.getDataRange();
var numberofservices = clienttable.getNumRows(); //The number of services in the Clientes sheet
var error1;
var error2;
var rangetocheck1;
var rangetocheck2;
var client;
var clientname;
var i=0;
var reportswitherrors = []; //Array for faulty reports
var email ='jvaldez#galt.mx';
var subject = "Reporte de producción y consumo - " + (new Date()).toString();
var body = "TEXT" ;
for (i=0;i<=2;i++){
gensheet.getRange('B2').setValue(clientsheet.getRange(i+2,1).getValue()); //This will change the cell "B2" in "Generador SE" to the current service number for the report generation
Utilities.sleep(3000); //A timer to let the importdata function get the data from the SE server in miliseconds
client = gensheet.getRange('B4').getValue;
clientname = String(client);
rangetocheck1 = gensheet.getRange('B8:C14').getValues(); //Data range that could present calculation errors ********
rangetocheck2 = gensheet.getRange('H8:H14').getValues(); //Data range that could present calculation errors ********
if(String(rangetocheck1).indexOf('#N/A') == -1) { //This checks if there are any errors in rangetocheck1
error1 = false;
} else {
error1 = true;
};
if(String(rangetocheck2).indexOf('#N/A') == -1) { //This checks if there are any errors in rangetocheck2
error2 = false;
} else{
error2 = true;
};
if(error1||error2){
reportswitherrors.push(clientsheet.getRange(i+2,1).getValue()); //This appends the current service number to the faulty services array
} else {
// Convert individual worksheets to PDF
var newSpreadsheet = SpreadsheetApp.create("Spreadsheet to export",15,60);
newSpreadsheet.getSheetByName('Sheet1').activate();
var newsheet = newSpreadsheet.getSheetByName('Sheet1');
var genRange = gensheet.getRange('A1:H50').copyValuesToRange(newsheet,0,10,0,55)
var pdf = DriveApp.getFileById(newSpreadsheet.getId()).getAs('application/pdf').getBytes();
var attach = {fileName:'Weekly Status.pdf',content:pdf, mimeType:'application/pdf'};
MailApp.sendEmail(email, subject, body, {attachments:[attach]});
DriveApp.getFileById(newSpreadsheet.getId()).setTrashed(true);
}
};
Logger.log(reportswitherrors);
}
It appears that you've got your row & column dimensions flipped between function calls. (Because Google decided to be inconsistent with the order of them...)
This line calls create(name, rows, columns):
var newSpreadsheet = SpreadsheetApp.create("Spreadsheet to export",15,60);
You've created a spreadsheet with 15 rows and 60 columns.
A bit further along, probably on line 81, copyValuesToRange(sheet, column, columnEnd, row, rowEnd) gets invoked:
var genRange = gensheet.getRange('A1:H50').copyValuesToRange(newsheet,0,10,0,55)
Using this great answer, I've managed to alter it to create a script to export events from a Google Spreadsheet to Google Calendar.
Create Google Calendar Events from Spreadsheet but prevent duplicates
I then got some great advice, and worked out that it wasn't populating the eventID column due to the error I was getting - "Exceeded maximum execution time" - due to the large number of rows (up to 1000).
Create Google Calendar events from a Google Spreadsheet - script is creating duplicates
I've been looking through answers to try and work out a way to get around this, but can't seem to work out an answer! Apologies - I'm quite new to all this.
Can anyone point me in the right direction, as to how I can either force the script to process beyond the 5 minutes, or anything else?
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 = 2; // Number of rows of header info (to skip)
var range = sheet.getDataRange();
var data = range.getValues();
var calId = "pma5g2rd5cft4lird345j7pke8#group.calendar.google.com";// use default claendar for tests
var cal = CalendarApp.getCalendarById(calId);
for (i in data) {
if (i < headerRows) continue; // Skip header row(s)
var row = data[i];
var date = new Date(row[12]); // WHC
var title = row[18]; // WHC Title
var tstart = setTimeToDate(date,row[15]);// start time
var tstop = setTimeToDate(date,row[16]);// end time
Logger.log('date = '+date+'tstart = '+tstart+' tstop = '+tstop);
var id = row[17]; //EventID WHC
// Check if event already exists, update it if it does
try {
var event = cal.getEventSeriesById(id);
event.setTitle('got you');// this is to "force error" if the event does not exist, il will never show for real ;-)
}catch(e){
var newEvent = cal.createEvent(title, tstart, tstop); // create a "normal" event
row[17] = newEvent.getId(); // Update the data array with event ID
Logger.log('event created');// while debugging
var event = cal.getEventSeriesById(row[17]);// make it an event Serie
}
event.setTitle(title);
}
// Record all event IDs to spreadsheet
range.setValues(data);
}
function setTimeToDate(date,time){
var t = new Date(time);
var hour = t.getHours();
var min = t.getMinutes();
var sec = t.getSeconds();
var dateMod = new Date(date.setHours(hour,min,sec,0))
return dateMod;
}
The idea is to count the time the script is taking inside the main function and to interrupt it when we reach the limit.
We have to store the row number where we interrupt the script and continue from there on the next run.
Since we don't want to do that manually (how lazy we are :-) we'll set up a trigger to run it every 5 minutes.
Below is a full script.
It will send you an email on each run to tell you the progress... you'll have to remove this line after your test of course (unless you like receiving emails every 5 minutes from yourself !)
You will have to change the calendar ID, the row distribution (I tested it on a sheet with less column than yours) but that will be fairly easy.
function createEventsWithBatch() {
// check if the script runs for the first time or not,
// if so, create the trigger and PropertiesService.getScriptProperties() the script will use
// a start index and a total counter for processed items
// else continue the task
if(PropertiesService.getScriptProperties().getKeys().length==0){
PropertiesService.getScriptProperties().setProperties({'itemsprocessed':0});
ScriptApp.newTrigger('createEventsWithBatch').timeBased().everyMinutes(5).create();
}
// initialize all variables when we start a new task, "notFinished" is the main loop condition
var itemsProcessed = Number(PropertiesService.getScriptProperties().getProperty('itemsprocessed'));
var startTime = new Date().getTime();
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 = "h22nevo15tm0nojb6ul4hu7ft8#group.calendar.google.com";
var cal = CalendarApp.getCalendarById(calId);
for (var i = itemsProcessed ; 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 = setTimeToDate(date,row[2]);
var tstop = setTimeToDate(date,row[3]);
// Logger.log('date = '+date+'tstart = '+tstart+' tstop = '+tstop);
var loc = row[4];
var desc = row[5];
var type = row[6];
var times = row[7]
var id = row[8];
// Check if event already exists, update it if it does
try {
var event = cal.getEventSeriesById(id);
event.setTitle('got you');
}catch(e){
var newEvent = cal.createEvent(title, tstart, tstop, {description:desc,location:loc});
row[8] = newEvent.getId(); // Update the data array with event ID
// Logger.log('event created');
var event = cal.getEventSeriesById(row[8]);
}
event.setTitle(title);
event.setDescription(desc);
event.setLocation(loc);
if(type=='PM'){
var recurrence = CalendarApp.newRecurrence().addMonthlyRule().times(times);
event.setRecurrence(recurrence, tstart, tstop);
}else if(type=='PW'){
var recurrence = CalendarApp.newRecurrence().addWeeklyRule().times(times)
event.setRecurrence(recurrence, tstart, tstop);
}
data[i] = row ;
Logger.log(i+' '+new Date().getTime()-startTime)
if(new Date().getTime()-startTime > 240000){ // if > 4 minutes
var processed = i+1;// save usefull variable
PropertiesService.getScriptProperties().setProperties({'itemsprocessed':processed});
range.setValues(data);
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),'progress sheet to cal','item processed : '+processed);
return;
}
}
range.setValues(data);// this time we are done !
killTrigger();// delete the trigger
PropertiesService.getScriptProperties().deleteAllProperties(); // clean up properties
}
function setTimeToDate(date,time){
var t = new Date(time);
var hour = t.getHours();
var min = t.getMinutes();
var sec = t.getSeconds();
var dateMod = new Date(date.setHours(hour,min,sec,0))
return dateMod;
}
function killTrigger(){
var trigger = ScriptApp.getProjectTriggers()[0];
ScriptApp.deleteTrigger(trigger);
}
There are various GAS libraries that help you get past the 5 minute execution timeout, using the same idea as #Serge suggested. I personally use the Continuous Batch Library.