Comparing a cell with a named range for conditional formatting - google-apps-script

I'm importing hourly sensor data from 10 different sensors. In short, I want to be able to see when the hourly data exceeds the average usage for that day of the week and time of the day.
I've created sheets named each of the sensor names ("32022", for example) and each of those sheets is a 26 column matrix of the date, all the hours from 00:00 to 23:00, and a WEEKDAY() function to extract the day of the week from the date.
In a separate sheet ("Daily Usage"), I manually created 10 different matrices of the daily average usage by hour for each sensor, with column A being the day of the week (by name) and the following 24 columns being all the hours of the day. Each one of those matrices has been made into a Named Range called "averageusage_32022".
I am trying to iterate through all the data and identify which data is above average for that particular day/time, and change the background of that cell to red if it exceeds the average.
From what I've gathered, I cannot refer to a named ranged in conditional formatting, which is why I'm looking to solve this programmatically.
var currentsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("32033");
var usagerange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName('averageusage_32033');
//how many rows there are in the sheet
var firstcolumn = currentsheet.getRange("A1:A").getValues();
var bottomrow = firstcolumn.filter(String).length;
//loop through every day, check day of week, iterate through each hour, compare cell to "averageusage" range
for (var i = 2; i < bottomrow; i++){
var day = currentsheet.getRange(i,26).getValue;
for (var j = 2; j < 26; j++){
if (currentsheet.getRange(i,j).getValue() > usagerange.getRange(day,j).getValue()){
currentsheet.getRange(i,j).setBackground("red");
}
}
}
I'm receiving an error of Cannot find function getRange in object Range. I'm assuming that this is because I cannot refer to the named range as I would any normal sheet.

Since getRangeByName(name) method returns Range instead of NamedRange, you need to remove both arguments and getRange() method from the one written into usagerange (please, note that day and j will be 0-based for values Array). Also, make sure to write values into a variable outside the loop to reduce service calls:
var usageVals = usagerange.getValues();
//...loop
if(currentsheet.getRange(i,j).getValue() > usageVals[day-1][j-1]) {
//do something;
}
//...loop

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;
}
}
}

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.

Calculating tardies using google script

I am trying to create an attendance system that calculates tardies based on certain criteria. First off, each student member has a row dedicated to them in the spreadsheet. (Ie, student1's attendance is found on row 3, student2's attendance is found on row 4, etc.). Each student has personal information in the first few columns. Then each student has an attendance record for the meeting. All attendance information comes in pairs with a sign in time and a sign out time.
A student receives a tardy if they show up after 6:30 PM. They also receive a tardy if they are present for less than 2 hours. So I wrote a code to calculate the number of tardies.
/**
each student has one row dedicated to their attendance. Sign in and sign out are always in pairs. I will ensure that the range
begins with range[0][0] being the first sign in time.
#param range the row for the student's attendance
#return the total tardies for each student
**/
function calculateTardies(range){
var search = range[0].length;
var tardies = 0;//initially set to 0
for(k = 0; k<search; k+2){ //go through the entire row of the student's sign in and sign out times (k+2 because they always come in pairs)
var timeIn = new Date(range[0][k]); //sign in time will always be at a K value
var date = Utilities.formatDate(timeIn, "EST", "EEE"); //get the day of week that time stamp is
var timeOut = new Date(range[0][k+1]); //sign out time is to the right of the sign in time
if(date == "Tue" || date == "Wed" || date == "Thu"){ //they only need to be there for 2 hours Tues-Thurs
var checkHour = Number(Utilities.formatDate(timeIn, "EST", "HH")); //The hour student arrived
var checkMin = Number(Utilities.formatDate(timeIn, "EST", "mm")); //The minute student arrived
var outHour = Number(Utilities.formatDate(timeOut, "EST", "HH")); //hour student left
var outHour = Number(Utilities.formatDate(timeOut, "EST", "HH")); // min student left
//If the student wasn't present for at least 2 hours, add a tardy
if((checkHour+2)*60+checkMin < (outHour*60)+outMin){
tardies = tardies+1;
}
//if the student left before 6:30 PM, add a tardy
if((checkHour*60)+checkMin > 1830){ //11100 is 6:30 PM in minutes 6PM=> 18 hours * 60 min = 1080 minutes + 30 additional minutes
tardies = tardies+1;
}
}
}
return tardies;
}
To check if they are there for two hours I had to change the time stamps to minutes. I found that I couldn't simply compare the times as is. The same applies for why I changed the arrival time to minutes before comparing it to 6:30 PM.
I am running into an error that states exceeded maximum execution time (line 0). Any advice on how I can change the code or fix this error would be greatly appreciated.
I am running into an error that states exceeded maximum execution time
You have an endless loop that makes your code run until it exceeds maximum execution time
This endless loop is created by the definition
for(k = 0; k<search; k+2)
depending on what you want to do, the correct syntax is either
for(k = 0; k<search; k=k+2)
(if you want to increase k by 2 after each iteration) or
for(k = 0; k<search; k=k++)
if you want to increase k by 1 after each iteration.
I found that I couldn't simply compare the times as is.
Dates are proceeded in Apps Script the same way like in Javascript.
The documentation provides many useful methods and examples of how to wokr with dates.
In a nutshell:
If you want to calculate the difference between two timestamps - the easiest way might be to convert both dates to ms with getTime(), substract them and convert the difference back to hours (1 h = 1000*60*60 ms)
If you want to know the weekday of a date, you need to use getDay()
For working with dates it is recommendable that they are formatted as such in your spreadsheet, rather than manually converting them from numbers inside your script.

script to add values from another sheet to monthly calendar sheet - Google Sheets

I have 4 names in column like this:
A
B
C
D
I want to add them into this Dynamic Monthly Calendar Template: https://docs.google.com/spreadsheets/d/1RZ9RXTg44XkBBZx98eKeGWajYVgrULaz64Ws7FpaPGI/edit?usp=sharing
Each day has atleast 1 name and every 2 days has 2 different names (no duplicate). Expected result like this:
(update) My problem:
This is a Dynamic Calendar, which update date cells by the month. How can I set values to only the date cells in month (ignore the blue
cells)?
How can I set 4 values into, example, 30 days order like 1-A, 2-B, 3-C, 4-A, 5-B?
To address your updated questions:
1. How to assign values only to cells that contain a valid date
You can write a script that checks the cell contents of the range of interest for numbers (=dates), and inserts values into the cells underneath the numbers:
if(typeof contents[i][j]=="number")
2. How to populate the calendar with values A, B, C, D sequentially
You can assign your values of interest to an array and loop through the array starting after the 4end of the loop from 0 by using the modulus:
k=(k+1)%4;
Sample:
function myFunction() {
var myValues=["A", "B", "C", "D"];
var k=0;
var myValue="";
var myRange=SpreadsheetApp.getActive().getSheetByName("January 2019").getRange("A3:G16");
var contents=myRange.getValues();
for (var i=0; i<contents.length;i++){
for (var j=0; j<contents[0].length;j++){
if(typeof contents[i][j]=="number"){
Logger.log(contents[i][j] + " is a number");
myValue=myValues[k];
myRange.getCell(i+2,j+1).setValue(myValue);
k=(k+1)%4;
}
}
}
}

Automatically copy values from cells every week at a set time after their value is changed manually by someone else

I run a google sheet to track attendance at a kids program I coordinate. Each row features the name, and then several columns of checkboxes to indicate whether they have brought the adequate materials for the program that week. There is a column (PTS TODAY) beside the checkbox columns that will calculate how many pts that child has earned that week based on the boxes they have checked.
So, every Wednesday at 6:30 pm, our check-in person at the desk manually clicks the checkboxes when the kids show up with whatever stuff they have, and by 6:45 all the points for that week are generated.
I would love to learn how to set up the sheet to perform an automated task weekly (Wednesday at 9 pm for example) that will either:
a) automatically copy the pts column value to an empty column in another sheet
b) automatically add the pts column value to another column that represents TOTAL PTS earned so far
and also
c) automatically resets the value of all the checkboxes to zero to reset the attendance sheet.
I have not tried doing any macros, as that is something I am not familiar with.
So far my efforts have included using formulas with an IF statement referencing a cell with a NOW function in order to create some sort of trigger at a certain time each week, but soon realized if that is even a possible tactic that it is beyond my somewhat limited Google-Sheets prowess.
Try this:
function thumb(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SHEET_NAME");
var data = ss.getDataRange().getValues();
var count;
for (var i = 1; i < data.length; i++){
count = 0;
for (var j = 1; j < data[0].length; j++){
if (j == data[0].length-1){
data[i][j] += count;
break;
}
if (data[i][j] == true){
count++;
data[i][j] = false;
}
}
}
ss.getDataRange().setValues(data);
}
I wrote this with a sheet that had a header row that was names, activities, total, the activities each had their checkbox button and in the total is where the points went in the end. The code checks which values are true and then adds to the count in the total column. In order to get the trigger set up for weekly running you have to follow these instructions, you'll be able to specify the day/time when you want it to be run.