I've been struggling with this for a couple weeks now. I have posted a question here a couple days a go but still i didn't got and solution yet. Probably because in fact it was a 2-3 tasks question and not so well explained by my fault. Thus i decided to post another one with a one (of the 2-3) more simple task.
So, i have the script below which deletes in the sample sheet the columns dates before today's date. The script works fine but i have two issues.
First... If the number of the old dates/columns is one it works fine. It deletes only the day before and NOT the first 3 columns (A,B & C in the sample sheet) which i want to keep. If they are more the one old dates then the script deletes several columns/dates before and after today's date. What i want is to delete all the columns/dates ONLY before today's date and NOT the first 3 columns (A,B & C in the sample sheet) which i want to keep.
The second issue is kinda weird. Currently i am running the script from a custom menu ("My Tools" in the sample sheet). Now, when run the script from the custom menu or with the "Run" button in script editor it works. When i try to add a time-driven trigger (not programmatically. i don't know how) so to run it once every 24 hours, then i get a script error with this message: Exception: Range not found at deleteColumnsDate(Code:14:49).
This is a sample of my working sheet: https://docs.google.com/spreadsheets/d/1NJvcLxwc96411-Sl_aTu1d-J5gQvqlZjPUQbWZoRqBM/edit?usp=sharing
And this is the script that i am currently using:
function onOpen()
{
var ui = SpreadsheetApp.getUi();
ui.createMenu('My Tools')
.addItem('Delete Older Dates','deleteColumnsDate')
.addToUi();
}
function deleteColumnsDate(row)
{
var row = (typeof(row) !== 'undefined') ? row : '3';
var day = 86400000;
var today = new Date().getTime();
var rng = SpreadsheetApp.getActiveSheet().getRange(row + ':' + row);
var rngA = rng.getValues();
for(var i = 1; i < rngA[0].length ;i++)
{
if(isDate(rngA[0][i]) && (((today - new Date(rngA[0][i]).getTime())/day) > 1 ))
{
SpreadsheetApp.getActiveSheet().deleteColumns(i + 1);
}
}
}
function isDate (x)
{
return (null != x) && !isNaN(x) && ("undefined" !== typeof x.getDate);
}
So, what am i doing wrong here?. Is it possible to make this process to run fully automatically every 24 hours?
Thank you in advance
Nessus
When you put a time trigger, you can't use getActiveSheet(). Instead you should use SpreadsheetApp.openById( [ID] )
You will find your ID inside URL of the document like : docs.google.com/spreadsheets/d/abc1234567/edit#gid=0
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#openbyidid
EDIT:
As Cooper mentionned it, I forgot something.
Try this way :
var ss = SpreadsheetApp.openById( [SHEET ID] );
var sh = ss.getSheetByName( [SHEET NAME] ); //delete column here
var rng = sh.getRange(row + ':' + row);
Complete function:
function deleteColumnsDate() {
var day = 86400000;
var today = new Date().getTime();
var ss = SpreadsheetApp.openById(" [SHEET ID]");
var sh = ss.getSheetByName(" [SHEET NAME] ");
var rng = sh.getRange('A1:Z1'); // change if you need larger
var rngA = rng.getValues();
var nb_to_delete = 0;
for(var i = 1; i < rngA[0].length ;i++) { // start colonne B
if(isDate(rngA[0][i]) && (((today - new Date(rngA[0][i]).getTime())/day) > 1 )){
nb_to_delete++;
}
}
sh.deleteColumns(2, nb_to_delete);
}
Related
Disclaimer: I'm a Google Apps Script newbie.
I'm trying to create a timesheet in Google Sheets that lets a user clock in & clock out to log hours on a given project. I've borrowed code from a YouTube video on the general structure of setting the whole thing up.
Here's what the blank time sheet looks like. It's pretty basic:
I've created a user button (off to the right) where the user presses "Start" and cell A2 will input a timestamp. Then the user can press an "End" button, and a second timestamp, this time in B2, will appear, along with a simple calculation in C2 that measures the delta in the two timestamps, thus giving a duration of time spent on a given task or project. Here's what it looks like:
When the user needs to press "Start" again, a new timestamp appears in cell A3, and so on so forth, along with a new delta calculation for each new row.
Problem: I'm unable to get the simple delta calculation in column C to increment down each new rows so that the setFormula function doesn't contain hardcoded references to cells A2 & B2. See below code for what I have so far:
function setValue(cellName, value) {
SpreadsheetApp.getActiveSpreadsheet().getRange(cellName).setValue(value);
}
function getValue(cellName) {
return SpreadsheetApp.getActiveSpreadsheet().getRange(cellName).getValue();
}
function getNextRow() {
return SpreadsheetApp.getActiveSpreadsheet().getLastRow() + 1;
}
function addStartRecord (a) {
var row = getNextRow();
setValue('A' + row, a);
}
function addEndRecord (b, c) {
var row = getNextRow()-1;
setValue('B' + row, b);
setValue('C' + row, c);
}
function punchIn() {
addSRecord(new Date());
}
function punchOut() {
addERecord(new Date(), '=B2-A2');
}
The problem is with the punchOut() function there at the bottom. Any idea on the best way to increment this delta calculator down each new row?
Note: I saw a pretty good answer to a similar question here, but the code is throwing an error in the script editor after the line containing data[i] = ['=A' + i+1.toString() + ' + 1 ' ]. Also, I don't want to set a definitive last row for the delta calculation (such as 20 in this example). I'd want the user to be able to record as many new start/end times for a project as they'd want.
Edit: Here's a link to the timesheet so you can test the code.
Try modifying your punchOut method like this:
function punchOut() {
var ss = SpreadsheetApp.getActiveSheet();
var row = ss.getLastRow();
addEndRecord(new Date(), '=B' + row + '-A' + row);
}
I tested it in the sheet and it worked well.
setFormula() - this enables you to describe the formula to be inserted into column C.
The following is two simple functions that handle "Punch in" and "Punch Out" (with its calculation).
function so5695101401in() {
// punchin
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lR = sheet.getLastRow();
// Logger.log("DEBUG: the last row is "+lR);
var punchinRange = sheet.getRange(lR+1, 1);
// Logger.log("DEBUG: the punchinRange = "+punchinRange.getA1Notation());
punchinRange.setValue(new Date());
}
function so5695101401out() {
// punchout
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lR = sheet.getLastRow();
//Logger.log("DEBUG: the last row is "+lR);
var punchoutRange = sheet.getRange(lR, 2);
// Logger.log("DEBUG: the punchoutRange = "+punchoutRange.getA1Notation());
punchoutRange.setValue(new Date());
var timeElapsed = sheet.getRange(lR, 3).setNumberFormat("hh:mm:ss");
timeElapsed.setFormula("=B2-A2");
}
setFormula
I use a workaround for this problem, via app script copy the cell with the formula to de new row or range!.
for you problem:
var formula1 = sheetDatos.getRange(lastRow, 3); //get the formula
var copyRange = sheetDatos.getRange(lastRow+1, 3);
formula1.copyTo(copyRange);
for me is more easy in this way, try to do in sheet to understand how this work.
you need a initial formula to go in this way ;)
I have a Google spreadsheet with multiple sheets. I would like the cursor to be set to a particular cell onOpen for all tabs. Each cell of the first row represents a week of the year, start to finish, for the whole year. I've written some code to have that particular column (week of the year) preselected on row 5 when opening.
Currently, only the last sheet has the cell selected that I want. This is because setActiveCell can only be used for one cell at a time I guess. I have also tried setActiveRange and setActiveSelection to no avail.
I found a question from years ago Here, but the solution does not work for me. Perhaps something has changed since then.
Here is my code:
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
Date.prototype.getWeek = function() {
var onejan = new Date(this.getFullYear(),0,1);
return Math.ceil((((this - onejan) / 86400000) + onejan.getDay()+1)/7);
}
var now = new Date();
var weekNum = now.getWeek();
var sheets = ss.getSheets();
for(var i = 0; i < sheets.length; i++) {
var newRange = sheets[i].getRange(5,weekNum + 1);
sheets[i].setActiveCell(newRange);
}
}
Within the link I provided above, I've tried variances of all of the given solutions, but my code was never exactly the same because I wasn't looking for the exact same things as the OP. For example, using Jacob Jan Tuistra's latest code solution, I came up with:
function setCursor() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
Date.prototype.getWeek = function() {
var onejan = new Date(this.getFullYear(),0,1);
return Math.ceil((((this - onejan) / 86400000) + onejan.getDay()+1)/7);
}
var now = new Date();
var weekNum = now.getWeek();
ss.getSheets().forEach(function (sheet, weekNum) {
Logger.log(weekNum);
sheet.setActiveRange(sheet.getRange(5,weekNum+1));
});
}
Which also only set the last sheet
Looks like you need to make a call to SpreadsheetApp.flush() when you loop over the sheets collection:
function onOpen() {
SpreadsheetApp.getActive().getSheets().forEach(function (s) {
s.setActiveSelection("B4");
SpreadsheetApp.flush(); // Force this update to happen
});
}
My guess is Apps Script's execution engine optimizes the writes to a single call, and in that single call only one active selection can be made. Calling flush forces 1 call per sheet to be updated.
I have the following script.
The archive part works fine, the delete part is causing issues (but it seems fine when I am using sheet.hideRows).
Here is how it should work:
It should look down Column N then check if the date in Column P is older than 1 month. If yes, append row to 'Archive' sheet and then delete the row.
I've probably missed something quite basic, but I've been looking at it all day and nothing is jumping out at me.
Can someone take a look and figure out why it's not quite right?
EDIT: So what it is doing is going deleting rows, but even if they have a ✔ in them. I'm wondering if its getting itself 'confused' by deleting a row and then moving on to the next?
// Following script looks at ticked rows that have a date older than 1 month and copies the data to a Archive tab and then deletes the original row.
function archive() {
var masterSS = SpreadsheetApp.getActiveSpreadsheet();
var sheet = masterSS.getSheetByName('IBM / CC / Cisco');
var archiveSheet = masterSS.getSheetByName('Archive');
var data = sheet.getDataRange().getValues();
var today = new Date();
var count = 0
for ( var r=4; r < data.length; r++) {
count++
var row = data[r];
Logger.log(row[1]);
if (row[1] == "") { break; }
if (row[13] == "✔") {
var date = row[15];
if (date != "" && ((today.getTime() - date.getTime()) > 2629746000)) {
Logger.log('actioning row ' + (r + 1));
archiveSheet.appendRow(row);
sheet.deleteRows(r+1)
//sheet.hideRows(r+1)
}
}
}
}
Thanks guys
I have two functions -
-The first gets Emails - getEmails();
-The second deletes my Trash - deleteForever();
a) I created a custom menu to trigger the above functions separately & they both function.
b) After testing I added a Google Project Trigger using Timed Event
The getEmails Function stops working properly.
When Ran, the script has no errors & the results are:
Data insert into worksheet <- REASON FOR THIS POST-THIS NEVER HAPPENS
Emails move to trash getEmails(); <- SUCCESS-I physically See it
Emails delete w/ deleteForever(); <- SUCCESS-I physically See it
Environment Variations: I tested when spreadsheet & gmail was both open and closed in browser
FIX Attempt: I had used getActiveSheet() * Note it Commented it out *
And replaced with the actual spreadsheet ID thinking that would help my problem but same results.
Any ideas?
Function 1 - (does not work with project trigger timmed event DOES work manually executing)
function getEmails() {
/*var ss = SpreadsheetApp.getActiveSheet();*/
var ss = SpreadsheetApp.openById("1Y-MY-SPREADSHEET-ID-8q9q98k");
var threads = GmailApp.search("is:starred in:WebOppCRM");
var row = ss.getLastRow();
for (var i=0; i<threads.length; i++) {
/* COMMENT OUT TO FOCUS ON PURPOSE OF QUESTION
var start = new Date();
if (isTimeUp_(start)) {
Logger.log("Time up");
break;
}
*/
var messages = threads[i].getMessages();
var newrow = row + 1;
for (var j=0; j<messages.length; j++) {
var msg = messages[j].getPlainBody();
var dat = messages[j].getDate();
var msgParse ='=SPLIT($A'+ (newrow) + ', "|")';
ss.appendRow([msg, dat, msgParse]);
newrow = newrow +1;
Utilities.sleep(100);// pause in the loop for 200 milliseconds
}
threads[i].moveToTrash();
}
Function 2 - does work with project triggers or manual
function deleteForever() {
var threads = GmailApp.search("in:trash");
for (var i = 0; i < threads.length; i++) {
Gmail.Users.Messages.remove("tannerfasteners#gmail.com", threads[i].getId());
}
}
You're missing this function. It's working here, also with a time driven trigger.
function isTimeUp_(start) {
var now = new Date();
return now.getTime() - start.getTime() > 300000; // alert above 3 minutes, to keep it under 5 minutes
}
I really would rather accept an answer by another, however nothing above even addressed my problem as to what I specifically laid out.
The answer was to give focus to the specific sheet when it was not opened by me.
I Realized
code was executing at the top of the script
code and functions below were executing
The script has not instructed WHAT Sheet to use
Kind of obvious now too - when moving from
var ss = SpreadsheetApp.getActiveSheet();
to the following and having the sheet closed
var ss = SpreadsheetApp.openById("-My-Sheet-ID-Removed-");
There was no way the data could write without knowing what sheet to write to.
Modifying my script and adding the line marked as /SOLUTION>/ below worked to set an active sheet
var ss = SpreadsheetApp.openById("-My-ID-Here");
/*SOLUTION>*/ SpreadsheetApp.setActiveSheet(ss.getSheetByName('data'))
var threads = GmailApp.search("is:starred in:WebOppCRM");
var row = ss.getLastRow();
This resolved my specific problem and question as to why it worked when the sheet was open ( because the sheet was active by my being on it ) but partially stopped when sheet was closed ( because the sheet was obviously no longer active )
Hopefully this saves someone some debug time.
I've searched for this topic and have found a few threads - however, none of the answers (usually in the form of scripts) in those topics have proven to be successful to me yet. I guess I'm messing up some variables.
I have a spreadsheet in Google Docs that holds a work roster. In row B1:OI1 is just over a years worth of dates, i.e., B1 = Friday May 1st 2015, B2 = Saturday May 2nd 2015, OI = Wednesday June 1st 2016.
I want the sheet to jump to the roster of either today, or to the monday of the current week, whenever the sheet is opened.
How do I accomplish this?
Thanks in advance for the help!
I suppose you have seen this post where the OP wanted to change the background color of today's date in a sheet, your case is very similar except that - if I understood you well - today is not necessarily present in the sheet.
So what we need is to find the closest date to today ? You mention it has to be a Monday, I didn't go that far, the script below finds the closest date in column A, you will adapt it to your needs by simply adapting the index in the array. (don't forget arrays count from 0)
code :
function onOpen() { // runs automatically
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getActiveSheet();
var data = sh.getDataRange().getValues();
var today = new Date().setHours(0,0,0,0);
var diffref = today;
var diff;
var idx;
for(var n=0;n<data.length;n++){
var date = new Date(data[n][0]).setHours(0,0,0,0);
diff=today-date;
if(diff==0){break}
Logger.log("diffref = "+diffref+" today-date = diff = "+diff);
if(diff < diffref && diff > 0){idx=n ; diffref=diff}
}
if(n==data.length){n=idx}
n++;
sh.getRange(n, 1).activate();
}
Edit :
To check the day of the week (yes, I'm curious by nature ;-) you can try to change the condition like this :
Logger.log("diffref = "+diffref+" today-date = diff = "+diff+" day = "+new Date(date).getDay());
if(diff < diffref && diff > 0 && new Date(date).getDay()==1){idx=n ; diffref=diff}
From my tests it seems to work as expected.
EDIT 2 :
Following your comment, it seems what you're looking for is much simpler :
function onOpen2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getActiveSheet();
var data = sh.getDataRange().getValues();
var today = new Date().setHours(0,0,0,0);
for(var n=0;n<data[0].length;n++){
var date = new Date(data[0][n]).setHours(0,0,0,0);
if(date==today){break};
}
n++;
sh.getRange(1,n).activate();
}
EDIT 3
For some reason that I ignore (please anyone give advise !!) your sheet does not return date values from the date in cells but rather the native spreadsheet values which are integers corresponding to the number of days since december 30,1899...
So what I did is to subtract this offset value from the javascript today variable, divide it by the number of milliseconds in a day (JS counts in milliseconds) and take the integer part of the result.
A bit cumbersome I admit but I didn't find a simpler way...
What is really weird is that in any other sheet I try dates are always returned as dates...
Anyway, for the time being, here is a working code for your spreadsheet :
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getActiveSheet();
var data = sh.getDataRange().getValues();
var offsetToSS = new Date('1899/12/30').setHours(0,0,0,0);
var today = parseInt((new Date().setHours(0,0,0,0)-offsetToSS)/86400000,10)+1;
for(var n=0;n<data[0].length;n++){
var date = data[0][n];
Logger.log("date = "+data[0][n]+" =? "+today);
if(date==today){break};
}
n++;
sh.getRange(1,n).activate();
}
Last note : for a better user experience, add this line of code right after var sh = ss.getActiveSheet();
sh.getRange(1,sh.getLastColumn()).activate();
this will select the last column before activating today's column and will place the selection on the left (near the frozen column in your sheet) which is more "natural".
function onOpen() {
// Activate cell with current date
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(),
data = sheet.getDataRange().getValues(),
now = new Date(),
columnWithDate = 0,
i, delta,
epsilonInMs = 0;
for (i = 0; data.length > i; i++) {
delta = now - new Date(data[i][columnWithDate]);
if (delta < epsilonInMs) break;
}
sheet.getRange(i, columnWithDate + 1).activate();
}
This works when dates are in first column (column A) and you want to go to the row near the current date (you can tweak the numbers to suit):
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var d1 = new Date().getTime();
var a;
for (a = 1; (Math.floor((d1-sheet.getRange(a,1).getValue())/86400000)) > 5; a++)
try { var range = sheet.getRange(a+25,2); }
catch(err) { var range = sheet.getRange(2, 2) }
sheet.setActiveSelection(range);
}