So I've got this sheet where on column A I've got only dates with hours, minutes and seconds. Each hour or so, depending on the time-driven trigger configuration, a new row will be recorded to LOG sheet with new data, including present date and time.
I've got this script which is executed together with the recording function. This script is supposed to automatically group rows. Each time a new row is recorded it will compare the last 2 rows to see if day as changed. If it did it will group all the rows belonging to the previous day. The same will be done for months and years.
It is working ok. Problem now is this script seems to be breaking the day group in 2 groups. This seems to be happening just for when there are rows with time between midnight and 1 am. I can't really understand why it does this. It does not to this with any other time frames. Check screen pics:
So this is the code I'm running for the grouping function:
const logSheetName = "LOG";
// Functions to group by day, month and year. Within each the variables are redefined, except logSheetName
// Grouping by Day
function groupDays() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(logSheetName);
// Variables needed to get row number of the first row of row block for last day, for rowStart variable
var dateDay = sheet.getRange(sheet.getLastRow() - 1, 1).getValue();
var getDayMonthYear = Utilities.formatDate(dateDay, Session.getScriptTimeZone(), "dd/MM/YYYY");
var cells = sheet.getRange("A5:A").createTextFinder(getDayMonthYear).findAll().map(x => x.getRowIndex());
var firstRowOfDay = cells[0];
var testes = Session.getScriptTimeZone();
// Variables needed for Grouping Function
rowStart = firstRowOfDay;
rows = sheet.getLastRow() - rowStart + 1;
dates = sheet.getRange(rowStart, 1, rows, 1).getValues().flat();
// Group by day execution
groupDates(dates, 'date');
// Collapse group
let groupClps = sheet.getRowGroup(firstRowOfDay, 1);
// groupClps.collapse();
// Console log
console.log("groupDays() executed");
console.log("first row from Days position is " + rowStart);
console.log(getDayMonthYear);
console.log(cells);
console.log("auto timezone is: " + testes);
}
// Grouping by Month
function groupMonths() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(logSheetName);
// Variables needed to get row number of the first row of row block for last day, for rowStart variable
var dateMonth = sheet.getRange(sheet.getLastRow() - 1, 1).getValue();
var getMonthYear = Utilities.formatDate(dateMonth, Session.getScriptTimeZone(), "MM/YYYY");
var cells = sheet.getRange("A5:A").createTextFinder(getMonthYear).findAll().map(x => x.getRowIndex());
var firstRowOfDay = cells[0];
// Variables needed for Grouping Function
rowStart = firstRowOfDay;
rows = sheet.getLastRow() - rowStart + 1;
dates = sheet.getRange(rowStart, 1, rows, 1).getValues().flat();
// Group by Month execution
groupDates(dates, 'month');
// Collapse group
let groupClps = sheet.getRowGroup(firstRowOfDay, 1);
groupClps.collapse();
// Console log
console.log("groupMonths() executed");
console.log("first row from Months position is " + rowStart);
}
// Grouping by Year
function groupYears() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(logSheetName);
// Variables needed to get row number of the first row of row block for last day, for rowStart variable
var dateYear = sheet.getRange(sheet.getLastRow() - 1, 1).getValue();
var getYear = Utilities.formatDate(dateYear, Session.getScriptTimeZone(), "YYYY");
var cells = sheet.getRange("A5:A").createTextFinder(getYear).findAll().map(x => x.getRowIndex());
var firstRowOfDay = cells[0];
// Variables needed for Grouping Function
rowStart = firstRowOfDay;
rows = sheet.getLastRow() - rowStart + 1;
dates = sheet.getRange(rowStart, 1, rows, 1).getValues().flat();
// Group by Year execution
groupDates(dates, 'year');
// Collapse group
let groupClps = sheet.getRowGroup(firstRowOfDay, 1);
groupClps.collapse();
// Console log
console.log("groupYears() executed");
console.log("first row from Years position is " + rowStart);
}
var rowStart;
var rows;
var dates;
var prevDate;
var rangeStart;
var rangeEnd;
var groupingPeriod;
function groupDates(dates, period) {
groupingPeriod = period;
dates.forEach((date, idx) => {
if (idx === 0) {
processInitialDate(date);
} else {
processDate(date, idx);
}
});
}
function processInitialDate(date) {
prevDate = date;
rangeStart = 0;
rangeEnd = rangeStart;
}
function processDate(date, idx) {
if (periodHasChanged(date, prevDate, groupingPeriod)) {
createGroup();
rangeStart = rangeEnd + 1;
rangeEnd = rangeStart;
} else {
rangeEnd++;
if (idx === dates.length - 1) {
createGroup();
}
}
prevDate = date;
}
function periodHasChanged(currDate, prevDate, period) {
switch (period) {
case 'year':
var currPeriod = currDate.getYear();
var prevPeriod = prevDate.getYear();
break;
case 'month':
var currPeriod = currDate.getMonth();
var prevPeriod = prevDate.getMonth();
break;
case 'date':
var currPeriod = currDate.getDate();
var prevPeriod = prevDate.getDate();
break;
}
return currPeriod !== prevPeriod;
}
function createGroup() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(logSheetName);
var rangeGroup = dates.slice(rangeStart, rangeEnd + 1);
if (rangeGroup.length > 1) {
var range = `${rowStart + rangeStart + 1}:${rowStart + rangeEnd}`;
sheet.getRange(range).shiftRowGroupDepth(1);
}
}
So the console.log()tests don't tell me anything strange is happening. The firstRowOfDayvar tells me the right row number is targeted for the group handle. It just makes 2 groups instead of 1 and the 1st is always between 00:00 and 01:00.
Am I missing something?
Here's the dummy file:
https://docs.google.com/spreadsheets/d/1ExXtmQ8nyuV1o_UtabVJ-TifIbORItFMWjtN6ZlruWc/edit?usp=sharing
I think the error has to do with time zone. Try to add the line:
var timeZone = SpreadsheetApp.getActiveSpreadsheet().getSpreadsheetTimeZone();
And use the timeZone variable whenever you try to make Date objects.
At least it worked for me. The function groupDays() in your script doesn't work for me somehow. I tried it but in vain. So I took my old variant from your "ARCHIVE.gs", tweaked it a bit, chaged the timeZone definition, and it works fine, as far as I can tell:
// group rows by years
function group_by_years() {
var sheet = SpreadsheetApp.getActiveSheet();
get_rows_array().forEach(y => shift_rows(y.map(m => m.flat()).flat(),sheet));
}
// group rows by months
function group_by_months() {
var sheet = SpreadsheetApp.getActiveSheet();
get_rows_array().flat().forEach(m => shift_rows(m.flat(),sheet));
}
// group rows by days
function group_by_days() {
var sheet = SpreadsheetApp.getActiveSheet();
get_rows_array().flat().flat().forEach(d => shift_rows(d.flat(),sheet))
}
function get_rows_array() {
// var timeZone = "GMT+1";
var timeZone = SpreadsheetApp.getActiveSpreadsheet().getSpreadsheetTimeZone();
var sheet = SpreadsheetApp.getActiveSheet();
var rowStart = 5;
var rows = sheet.getLastRow() - rowStart + 1;
var values = sheet.getRange(rowStart, 1, rows, 1).getValues().flat();
var o = {};
// make the object {'year': { 'month':[days] } }
values.forEach((date, i) => {
var [y, m, d] = Utilities.formatDate(date, timeZone, "yyyy,MM,dd").split(",");
// console.log(y, m, d);
if (!o[y]) o[y] = {};
if (!o[y][m]) o[y][m] = {};
if (!o[y][m][d]) o[y][m][d] = [];
o[y][m][d].push(rowStart + i);
});
// convert the unordered object {year:{month:[days]}}
// into the ordered 3d-array [year[month[days]]]
const numsort = (a,b) => parseInt(a) - parseInt(b);
return Object.keys(o).sort(numsort)
.map(y => Object.keys(o[y]).sort(numsort)
.map(m => Object.values(o[y][m]).sort(numsort)));
}
function shift_rows(rows,sheet) {
if (rows.length === 1) return;
var range = `${ rows[1] }:${ rows.slice(-1) }`;
sheet.getRange(range).shiftRowGroupDepth(1);
}
As an quick dirty patch you can try to replace in your functions groupDays() this line :
dates = sheet.getRange(rowStart, 1, rows, 1).getValues().flat();
with this:
dates = sheet.getRange(rowStart, 1, rows, 1).getDisplayValues().flat(); // get strings
const eu_to_us = d => d.replace(/^(\d\d\/)(\d\d\/)/, '$2/$1/'); // dd/MM --> MM/dd
dates = dates.map(d => new Date(eu_to_us(d))); // get US dates
It converts the EU dates dd/MM/YYYY into US dates MM/dd/YYYY, and it does the trick. It seems so. Probably it fixes time zone along the way. But it's not a solid solution by any measure. It's jus a patch. I can't guarantee anything.
I believe it's a wrong idea to consider these strings as proper dates in this case. You can search, sort and group them as strings. As soon as you convert them into dates you get the mess with time zones and date formats. Imho.
Here is the part for the date format issue:
// group rows by days
function group_by_days2() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(logSheetNameYR);
// Tweak to get the number of first row of block of rows of previous day + tweak for date format US/EU etc
var dateDay = sheet.getRange(sheet.getLastRow() - 1, 1).getValue();
// Conditional variable for US and EU date formats, changes according to locale on spreadsheet settings. Also forces date format.
var locale = SpreadsheetApp.getActive().getSpreadsheetLocale();
var getDayMonthYear;
switch (locale) {
case 'en_US', 'en_CA', 'en_ZA':
sheet.getRange("A5:A").setNumberFormat('m"/"d"/"yyyy" "h":"mm":"ss');
getDayMonthYear = Utilities.formatDate(dateDay, Session.getScriptTimeZone(), "M/d/YYYY");
console.log("no locale detected");
break;
case 'zh_CN', 'hu_HU', 'lt_LT':
sheet.getRange("A5:A").setNumberFormat('yyyy"-"mm"-"dd" "hh":"mm":"ss');
getDayMonthYear = Utilities.formatDate(dateDay, Session.getScriptTimeZone(), "YYYY-MM-dd");
break;
default:
sheet.getRange("A5:A").setNumberFormat('dd"/"mm"/"yyyy" "hh":"mm":"ss');
getDayMonthYear = Utilities.formatDate(dateDay, Session.getScriptTimeZone(), "dd/MM/YYYY");
console.log("no locale detected");
}
// 2nd part of tweat for getting the number of first row of block of rows of previous day
var cells = sheet.getRange("A5:A").createTextFinder(getDayMonthYear).findAll().map(x => x.getRowIndex());
var firstRowOfDay = cells[0];
// Variables needed for Grouping Function
rowStart = firstRowOfDay;
get_rows_array2().flat().flat().forEach(d => shift_rows2(d.flat(),sheet))
}
I realized something. The main sheet I am working on will be used by lets say 60% of people from US the rest 40% rest of the world. So if the dates are in US format this code won't work. I need something that will work either the User is in US Date format or EU or other. Your code for grouping rows does the job for any date format it seems. Problem is after I tweak your code for using with automatic grouping it displays an error: Exception: The parameters (null,number,number,number) don't match the method signature for SpreadsheetApp.Sheet.getRange. Problem is with my tweak.
So below is just the group_by_days() with my tweak:
function group_by_days() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(logSheetNameYR);
// Variables needed to get row number of the first row of row block for last day, for rowStart variable
var dateDay = sheet.getRange(sheet.getLastRow() - 1, 1).getValue();
var getDayMonthYear = Utilities.formatDate(dateDay, Session.getScriptTimeZone(), "dd/MM/YYYY");
var cells = sheet.getRange("A5:A").createTextFinder(getDayMonthYear).findAll().map(x => x.getRowIndex());
var firstRowOfDay = cells[0];
// Variables needed for Grouping Function
rowStart = firstRowOfDay;
get_rows_array().flat().flat().forEach(d => shift_rows(d.flat(),sheet))
}
var rowStart;
The variable rowStart I declared it outside any function so I can call it within each function with different values.
So the problem might be with this line:
var getDayMonthYear = Utilities.formatDate(dateDay, Session.getScriptTimeZone(), "dd/MM/YYYY");
Is there a way to make this combine with either US or any other date formats?
Full code at:
https://docs.google.com/spreadsheets/d/1ExXtmQ8nyuV1o_UtabVJ-TifIbORItFMWjtN6ZlruWc/edit?usp=sharing
I am trying to split day from full-date and calculating the range between them and inserting them into a array to add that to a database.
<script>
function()
{
var sd = "26-04-2020";
var a = sd.split("-");
var ed = "28-04-2020";
var b = ed.split("-");
var p1= a[1];
var p2= a[2];
var p3= a[3];
var q1= b[1];
var q2= b[2];
var q3= b[3];
var datearry = [];
if( p1<q1 && p2=q2 && p3=q3)
{
for (i=p1; i<q1; i++)
{
datearry = ("i"+"p2"+"p3");
}
}
else if( p1<q1 && p2<q2 && p3=q3)
{
for (i=p1; i<q1; i++)
{
for (j=p2; j<q2; j++)
{
datearry = ("i"+"j"+"p3");
}
}
}
else if( p1<q1 && p2<q2 && p3<q3)
{
for (i=p1; i<q1; i++)
{
for (j=p2; j<q2; j++)
{
for (k=p3; k<q3; k++)
{
datearry = ("i"+"j"+"k");
}
}
}
}
alert(datearry);
}
</Script>
I did not get the expected result, anyone please give some suggestions or even code that works thankyou.
Your code has a number of issues:
function()
{
Is a syntax error, function declarations require a function name.
datearry = ("i"+"p2"+"p3");
datearray is initialised as an array, but here a string literal is assigned (literally "ip2p3"). You probably meant:
datearray.push(String(i) + p3 + p3);
which will add a string like "26042020" the first time, however as the algorithm progresses and the strings are turned into numbers, you'll start to get values like "152020" for 1 March 2020. So you need some padding, and probably a delimiter, to get "01-05-2020" and match the input format.
Lastly, the code doesn't seem to correctly accommodate transitions over month and year boundaries. Fixing that really isn't useful as it's much more complex than it needs to be or I'm prepared to bother with given there are much simpler alternatives. :-)
If you're looking for an array of timestamps between two dates, one strategy is to convert the timestamps to dates, then loop over the required range storing the required output timestamps in an array.
Existing questions deal with how to parse a date string and how to format a date. You can put those together to create a function something like the following:
// startDate, endDate in format dd-mm-yyyy
// Return array of dates inclusive of start and end in same format
function getDateRange(startDate, endDate) {
// Parse dd-mm-yyyy
let qParse = s => {
let [d, m, y] = s.split(/\D/);
return new Date(y, m-1, d);
};
// Format date as dd-mm-yyyy
let qFormat = d => {
let z = n => (n<10? '0' : '') + n;
return z(d.getDate())+'-'+z(d.getMonth()+1)+'-'+d.getFullYear();
}
// Setup for loop
let start = qParse(startDate);
let end = qParse(endDate);
let result = [];
// Loop from start to end, incrementing
// the start date and writing to result array
do {
result.push(qFormat(start));
start.setDate(start.getDate() + 1);
} while (start <= end)
return result;
}
console.log(getDateRange('26-02-2020','03-03-2020'));
// Different delimiters in input
console.log(getDateRange('28.12.2020','03/01/2021'));
You should also validate the input. The parse and format functions are closed in the function as they're only designed to work with the specific format of the OP (i.e. they aren't general parsing or formatting functions).
I have a google spreadsheet where some of the sheets names are text and others are dates in "dd/mm/yyyy/" format. I need a function that can put the sheet called "Tablero" first, then sort the sheets named with dates descendingly, and leave at the end the rest of the sheets.
This is my code so far:
function testOrdenar() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetsCount = ss.getNumSheets();
var sheets = ss.getSheets();
// I need the sheet "Tablero" to go first
var tablero = ss.getSheetByName("Tablero");
ss.setActiveSheet(tablero);
ss.moveActiveSheet(1);
var names = []; // This is where the sheets named with dates will go
var j = 1; // I use this as a counter, but it is not absolutly necessary
for (var i = 0; i<sheetsCount; i++){
var sheetName = sheets[i].getName();
var pattern = /(\d{2})\/(\d{2})\/(\d{4})/;
var dt = new Date(sheetName.replace(pattern,'$3-$2-$1'));
if (dt instanceof Date && !isNaN(dt.valueOf())) {names[j] = dt; j++;} // This is to distinguish the sheets that are dates
}
// Here I sort the sheets descendingly
for (var m = 1;m<j;m++) {
for (var n = 1;n<j;n++) {
if (names[n] < names[m]) {
var aux = names[n];
names[n] = names[m];
names[m] = aux;
}
}
}
var pos = 2;
for (var a = 0;a<j;a++) {
var sheetName = Utilities.formatDate(new Date(names[a]), "GMT-3", "dd/MM/yyyy");
Logger.log(sheetName);
var sheet = ss.getSheetByName(sheetName);
ss.setActiveSheet(sheet);
ss.moveActiveSheet(pos);
pos++;
}
The sorting is correct, but I don't know why each date ends up being a day less. I tried adding one to de variable but it comes out as "Invalid Object". And I also need those dates as strings because that is how i can then call the sheets.
My questions:
1) How can I get the correct dates? (not a day before each one). Could it have something to do with the timezone? I'm in "GMT-3".
(If the answer is adding one, please tell me how because I tried that and comes back as an error.)
2) How can I get the sorted dates as strings in "dd/MM/yyyy" format?
Here are the screenshots of my sheets and the logs I get:
Cause:
As written in the documentation, Date.parse(timestring), where timestring is a date only iso8601 compatible string, returns a date in UTC. When you format the date in GMT-3, the date is offset 3 hours to the previous day.
Solution:
Use GMT as timezone argument of Utilities.formatDate
Alternatively, You can avoid converting to date at all and sort them as plain strings.
Sample script:
function testOrdenar1() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheets = ss.getSheets();
const pattern = /(?:Tablero)|(\d{2})\/(\d{2})\/(\d{4})/;
const map = sheets.reduce( //Create a map of sheet object : sheetName(as yyyyMMdd)
(m, sheet) =>
m.set(
sheet,
sheet
.getName()
.replace(pattern, (m, p1, p2, p3) =>
m === 'Tablero' ? '99999999' : p3 + p2 + p1//make Tablero highest 8 digit number string
)
),
new Map()
);
sheets.sort((a, b) => map.get(b) - map.get(a));//sort sheet objects by their name
let pos = 1;
sheets.forEach(sh => {
ss.setActiveSheet(sh);
ss.moveActiveSheet(pos++);
});
}
I have looked at various solutions posted i.e. parsing, substrings and splitting and none of them either produce a value or the required value.
The format received via Salesforce API is "2014-08-19T02:26:00.000+0000"
Essentially I would like a custom function that can be used within Google Sheets to convert this date/time format and take daylight saving into consideration
Thank you beforehand
I use a simple function like below :
function parseDate(string) {
var parts = string.split('T');
parts[0] = parts[0].replace(/-/g, '/');
var t = parts[1].split(':');
var refStr = new Date(new Date(parts[0])).toString();// use this to get TZ for daylight savings
var fus = Number(refStr.substr(refStr.indexOf('GMT')+4,2));
return new Date(new Date(parts[0]).setHours(+t[0]+fus,+t[1],0));
}
firstly thank you for everyone's input. By using a combination of the info provided by RobG and Serge insas I revised the script and created one that suited my needs. Please see below, any further advice would be welcome.
/*
The script first has all variables declared.
As the script runs inconjunction with an API query running off single trigger for defined sequential functions where the previous parsed date records are cleared and then re-parsed and runs with loop function for a whole column of data within specified range
*/
function parseDate() {
var source_spreadsheet = SpreadsheetApp.openById("Sheet_Id");
SpreadsheetApp.setActiveSpreadsheet(source_spreadsheet);
var sheet = source_spreadsheet.getSheetByName("Sheet_Tab");
var startRow = 2;
var numRows = 4500;
var startCol = 1;
var numCols = 7;
var dataRange = sheet.getRange(startRow, startCol, numRows, numCols)
sheet.getRange(startRow, startCol + 1, numRows, numCols - 1).clear({contentsOnly: true});
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var SFConnectDate = row[0];
var DConnected = row[1];
var SFCutoverDate = row[2];
var DInUse = row[3];
var Lat = row[5];
var Long = row[6];
if (SFConnectDate != "" && DConnected == "" && Lat != "" && Long != "") {
var parts = SFConnectDate.split('T');
parts[0] = parts[0].replace(/-/g, '/');
var Fdd = parts[0].split('/');
var AllTime = parts[1].split('.');
var Ftt = AllTime[0].split(':');
var D = new Date(Fdd[0],(Fdd[1]-1),Fdd[2] ,Ftt[0],Ftt[1],Ftt[2]);
var TZ = (D.getTimezoneOffset())/60;
var DConnected = new Date(Fdd[0],(Fdd[1]-1),Fdd[2],(Ftt[0]-TZ),Ftt[1],Ftt[2]);
sheet.getRange(startRow + i, 2).setValue(DConnected);
}
}
}
I have some google spreadsheet logbook where I store duration of some activities in hours format [[HH]:MM:SS]. The spreadsheet adds such cells with no issues. However when I try to add them via Google Script I get some garbage. What I found is that Date() object is implicitly created for such cells, but I cannot find API of that value type.
I know I can convert the data to "hour integers" by multiplying them by 24 but that is a nasty workaround as it demands duplication of many cells. I would rather like a solution that will allow to do that in google script itself.
here is a working function that does the trick.
I first tried to format it as a date but 36 hours is not really standard !! so I did a little bit of math :-) )
To get it working you should set a cell somewhere with value 00:00:00 that we will use as a reference date in spreadsheet standard. in my code it is cell D1(see comment in code, reference date in SS is in 1900 and in Javascript is in 1970 ... that's why it is a negative constant of 70 years in milliseconds...)
here is the code and below a screen capture of the test sheet + the logger
It would be a good idea to modify this code to make it a function that takes cell value as parameter and returns the result as an array for example ([h,m,s] or something similar), this code is only to show how it works.
function addHoursValues() {
var sh = SpreadsheetApp.getActive()
var hours1 = sh.getRange('A1').getValue();
var hours2 = sh.getRange('B1').getValue();
var ref = sh.getRange('D1').getValue().getTime();
//var ref = -2209161600000 // you could also use this but it would be less obvious what it really does ;-)
Logger.log(ref+' = ref');
var h1 = parseInt((hours1.getTime()/3600000)-ref/3600000);
var h2 = parseInt((hours2.getTime()/3600000)-ref/3600000);
Logger.log(h1+' + '+h2+' = '+(h1+h2))
var m1 = parseInt((hours1.getTime()-h1*3600000-ref)/60000);
var m2 = parseInt((hours2.getTime()-h2*3600000-ref)/60000);
Logger.log(m1+' + '+m2+' = '+(m1+m2))
var s1 = parseInt((hours1.getTime()-h1*3600000-m1*60000-ref)/1000);
var s2 = parseInt((hours2.getTime()-h2*3600000-m2*60000-ref)/1000);
Logger.log(s1+' + '+s2+' = '+(s1+s2))
var ts=s1+s2
var tm=m1+m2
var th=h1+h2
if(ts>59){ts=ts-60;tm++};
if(tm>59){tm=tm-60;th++}
Logger.log('sum = '+th+':'+tm+':'+ts)
}
EDIT : here are 2 "function" versions with corresponding test functions that show how to use it
function getHMS(hrs) {
var t = hrs.getTime()/1000;
var ref = -2209161600;
var h = parseInt((t-ref)/3600);
var m = parseInt((t-h*3600-ref)/60);
var s = parseInt(t-h*3600-m*60-ref);
return[h,m,s];// returns an array of 3 discrete values
}
function testHMS(){
var sh = SpreadsheetApp.getActive();
var hours1 = sh.getRange('A1').getValue();
var hours2 = sh.getRange('B1').getValue();
var sumS = getHMS(hours1)[2]+getHMS(hours2)[2];// add seconds
var sumM = getHMS(hours1)[1]+getHMS(hours2)[1];// add minutes
var sumH = getHMS(hours1)[0]+getHMS(hours2)[0];// add hours
if(sumS>59){sumS=sumS-60 ; sumM++}; // handles values >59
if(sumM>59){sumM=sumM-60 ; sumH++}; // handles values >59
Logger.log(sumH+':'+sumM+':'+sumS);
}
OR
function addHMS(hrs1,hrs2) {
var t1 = hrs1.getTime()/1000;
var t2 = hrs2.getTime()/1000;
var ref = -2209161600;
var h = parseInt((t1-ref)/3600)+parseInt((t2-ref)/3600);
var m = parseInt((t1-parseInt((t1-ref)/3600)*3600-ref)/60)+parseInt((t2-parseInt((t2-ref)/3600)*3600-ref)/60);
var s = parseInt(t1-parseInt((t1-ref)/3600)*3600-parseInt((t1-parseInt((t1-ref)/3600)*3600-ref)/60)*60-ref)
+parseInt(t2-parseInt((t2-ref)/3600)*3600-parseInt((t2-parseInt((t2-ref)/3600)*3600-ref)/60)*60-ref);
if(s>59){s=s-60 ; m++}; // handles values >59
if(m>59){m=m-60 ; h++}; // handles values >59
return[h,m,s];// returns sum in an array of 3 discrete values
}
function othertestHMS(){
var sh = SpreadsheetApp.getActive();
var hours1 = sh.getRange('A1').getValue();
var hours2 = sh.getRange('B1').getValue();
Logger.log(addHMS(hours1,hours2));
}