Thanks already to Serge insas for his insight both here and here, which have been a godsend for me already. But...I'm having trouble tying everything together with date validation.
To clarify, I have a GAS intended to verify that the date in Column A is (a) more than seven days old and (b) not null. If both pass, the script determines the first empty row in Column G, and then pauses before completing various functions. The beginning of the script looks like...
function getStats() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("Main");
var TITLE_ROW = 1;
var DATE_COL = 1;
var URL_COL = 4;
var sevendaysBefore = new Date(new Date().getTime()-7*24*60*60*1000);
if (DATE_COL != ''||(DATE_COL != null || DATE_COL< sevendaysBefore)) {
var end = sheet.getLastRow();
for( var i = 1; i < end; i++) {
var Gvals = sheet.getRange("G1:G").getValues();
var Glast = Gvals.filter(String).length;
var rowNum = TITLE_ROW+Glast;
var itemurl = sheet.getRange(rowNum,URL_COL).getValues();
Utilities.sleep(500);
...
I've clearly implemented something wrong, though, because the date validation doesn't work—the script appears to function as though the data in Column A doesn't matter. I'm sure I've done something incredibly idiotic, but I'm too ignorant to spot it on my own. So...anyone know what I've overlooked?
While the other answer is probably working (didn't test), its approach is very different from yours.
Below is code that follows the same logic as yours but works at the array level (to follow recommendations in Best practices).
I added a few comments to show the differences, hoping it will help you to understand how it works.
function getStats() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("Main");
var Glast; // define the variable for later use
var vals = sheet.getDataRange().getValues();// get all data in an array (do that before loop)
var TITLE_ROW = 0;// use array index instead of real row numbers
var DATE_COL = 0;// use array index instead of real column numbers
var URL_COL = 3;// use array index instead of real column numbers
var sevendaysBefore = new Date(new Date().getTime()-7*24*60*60*1000).getTime();// get native value in milliseconds to make comparison easier below
for( var i = 1; i < vals.length; i++) { // start loop from Row 2 (=array index 1)
if(vals[i][0]!='' && vals[i][0]!=null&&vals[i][0].getTime()<sevendaysBefore){continue};// use && instead of ||, we want ALL conditions to be true ( see !='' and !=null)
Glast = i; break ;// first occurrence of data meeting above condition (non null and date < 7 days before)
}
var itemurl = vals[Glast][URL_COL];// get the value from the array
Utilities.sleep(500);
//...
Mistake : You are hard coding DATE_COL = 1 and you are using this in if statement. It doesn't get the value of the cell. Also I am not getting your statement "date in Column A is (a) more than seven days old". Is that date is from a cell or you are iterating through all the cells in column A ?.
Below code will satisfy your need and I tested. Here as example I am checking date validation for cell R1C1(A1).
1)Get the date from cell. You can change it or Iterate the cells in column for date.
2) We have date.valueOf() method which returns the number of milliseconds since midnight 1970-01-01.
3) Validation : check the cell data is date and greater than 7 days
function compDate()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var cell = sheet.getRange("A1"); //point1
var date01 = new Date();
var date02 = cell.getValue(); //point2
var dateDiff = (date01.valueOf()-date02.valueOf())/(24*60*60*1000);
if((isValidDate(date02)) == true && dateDiff > 7) //point3
Logger.log("success");
}
//below function will return true if the arg is valid date and false if not.
function isValidDate(d) {
if ( Object.prototype.toString.call(d) !== "[object Date]" )
return false;
return !isNaN(d.getTime());
}
Related
EDIT - SOLVED
I have to make a rotation schedule for coworkers for the next year. Some coworkers have standard days off and I do not want to schedule them on those days.
This is the manual outcome I would like to get.
Example: Consultant A does not work on mondays, so I do not want Consultant A to be added to the schedule on a monday.
I then want consultant B to be added to the schedule as a fill-up. Consultant A would be next in line on a tuesday etc. Next would be consultant C but consultant C does not work on wednesdays. Therefore, we need to take consultant D for wednesday and consultant C on a thursday, and so on. When we are at the last consultant of the F column, it needs to start again at consultant A.
I have tried all kinds of formulas, like if statements and arrayformula. But there is no way that I know of to loop through the F column just with formulas.
I am not sure if this is at all clear what I want to achieve here, I am stuck 😄
I am using additionally an add-on to send the schedule to everyone's agenda, thats also the reason why i'd love to automate this, because it would help me SO much.
I did try myself on some coding, but I am no coder and I am not sure if it would be helpful at all to share my failure 😄 But this is what I've tried so far:
function Loop() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var EndRow = ss.getLastRow();
for (var i = 2; i <= EndRow; i++) {
var Day = ss.getRange(i,2).getValue();
var Consultants = ss.getRange(i,6).getValue();
var Off = ss.getRange(i,7).getValue ();
var Count = ss.getRange(i,8).getValue();
if(Day == Off){
ss.getRange(i, 3).setValue(Consultants)
}else{
ss.getRange(i, 3).setValue(Consultants)
}
}
}
EDIT:
I found a way without using apps scripts, costs me some more work manually and first tried it with a shorter team list.
The highlighted yellow cells are the cells in which the day off was identical to the work-day cell. So they got switched.
I did have to copy paste my input list of consultants but if this is the only manual way, its fine :)
Try this:
Code:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName("Sheet1");
var dayCol = sh.getRange("B2:B343").getValues().flat(); //get day column values and convert it to 1d array
var dayOffCol = sh.getRange("G2:G9").getValues().flat(); //get day off values and convert it to 1d array
var dayOffColCopy; //initialize copy
var consCol = sh.getRange("F2:F9").getValues().flat(); //get consultants values or column F
var consColCopy; //initialize copy
var tempArray = []; //storage of final value for column C
for(var i = 0; i < dayCol.length; i++){ //loop through dayCol values
var ctr = (i % consCol.length); //used modulo as counter. the value will return to 0 if the value of i is divisible to the length of consCol or in example 8
//The if statement below will help up reset the value of
//consColCopy and dayOffColCopy once the values are emptied because of the splice()
if(ctr == 0){
consColCopy = consCol.slice();
dayOffColCopy = dayOffCol.slice();
}
//the loop below will get the first non-matching values of dayCol and dayOffColCopy,
//the first non-matching values will be removed to the copy variables using splice()
//and insert it to tempArray using push()
for(var j = 0; j < dayOffColCopy.length; j++){ //loop through dayOffColCopy values
if(dayCol[i] != dayOffColCopy[j]){
tempArray.push(consColCopy.splice(j, 1));
dayOffColCopy.splice(j, 1);
break; //exit loop
}
}
}
sh.getRange(2, 3, tempArray.length, 1).setValues(tempArray); //set the values of temp array to column C
}
Example Data & Output:
Note: Make sure to use the cell that has data in your range and change the sheet name. I also added comments in my code to explain the process.
References:
Array.prototype.push()
Array.prototype.slice()
Array.prototype.splice()
Class Range
This code works perfectly but its slow like turtle. Generally I don't take this approach but I was not able to find any other option.
Well my requirement is to where ever in the column code finds 1 get the key and value of the that index defined range and go to To sheet find the key and paste the value in the required columns.
If I can jump to cells where 1 is in selected column and get the index to find the key and value and then jump to To page key column where key is instead of going to every cell and checking for it whether it is there or not.
I am new to app script and little help would be great.
Thank you in advance.
function Data_Update(){
//assigning sheet name to variables
var ss = SpreadsheetApp.getActive()
var from = ss.getSheetByName("From Sheet")//update here from sheet name(Updated)
var To= ss.getSheetByName("To Sheet")//update here to sheet name(Updated)
// Creating Loop
for (var i = 4;i<=7000;i++){//Update here from which row to start
//assigning values to check if there is 1 in column K
var udaterang = from .getRange("K"+i).getValue()//update here from which column to check for
Logger.log(i)
// Checking condition if the value is diffrent from the value already is
if (udaterang == 1) {
//creating key to find the value
var name1 = from .getRange("A"+i).getValue()//update here the key column 1
var name2 = from .getRange("B"+i).getValue()//update here the key column 2
var name3 = from .getRange("C"+i).getValue()//update here the key column 3
var name = name1.trim()+name2.trim()+name3.trim()
var rng = from .getSheetValues(i,4,1,7)//start row, start column, # rows, # columns
// Looping through each cell to check if the data needs update
for(var j=2;j<=12500;j++){
var key = To.getRange("AP"+j).getValue()
if(key == name){ //[1] because column B
To.getRange("AI"+j+":"+"AO"+j).setValues(rng)
break
}
}
}
}
}
Try this:
function Data_Update() {
const ss = SpreadsheetApp.getActive();
const fsh = ss.getSheetByName("From Sheet");
const fkvs = fsh.getRange(4, 11, fsh.getLastRow() - 3).getValues().flat();
const fvs = fsh.getRange(4, 1, fsh.getLastRow() - 3, fsh.getLastColumn()).getValues();
const tapvs = tsh.getRange(2, 42, tsh.getLastRow() - 1).getValues().flat();
fkvs.forEach((k, i) => {
let udaterang = k;
if (k == 1) {
let name = fvs[i][0].toString().trim() + fvs[i][1].toString().trim() + fvs[i][2].toString().trim();
let idx = tapvs.indexOf(name);
if (~idx) {
tsh.getRange(idx + 2, 35, 1, 7).setValues(tsh.getRange(i + 2, 4, 1, 7).getValues());
}
}
});
}
This may take some tweaking because I have not tested this as I don't have the data to do so.
This is a follow up to my previous question that was answered: How to Not Transfer Blank Column to Master Sheet?
2 new questions.
How do I sort by 2 items? I need to sort by Person and their Status (ie Completed). In the status cell is also a date, I just want it to find the completed part.
How to skip if the status is completed? Both of these are for the page that has already been combined. Hope this makes sense.
Sample Sheet: https://docs.google.com/spreadsheets/d/1Y48N8W8CdaNlrxXopj5lnHTiNep33g_qA2-kTrPdt3A/edit#gid=130911536
function combineSheets() {
const sApp = SpreadsheetApp.getActiveSpreadsheet();
const months = ['January','February','March','April','May','June','July',
'August','September','October','November','December'];
const master = sApp.getSheetByName('Master');
const sourceData = [];
months.forEach(m=>{
let sh = sApp.getSheetByName(m);
let vals = sh.getRange(1,2,sh.getLastRow(),27).getValues().filter(r=>r[0]!='');
sourceData.push(...vals);
});
master.getRange(1,1,sourceData.length,sourceData[0].length).setValues(sourceData);
}
Note: In my example, I removed the Other Data columns from your example to make the sheet cleaner. I also added Person column.
Try this:
Sheet1:
Code:
function filternsort() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var sheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2');
var sheet3 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet3');
//get sheet1 data
var val = sheet.getDataRange().getValues();
var completed = [];
var notcompleted = [];
//start to 1 to exclude header
for (var i = 1; i < val.length; i++) {
if(val[i][5].toString().match(/Completed.+/)){
//add to array completed if it matches the word Completed in the Date Due column
completed.push(val[i]);
}else{
//add to array notcompleted if not match.
notcompleted.push(val[i]);
}
}
//insert header to the beginning of the array
completed.unshift(val[0]);
//set filtered values
sheet2.getRange(1, 1, completed.length, completed[0].length).setValues(completed);
//sort by Person
sheet2.getRange(2, 1, completed.length, completed[0].length).sort(1);
//insert header to the beginning of the array
notcompleted.unshift(val[0]);
//set filtered values
sheet3.getRange(1, 1, notcompleted.length, notcompleted[0].length).setValues(notcompleted);
//sort by Person
sheet3.getRange(2, 1, notcompleted.length, notcompleted[0].length).sort(1);
}
Sheet2:
Sheet3:
References:
Array.prototype.unshift()
Range.sort()
String.prototype.match()
I have two spreadsheets:
Column A on sheet 6th&7thRoster lists all IDs in a sample, contains 853 items.
Column C on sheet alreadySubmitted contains the IDs of users who've completed a task. Contains 632 items.
I'm trying to parse through both columns. If a user from Column A of sheet 6th&7thRoster matches a user from Column C of sheet sandboxAlreadySubmitted, I want to write the word "Yes" on Column I of the current row of sheet 6th&7thRoster. When using the code below, I'm not seeing not seeing any instances of the word "Yes" on Column I of 6th&7thRoster, even though I know there's multiple places where that should be the case.
function checkRoster() {
var mainSheet = SpreadsheetApp.openById('XXXXXXX');
var roster = mainSheet.getSheetByName('6th&7thRoster');
var submissions = mainSheet.getSheetByName('alreadySubmitted');
var rosterLastRow = roster.getLastRow();
var submissionsLastRow = submissions.getLastRow();
var rosterArray = roster.getRange('A2:A853').getValues();
var submissionsArray = submissions.getRange('C2:C632').getValues;
var i;
var x;
for (i = 1; i < 853; i++) {
for (x = 1; x < 632; x++){
if (rosterArray[i] == submissionsArray[x]){
roster.getRange(i, 9).setValue("Yes");
}
}
}
}
Feedback on how to solve and achieve this task will be much appreciated. For confidentiality, I cannot share the original sheets.
You want to compate the values of A2:A853 of 6th&7thRoster and C2:C632 of alreadySubmitted.
When the values of C2:C632 of alreadySubmitted are the same with the values of A2:A853 of 6th&7thRoster, you want to put Yes to the column "I".
If my understanding is correct, how about this modification? Please think of this as just one of several possible answers.
Modified script:
function checkRoster() {
var mainSheet = SpreadsheetApp.openById('XXXXXXX');
var roster = mainSheet.getSheetByName('6th&7thRoster');
var submissions = mainSheet.getSheetByName('alreadySubmitted');
var rosterLastRow = roster.getLastRow();
var submissionsLastRow = submissions.getLastRow();
var rosterArray = roster.getRange('A2:A853').getValues();
var submissionsArray = submissions.getRange('C2:C632').getValues(); // Modified
// I modified below script.
var obj = submissionsArray.reduce(function(o, [v]) {
if (v) o[v] = true;
return o;
}, {});
var values = rosterArray.map(function([v]) {return [obj[v] ? "Yes" : ""]});
roster.getRange(2, 9, values.length, values[0].length).setValues(values);
}
Flow:
Retrieve values from A2:A853 of 6th&7thRoster and C2:C632 of alreadySubmitted.
Create an object for searching the values from the values of alreadySubmitted.
Create the row values for putting to 6th&7thRoster.
References:
reduce()
map()
If I misunderstood your question and this was not the direction you want, I apologize.
I create a dateRange wich is an array of dates.
Then I have an array of day numbers like 0 for Sunday, 1 for Monday etc...
Now I want to get all dateRange dates according to the visibleWeekDays array.
The solution is in the getVisibleDateRange function.
But I want to do it with LINQ because why reinvent the wheel...
The inner or outer selector would still need a .day() because one of the selector is a momentJS object.
But to get the day of week I would need to put the ".day()" into the linqJS string which can not work...
What would be your solution with linqJS ?
// Arrange
var startDate = moment(new Date(2014, 1, 1));
var endDate = moment(new Date(2014, 1, 15));
var visibleWeekDays = [0,1]
// Act
var dates = dateFactory.dateRange(startDate, endDate);
var visibleDays = dateFactory.getVisibleDateRange(visibleWeekDays ,dates);
function getVisibleDateRange(visibleWeekDays, dateRange) {
var visibleDateRange = [];
for (var i = 0; i < dateRange.length; i++) {
for (var j = 0; j < visibleWeekDays.length; j++) {
var currentDate = dateRange[i];
var dayOfWeek = currentDate.day();
var visibleDayOfWeek = visibleWeekDays[j];
if (visibleDayOfWeek === dayOfWeek) {
visibleDateRange.push(currentDate);
}
}
}
return visibleDateRange;
}
var visibleDateRange = Enumerable.from(visibleWeekDays).join(dateRange,"","","outer,inner=>outer + ':' + inner")
Here's how I would write the inner join:
var dateRange = Enumerable.Range(1, 15).Select("new Date(2014, 1, $)");
var visibleWeekDays = Enumerable.From([0, 1]);
var visibleDateRange = dateRange.Join(visibleWeekDays,
"$.getDay()", // outer selector
"$", // inner selector
"$") // result selector (select outer value, the date)
.ToArray();
Here, I've used a more compact syntax for defining lambdas. Basically lambdas will generally have at most 4 paramters. So you can reference the nth parameter simply by adding additional $'s in the identifier. So $ refers to the first parameter, $$ the second, etc.
The parameters of the join is exactly the same as in the call you would make in LINQ. The first parameter is the inner collection, then the outer selector, inner selector, and result selector.
Since the outer collection is the dates, you have access the corresponding item and its properties. Since we want to get the result of calling date.getDay(), you simply call getDay() on the object (the first parameter).