Writing to a cell based on todays date - google-apps-script

I am learning coding in Sheets, and have already jigsawed together a couple of games (based off very limited coding knowledge. Very fun to see it come together
Next task:
I am logging my hours spent doing "Growth" things (like learning, exercise, work etc.) vs "Leisure" things (like TV, games, FB scrolling etc). Up until now I just scroll down to today's date and add in the numbers manually. Would be fun to have a button at the top of my sheet that just adds hours to the "Growth" or "Leisure" cells for today.
I have used formulae in the cell G371 to reveal the A1 notation of my target cell, hoping I can use this info in a script to write to that cell:
=substitute(Cell("address",vlookup(A1,A2:D367,3)),"$","")
Where cell A1 contains =today()
and A2:A367 are all the dates in the year.
and columns C and D hold numbers for growth and leisure respectively.
Then I tried this code to try to see how to write to a cell. I realise I am probably missing some very fundamental knowledge...
var ss = SpreadsheetApp.getActiveSpreadsheet();
var cellname = ss.getRange('G371') // makes cellname refer to G371
var input = cellname.getValue() // makes 'input' return the contents of G371, in today's case its 'C120'
var test = ss.getRange(input) // hopefully reads 'C120' instead of "input"
test.setValue(1) // setting value to 1 for now, as I'm just testing cell
// referencing. later I will work on figuring out the
// math for the buttons. I'm not there yet.
So it didn't work. apparently the problem is with ss.getRange(input) but I don't know what to do about that.
So my question is how to write to a cell that is named in another cell?
hope it makes sense
Thanks!
PS Here is a copy of the sheet
https://docs.google.com/spreadsheets/d/1KkoCb8kY1XICMeB9bx65HXsldCS-fa75xJCaU52WGg8/edit?usp=sharing

The formula in the cell might be messing with the value you retrieve. To make sure that does not happen, you can use getDisplayValue() instead of getValue().
Another way:
Also, if you just want to retrieve the cell where to write today's growth (or leisure), you don't need to follow this roundabout way of creating a formula in another cell to reference this one. You can do this instead (check comments):
function todayGrowthCell() {
var sheet = SpreadsheetApp.getActiveSheet();
var date = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "d/M"); // Format today's date as in spreadsheet, in order to compare
var firstRow = 2; // Row where days start
var column = 3; // Column C (Growth)
var dates = sheet.getRange(2, 1, 366).getDisplayValues().map(row => row[0]); // Retrieve columns with dates (its display value, in order to compare with today's date, which is formatted accordingly)
var row = dates.indexOf(date) + firstRow; // Today's row
var cell = sheet.getRange(row, column); // Today's growth cell
cell.setValue(1); // Set value to today's growth cell
}

Related

How to create Work Shifts clock AppScript

I am learning right now scripts functionally in Google Sheet, however, can't twist my head around constructing a very simple App script.
I have the following table (Snoopi Tab)
https://docs.google.com/spreadsheets/d/1l6nYBAqB1GWoMkIOwlykhiuMpaXdWHTo7UhgZdq6hT8/edit?usp=sharing
I want it to do this simple action:
EXAMPLE: If today is not Sunday or Saturday and the date is 14.2.14 and cell BF5 is
---> go down 3 rows and paste current time "Clocking in" working-shift
When button "IN" is clicked:
If (TODAYDATE = Value in cell in row 5) & (row 3 ==!"S") both true
Set current time in (same column just row 8)
Same with "OUT" button, but this I'll try to figure by myself.
The other answer is acceptable, but is very resource intensive and have a lot of loops to do resulting to very slow execution time especially when it gets later on the year since it will loop all those dates.
Also, the run you did on the other answer did finish successfully but didn't write anything due to it missing the actual date value. This might have been caused by a timezone issue, or by only modifying the actual date while getting the raw time of the cell value.
A better alternative would be to make use of the 4th row where it contains x value when the date is equal to the current date. By using that, you wouldn't need to loop thus resulting in faster execution time and wouldn't need to convert time thus making it safer. As long as row 4 is populated on all columns (which your problem is), there should be no issue of using this script.
Script:
function WorkClock() {
var currentDate = new Date();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
// you only need 3rd and 4th row of data
var data = sheet.getRange("E3:NE4").getValues();
// 4th row contains 'x' when today matches the column, find that index
var indexToday = data[1].indexOf('x');
// if that column's row 3 is not 'S'
if(data[0][indexToday] != 'S')
// write the time on row 8
sheet.getRange(8, indexToday + 5).setValue(Utilities.formatDate(currentDate, ss.getSpreadsheetTimeZone(), 'HH:mm'));
}
Output:
Note:
Timezone used is based on the spreadsheet's timezone which is GMT-8. Wherever the user is, it will use GMT-8, not its local time which should be helpful in some cases.
Performance difference between this and looping all dates would be vast if we are now dealing with the later months of the year (e.g. November, December)
For the OUT button, create another function by duplicating the current function. Then replace where you write the time. Instead of row 8 (Start), write it in row 10 (Finish).
function myFunction() {
var actualDate = new Date(new Date().setHours(0, 0, 0, 0)).getTime();
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getRange("E3:NE6").getValues();
for(var i = 0; i < data[0].length; i++) {
if (data[2][i].getTime() === actualDate) {
if (data[0][i] !== "S") {
sheet.getRange(8, (5+i)).setValue(new Date().getHours() + ":" + new Date().getMinutes());
}
break;
}
}
}

Paste formula as value on a certain date

I've been digging around on line for a couple of hours now to no avail. I'm trying to write a formula(which I assume is only possible through AppScript), that based on a certain date, will re-paste contents in a cell as values only.
The issue I've had with other scripts is that in my situation I have several different values that need to be checked. I've set up a continuously updating calendar that pulls from an array with listed dates for each item. However, in that array, the row gets moved into an archive once it hits todays date. Thus, they get deleted from the calendar too.
If there is another solution to this, like pasting based off conditional formatting(this would then keep the formulas that haven't found a value yet, and paste ones that have been found as values only), that would be great too.
Any help or advice would be greatly appreciated. If you need any other info, please ask.
EDIT:
The spreadsheet link is in comments. Attached are a before and after picture to help visualize whats going on here. Since the calendar is pulling from the active sheet, and rows on the active sheet go to "archive" when J2-Today() is -1(yesterday), those then disappear from the calendar. I've tried to make a mixed sheet with both archive and active, but I get duplicates as a frequent issue.
See code below:
function archiveRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var mainSheet = ss.getSheetByName("Sheet1");
var archiveSheet = ss.getSheetByName("Sheet2");
// last row should be on the data itself, same with column
var lastRow = mainSheet.getLastRow();
var lastCol = mainSheet.getLastColumn();
// set time to 0 so we can only compare dates
var dateToday = new Date().setHours(0, 0, 0, 0);
var range = mainSheet.getRange(1, 1, lastRow, lastCol);
var values = range.getValues();
// remove header to only process data
var header = values.shift();
var indices = [], difference = [];
// remove dates that are earlier than today
// collect rows and indices data when condition is not met
values.forEach(function (row, index) {
if (row[9] >= dateToday)
return true;
difference.push(row);
indices.push(index);
});
// remove the row with earlier date
// offset with 2 due to 0-indexing and header
indices.forEach(index => mainSheet.deleteRow(index + 2));
// all rows that were deleted are copied to archive sheet
difference.forEach(row => archiveSheet.appendRow(row));
}
Sample Data:
Output:
Note:
If you need to have this run on a daily basis, Time Driven trigger is your friend. Just have it set on a daily basis triggering archiveRows
Make sure that the main sheet only contains the data. (I see your sheet has rows containing 9AM values below. Make sure to clear those as the script will mistakenly include it on the processing)
Adjust date condition if needed.

Sending Emails a day before a specified date based on a list of dates

I have a table in google sheets with several columns. I need to send an email reminder a day before dates specified in one of the columns.
My idea of how it should work:
Take date column
Loop through each date and subtract 1 day from it to get the reminder date.
Filter through by comparing "today's" date with the reminder date
Send emails with all rows that meet the filter criteria
I have tried the following:
function StatusAlert() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const ws = ss.getSheetByName("On hold procedure");
const lr = ws.getLastRow();
var data = ws.getRange(2, 2, lr, 8).getDisplayValues();
var today = new Date();
var exclusiondate = ws.getRange(2,8,lr).getDisplayValues();
for (var i = 0; i < data.length; i++) {
var alertdate = new Date(exclusiondate[I] - 1)
}
I'm struggling with referencing the date column and then applying the date math to it. Also, I am not quite sure that I needed the second getDisplayValues call (I was trying to reference the date column I need specifically).
Because Even if I manage to apply the change to every row based off of the second getDisplayValues call, I won't be able to go onto the next step of filtering the entire table and sending the relevant rows via email.
So ideally I would like to be able to reference the specific date column from the entire range, and apply date math to it.
Does anyone have any idea how I can best proceed on this?
Regards,
Michael
Try this:
function StatusAlert() {
const ss=SpreadsheetApp.getActive();
const ws=ss.getSheetByName("On hold procedure");
var data=ws.getRange(2,2,ws.getLastRow()-1,8).getValues();
var today=new Date();//add this `valueOf()` if you want to compare with another date value
var xd=ws.getRange(2,8,ws.getLastRow()-1).getValues();
for (var i=0;i<data.length;i++) {
var dt=new Date(xd[i][0]);
var alertdate=new Date(dt.getFullYear(),dt.getMonth(),dt.getDate()-1);//subtract off one day
}
}
If you want to compare today and alertdate then I would use valueOf() for both of them and then they are just numbers.
I would refer you to this post. I would use getValues instead of getDisplayValues because it returns a more reliable value. For example, if you change the formatting of your date in the spreadsheet, it will break the code.
Lastly, JavaScript is case sensitive do not capitalize "I" in the last line of code.

Google Sheets (Google Finance) - Get stock price once and track

I would like to, upon data being entered into a new row (from external sheet - date, time, stock ticker), use google finance to get the current price of the stock into a cell and then no longer update the price in that cell. In the cell next to it, I want to track the price and in the cell next to that, I want to track the high, since creation (the highest value of the 'current price' cell).
Here is my sheet https://docs.google.com/spreadsheets/d/1EUU1bZnIfBatNI8H9pPk202PCD1g8wWXt5M0wx6S-jM/edit?usp=sharing
All I've got so far is a high value tracker that runs on the first row only for the right cells. Embarrasingly enough I can't figure out how to apply it to the entire columns.
So in summary, date time and stock will be entered into column A B and C. When that happens, I want to get the current price of the stock in C, and have that number no longer update. In D I want the price to be tracked, like Google Finance normally functions, and in E, the highest value of the D, in the same row.
That's the goal. Any help would be very much appreciated :)
All I've got so far is a high value tracker that runs on the first row
only for the right cells. Embarrasingly enough I can't figure out how
to apply it to the entire columns.
The code bound to your spreadsheet is
function onEdit() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("D2:E2");
var values = range.getValues()[0];
range.setValues([[values[0], Math.max(values[0], values[1])]]);
}
What does this code do?
It sets the range to work with always to D2:E2 - no matter what the actual edited range is
It works only with the first row of the range (range.getValues()[0])
It compares the 0 and 1 columns of the range (e.g. columns D and E) and assigns the value of column D back to column D (is it necessary?) and the higher of the two values to column E
How to modify your code?
It is not quite clear from your description how column D is populated and what you want to do with column F, but to give your general advice:
If you want your function to run on all rows:
Modify your range and expand it until the last row. Subsequently loop
through all rows:
function onEdit() {
var sheet = SpreadsheetApp.getActiveSheet();
var lastRow = sheet.getLastRow();
var range = sheet.getRange("D2:E"+lastRow);
for (var i = 0; i < lastRow-1; i++){
var values = range.getValues()[i];
range.setValues([[values[0], Math.max(values[0], values[1])]]);
}
}
If you want you code to run only on the currently edited row:
Use the event object e.range to find out which is the edited row and work on this row:
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var range = e.range;
var row = range.getRow();
var values = sheet.getRange(row, 4, 1, 2).getValues()[0];
range.setValues([[values[0], Math.max(values[0], values[1])]]);
}
Note: getRange(row, 4, 1, 2) is the notation to get the range starting with the defined row and column 4 (D), 1 row long and two columns wide, see here.
IMPORTANT: If your sheet is being populated automatically from an
external sheet - the onEdit trigger will not work for you (it only
fires on manual, human-made edits). In this case you will need a
workaround as described here.

Set Google Script to retrieve the value of a certain cell and record that every day/week

I am using a Google Sheet to track my portfolio and I have a cell that calculates the total current market value of my portfolio from various parts of the sheet.
I am trying to see if it is possible to create a script that will record the date as well as the market value (the value only, not the formula) automatically from this cell every day. I know it is possible to set a trigger to run the code weekly/daily, but am having trouble with the code itself.
I can think of 2 approaches. I wasn't sure how to code the second implementation, and what I have so far for the first implementation is below.
1) (First, use Cell A1 as a counter. Set initial value to, say, 3.)
function recordValue() {
//read the counter in Cell A1
var counter = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ThisSheet").getRange("A1")
//record current date in cell B&(contents of cell A1)
var dateCell = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ThisSheet").getRange("B"&counter)
outputRange.setValues(Utilities.formatDate(new Date(), Session.getTimeZone(), 'MM-dd-yyyy'));
//read the current market value in 'Summary'!A1 and record it in cell C&(contents of cell A1)
var marketValue = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Summary").getRange("A1:A1");
var outputCell = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ThisSheet").getRange("C"&counter);
outputCell.setValues(marketValue);
//add 1 to the counter in cell A1
counter.setValues(counter + 1);
};
2) Record the current date in the cell directly below, then read the current market value in 'SheetA'!A1 and record it in the cell to the right of the date. Then create a new, empty row directly below (ie pushing the recently written data down by one row)
I'm new to Google Sheets and don't have much programming knowledge. Will either of the above approaches work (and work well with a time-driven trigger)? What's wrong with the current code I have for the first implemetation?
Thanks!
I made some changes in your code because you don't use the getValue() .
I don't really understand exactly what you want to do but the code below works :
function recordValue() {
//read the counter in Cell A1
var counter = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ThisSheet").getRange("A1");
//record current date in cell B&(contents of cell A1)
var dateCell = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ThisSheet").getRange("B"+counter.getValue());
dateCell.setValue(new Date());
//read the current market value in 'Summary'!A1 and record it in cell C&(contents of cell A1)
var marketValue = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Summary").getRange("A1");
var outputCell = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ThisSheet").getRange("C"+counter.getValue());
outputCell.setValue(marketValue.getValue());
//add 1 to the counter in cell A1
counter.setValue(counter.getValue() + 1);
}