I'm making a basic script that fetches time durations from external sheets, and sums them. What I have so far is:
function getHorasCasoUso() {
var libros = {
"key1" : "externalSheetURL1",
"key2" : "externalSheetURL2",
...
};
var horas_por_caso_uso = {};
for (var key in libros) {
var libro = SpreadsheetApp.openByUrl(libros[key]);
var sheets = libro.getSheets();
for (var i = 0; i < sheets.length; i++) {
var sheet = sheets[i];
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
for (var j = 5; j < numRows; j++) {
var row = values[j];
var caso_uso = row[6];
var horas = row[4]; //The cell format is 'Duration'
if (!caso_uso)
continue;
if (!!horas_por_caso_uso[caso_uso])
horas_por_caso_uso[caso_uso] += horas;
else
horas_por_caso_uso[caso_uso] = horas;
}
}
}
var ss = SpreadsheetApp.getActiveSheet();
for (var key in horas_por_caso_uso) {
ss.appendRow([key, horas_por_caso_uso[key]]);
}
}
The problem is that the data stored in 'horas' is a string. I want to get the time duration in that cell. How can I do that?
Your issue seems quite similar to the one in this post but at a larger scale...
You should convert row[4] value to minutes (or seconds if you need this accuracy) before adding that value to the total counter.
If the cells are formatted as duration (as you say it is) it should work without changes.(see code at the end of this post)
If not, ie if these values are returned as strings then a simple string manipulation will do the job for you like this :
example : testString = 12:34
function toMinutes(value){
var minutes = Number(value.split(':')[0].replace(' ',''))*60+Number(value.split(':')[1].replace(' ',''));
return minutes;
}
(code working as a function) will return 754 (60*12+34)
usage in your code : var horas = toMinutes(row[4]);
function toMinutes(value){
var duration = new Date(value);
var min = duration.getMinutes()+60*duration.getHours();
return min;
}
You can eventually improve this function with a few error trapping features to handle cases where cell is empty of malformed... Since I don't know what the data look like I prefer let you do it yourself.
Related
I'm trying to create an apps script to autofill a gantt chart when the sheet is edited, but having trouble.
Here is a link to the spreadsheet if it helps.
function ganttChart()
{
const ss = SpreadsheetApp.getActiveSpreadsheet();
const ganttSheet = ss.getSheetByName("Gantt Chart");
var headerRow = ss.ganttSheet.getRange('headerRow').getRow();
var lastRow = ss.ganttSheet.getLastRow();
var lastCol = ss.ganttSheet.getLastColumn();
var firstTask = headerRow + 1
var taskRoleCol = ss.ganttSheet.getRange('taskRole').getColumn();
//I'm not sure if I need to do the below RoleCol if I already have a named range -- this will return an integer which is the column #
var roleCol = ss.getSheetByName("Roles").getRange('Roles').getColumn();
var taskCol = ss.ganttSheet.getRange('taskNames').getColumn();
var startWeekRow = ss.ganttSheet.getRange('startWeek').getRow();
var expDurationCol = ss.ganttSheet.getRange('expDuration').getColumn();
//set the requirements for the edit trigger -- not sure what these would be
//if (e.range)
//{
for (var i = firstTask; i < lastRow; i++)
{
var currentTask = ss.ganttSheet.getRange(i, taskCol).getValue();
var currentStartWeek = ss.ganttSheet.getRange(i, startWeekCol).getValue();
var currentTaskExpDuration = ss.ganttSheet.getRange(i,expDurationCol).getValue();
var currentTaskRole = ss.ganttSheet.getRange(i,taskRoleCol).getValue();
if (currentTask != null)
{
if (currentStartWeek != null)
{
//for loop to identify the column that matches the start week #
for (var j = 0; j < lastCol; j++)
{
var checkWeek = ss.ganttSheet.getRange(startWeekRow, j).getValue();
if (checkWeek == currentStartWeek)
{
//identify the range
var taskTimeRange = ss.ganttSheet.getRange(i,j - 1,(currentTaskExpDuration*2 +1), 1);
//for loop get the background color based on role
for (var k = 0; k < lastRow; k++)
{
var checkRole = ss.ganttSheet.getRange(k, roleCol).getValue();
//if role value matches the currentTaskRole
if (checkRole == currentTaskRole)
{
var roleColor = ss.ganttSheet.getRange(k, roleCol).getBackground();
//reformat the range based on duration
taskTimeRange.setBackground(roleColor);
}
}
}
}
}
}
}
//}
}
I took off the "onEdit" to try and get the program to work on run, but I'm still getting a "cannot read properties of undefined" error.
What should happen is:
when a user edits the "Gantt Chart" sheet
the program changes the background color in the corresponding range to indicate the weeks a task is being worked on, based on the start week and calculated duration
the background color should correspond to the task role, based on the colors set in the "Roles" sheet
If the above isn't clear, here is a link to a video where I try to explain what the program should do
The question relates to onEdit but the trigger is, at this point of development of the script, irrelevant, since the script is littered with syntax errors.
Even so, I suggest that the trigger is irrelevant in any event. The script can/should be triggered when the data has been populated. This trigger can be done manually, or (perhaps) via a menu option.
getRange(), getLastRow() and getLastColumn are sheet-based methods.
Incorrect
var headerRow = ss.ganttSheet.getRange('headerRow').getRow()
var lastRow = ss.ganttSheet.getLastRow()
var lastCol = ss.ganttSheet.getLastColumn()
Correct
var headerRow = ganttSheet.getRange('headerRow').getRow()
var lastRow = ganttSheet.getLastRow()
var lastCol = ganttSheet.getLastColumn()
Other
startWeekCol is not defined
for (var j = 0; j < lastCol; j++)
"j" substitutes for the column number, but a value of 0 is invalid
var taskTimeRange = ganttSheet.getRange(i,j - 1,(currentTaskExpDuration*2 +1), 1)
when "j" is 1, "j-1" resolves to 0 (zero) which is invalid
for (var k = 0; k < lastRow; k++)
"k" substitutes for the row number, but a value of 0 is invalid
I have a template sheet with checkboxes and I want to copy the checked ones to a new sheet. I have a working version that involves adding rows but I am looking for something faster. I thought getting a range of values on both the new and old sheets and working on the arrays would be best but I hit a error:
'Cannot covert Array to Object[][]".
I think the issue has to do with the fact that this is a new unpopulated sheet. The code below is the simplest example of what is happening. Am I doing something wrong, or is this just not possible?
function test(){
var s = SpreadsheetApp.getActiveSpreadsheet().insertSheet();
var r = s.getRange(1,1,5);
var v = r.getValues();
for ( var i=0; i < 5; i++) {
v[i] = i;
}
r.setValues(v); //ERROR: Cannot covert Array to Object[][]`enter code here`
}
It looks like the line v[i] = i; converts the Object[][] to an array. So , i think (bizarre) I need to create a new array[][] asfollows:
function test(){
var s = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var r = s.getRange(1,1,5,1);
var v = r.getValues();
var ta = [];
for ( var i=0; i < 5; i++) {
ta[i] = [];
ta[i].push(i) ;
}
r.setValues(ta);
}
Ok. Here is the full solution.
The function looks for the sheet "Work" that has 2 columns; the first is a checkbox, the second is the string value of interest. For every checked box (value == true), the 2nd column's value, Font weight, and Font size are copied into appropriately 'shaped' structures.
Once constructed, a new sheet is created, a range in the new sheet is retrieved and used to set the values, weights and sizes of a single column.
function copyCheckedItems () {
var cl = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Work');
if (cl) {
var cnt = cl.getLastRow();
var range = cl.getRange(1,1, cnt, 2 );
var values = range.getValues();
var weights = range.getFontWeights();
var sizes = range.getFontSizes();
// Compute data needed for new sheet in the right shape.
var tv = [];
var tw = [];
var ts = [];
var newCnt = 0;
for (var row in values) {
if(values[row][0]){
tv[newCnt] = [];
ts[newCnt] = [];
tw[newCnt] = [];
tv[newCnt].push(values[row][1]);
tw[newCnt].push(weights[row][1]);
ts[newCnt].push(sizes[row][1]);
newCnt++;
}
}
// construct the new sheet in a minimum of calls
var name = Browser.inputBox('Enter WorkSteet name');;
var sheetOut = SpreadsheetApp.getActiveSpreadsheet().insertSheet(name);
var ro = sheetOut.getRange(1,1,newCnt,1);
ro.setValues(tv);
ro.setFontSizes(ts);
ro.setFontWeights(tw);
//Browser.msgBox("Done.");
}
else {
Browser.msgBox('Work sheet not found!');
}
}
The time triggered function I'm calling got a fixed range. It validates this range and sends an email if necessary. This works so far but sometimes I got just errors. I assume the content of the range is not loaded, but I'm not sure. With your help, I want to improve the error message I got or figure out how to solve that problem.
sendMailToCheckedMembers is the time triggert function
getMembersFromRange gets the values of the cells, returned as a map
function sendMailToCheckedMembers(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(MAIN_PAGE);
var rangeToCheck = sheet.getRange("L5:P28");
var map = getMembersFromRange(rangeToCheck);
//check for errors
for (var ele in map) {
if(isErrorOrLoading(ele)){
sendError(createStringFromMap(map));
return;
}
}
//do validation stuff
//send validation mail
}
function getMembersFromRange(range){
var numRows = range.getNumRows();
var numCols = range.getNumColumns();
var map = {};
for (var i = 1; i <= numRows; i++) {
for (var j = 1; j <= numCols; j++) {
var currentCell = range.getCell(i,j).getValue();
var entryArray = currentCell.split(" ");
for(var k=0;k<entryArray.length;k++){
map[entryArray[k]] = entryArray[k];
}
}
}
return map;
}
this line var currentCell = range.getCell(i,j).getValue(); contains sometimes the String #ERROR! but it seams like no further information.
I tried to work with sleep(time) to just wait a few seconds after I found errors and then just try it again, but this does not help at all. I don't know what the error is about.
I'm searching for something like range.getCell(i,j).getErrorText() or a way to just wait for the sheet to finish loading the cell's content.
Like I sad above the expected validation email is send 4 out of 7 days and the other 3 days I just get the error mail.
I hope you have some ideas that a can check.
thanks!
Edit:
The content of the cells are filled with the following custom script:
function conCatDates(rangeAsString,range){
var args = Array.prototype.slice.call(arguments, 1);
var rangeArr = rangeAsString.split(";");
var returnValue =[];
for(var i=0;i<rangeArr.length;i++){
var splited = rangeArr[i].split("!");
var sheetname = splited[0];
if(args[i] != ""){
returnValue.push(sheetname);
}
}
if(returnValue.length==0){
return "";
}
return returnValue.sort().join(" ");
}
conCatDates example input conCatDates("Sheetname1!G13;Sheetname2!G13";Sheetname1!G13;Sheetname2!G13), goal of that function is, to get the sheetname of all not empty cells
This is my first question here. I have searched the site to the best of my knowledge, but haven't found any other examples of my question.
Here is the Google Sheets file
https://docs.google.com/spreadsheets/d/1HxyhoxuPK8H8_vhLg0ZZ-THyOn1cn9nPYRyls8y47iM/edit?usp=sharing
I have 2 sheets in the same Google Sheets document.
The first, "schema" contains a base-school schedule for a teacher, with different classes in different blocks. This needs to be replicated, so that all teachers has this exact setup - so that all unique users have the same 50 lines of schedule data - only with their allocated classes.
The second sheet contains information about the users. Each line contains a UNI-login username and their designated class 1a-1, 4a-1 and 8a-1 for user uni12345 for example. 1a-1 needs to replace 1, in "uni12345"'s schedule data.
I would like all these data (a lot of lines) combined into one sheet, fx. called "combined" - but you get your pick on the name :-)
I have made a Combined Example sheet, that presents how I would like the output for user1+2 in the list.
If the question already is partly answered elsewhere, I'll be happy to look at that also!
Edit:
Since my original question I have made it work - only now I'm hitting the 6min script exectution time limit. Any way around that, eg. optimization?
function merge() {
var CurrentDate = new Date() ;
var CurrentDateString1 = Utilities.formatDate(CurrentDate, "GMT", "MM-dd-yyyy HH:mm:ss") ;
var ss=SpreadsheetApp.getActive();
// var mergeSht=ss.getSheetByName(CurrentDateString1);
var users=ss.getSheetByName('users');
var schema=ss.getSheetByName('schema');
var mergeSht = ss.insertSheet();
mergeSht.setName(CurrentDateString1);
var usersValues = users.getDataRange().getValues();
var schemaValues = schema.getDataRange().getValues();
var counter = 1;
for(var n=1; n < usersValues.length ; n++){
var usersValue = usersValues[n];
var uniName = usersValue[5];
var levelInd = usersValue[2];
var levelMellem = usersValue[3];
var levelUdsk = usersValue[4];
// Logger.log(usersValues[n][5])
for(var i=1; i < schemaValues.length ; i++){
var schemaValue = schemaValues[i];
if (schemaValue != null && schemaValue.length > 0) {
var level = schemaValue[3];
var subject = schemaValue[4];
var room = schemaValue[5];
var day = schemaValue[6];
var position = schemaValue[7];
var levelAfd = getlevel(level,levelInd, levelMellem, levelUdsk);
Logger.log(levelAfd);
// print
var row=[];
row.push(counter++,'','unilogin:'+ uniName, levelAfd, subject, room, day, position);
mergeSht.appendRow(row);
}
}
}
}
function getlevel(level, levelInd, levelMellem, levelUdsk){
switch (level)
{
case 1:
return levelInd;
case 4:
return levelMellem;
case 7:
return levelUdsk;
}
}
Here's something else you could try:
This code creates a new array which avoids having to push each row one by one. Instead, at the end in loads the entire mergeSht all at one time.
function merge()
{
var CurrentDate = new Date() ;
var CurrentDateString1 = Utilities.formatDate(CurrentDate, "GMT", "MM-dd-yyyy HH:mm:ss") ;
var ss=SpreadsheetApp.getActive();
// var mergeSht=ss.getSheetByName(CurrentDateString1);
var users=ss.getSheetByName('users');
var schema=ss.getSheetByName('schema');
var mergeSht = ss.insertSheet();
mergeSht.setName(CurrentDateString1);
var usersValues = users.getDataRange().getValues();
var schemaValues = schema.getDataRange().getValues();
var counter = 1;
var mergeA=[];//Small change
mergeA.push(['H1','H2','H3','H4','H5','H6','H7','H8']); //First row is headers
for(var n=1; n < usersValues.length ; n++)
{
var usersValue = usersValues[n];
var uniName = usersValue[5];
var levelInd = usersValue[2];
var levelMellem = usersValue[3];
var levelUdsk = usersValue[4];
// Logger.log(usersValues[n][5])
for(var i=1; i < schemaValues.length ; i++)
{
var schemaValue = schemaValues[i];
if (schemaValue != null && schemaValue.length > 0)
{
var level = schemaValue[3];
var subject = schemaValue[4];
var room = schemaValue[5];
var day = schemaValue[6];
var position = schemaValue[7];
var levelAfd='';
switch(level)
{
case 1:
levelAfd=levelInd;
break;
case 4:
levelAfd=levelMellem;
break;
case 7:
levelAfd=levelUdsk;
break;
default:
levelAfd='';
break;
}
if(levelAfd)
{
mergeA.push([counter++,'','unilogin:'+ uniName, levelAfd, subject, room, day, position]);
}
}
}
}
mergeSht.getRange(1,1,mergeA.length,mergeA[0].length).setValues(mergeA);
}
google script spreadsheet
Novice
I try to create a matrix , if the array is a small database everything works fine, of course if it exceeds 800 lines and more rests on the error "You have exceeded the maximum allowed run time ." Not effectively create a matrix :
var s = SpreadsheetApp.getActiveSheet(); //List
var toAddArray = []; //Greate Arr
for (i = 1; i <= s.getLastRow()+1; ++i){ //Start getting Value
var numbr = s.getRange(i,4); //detect range
var Valus = numbr.getValues().toString(); //get value
//filter value
var newznach = Valus.replace(/\-/g, "").replace(/[0-9][0-9][0-9][0-9][0-9][a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "").replace(/[a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "");
toAddArray.push([i.toFixed(0),Valus,newznach]); //add to array 0- Row numb, 1- Value, 2- "filtered" value
}
toAddArray =
{
Row, Value, NewValue - filtered
Row, Value, NewValue - filtered
Row, Value, NewValue - filtered
...
}
Can I somehow get an array of the same the other way ( faster, easier ) ?
You're doing a call to getValues every row, that eats a lot of performance.
It is better to do one big call to have all the data and then go through it sequentially.
var s = SpreadsheetApp.getActiveSheet();
var data = s.getRange(1,4, s.getLastRow()).getValues();
var toAddArray = data.map(function(row, i) {
var Valus = row[0].toString();
var newznach = Valus.
replace(/\-/g, "").
replace(/[0-9][0-9][0-9][0-9][0-9][a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "").
replace(/[a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "");
return [i.toFixed(0), Valus, newznach];
});
this code:
var Valus = numbr.getValues().toString();
slows you down because you read data from the sheet in a loop.
Try reading data once into array and then work with it:
var data = s.getDataRange().getValues();
And then work with data, in a loop. This sample code log each cell in active sheet:
function logEachCell() {
var s = SpreadsheetApp.getActiveSheet();
var data = s.getDataRange().getValues();
// loop each cell
var row = [];
for (var i = 0; i < data.length; i++) {
row = data[i];
for (var j = 0; j < row.length; j++) {
Logger.log(row[j])
}
}
}