Google Scripts: Time formatted cells cause Error: Overflow - google-apps-script

I am trying to write a custom function that takes two cell ranges (Start time and Stop time) as input and then outputs the total hours. If the cells are formatted as normal numbers (i.e. 9:00pm is represented as 0.875) the function works fine. If I format the cells as time the cell value the formula is in says "#NUM!" and the tooltip is "error: Overflow". Is there a way to read the raw cell data without the formatting so that my formula will work?
Formula code
function getHoursTest(startRange, stopRange) {
var hours = 0; // define "hours" with start value of 0
var i = startRange.length - 1; // define "i" and assign the position of the last array element
while(i>=0){
hours = hours + ((stopRange[i] - startRange[i])*24);
i--
}
return hours;
}

When a cell is formatted as a date or time value then it's value is passed to Apps Script as a JavaScript Date object. You can determine if the value is a date by using code like:
if (value instanceof Date) {
...
} else {
...
}

Related

Static timestamping in Google Sheets

I am trying to add STATIC timestamp to my data whenever it is imported or pasted in the sheets.
I am using this formula now
(=ARRAYFORMULA( IFS(I1:I="","",L1:L="",NOW(),TRUE,L1:L)))
but, whenever I open the sheet again the time gets changed automatically to the current time as i am using the now() function. I tried on-Edit in the script, but it's only working when the data is manually entered.
Is there any other way I can use to static timestamp when data is being pasted or imported?
Instead of NOW() on the formula, do it via script using new Date().
The NOW() function updates the timestamp every time the spreadsheet is open or something changes in it, while the new Date() gives you a full (date and time) and static timestamp.
Also, as I've seen on the comments of your question, there really is no way to use onEdit() through automated scripts and macros.
Answer
You can use a custom function to return the actual date with the method new Date() and the Properties Service. Open Apps Script and paste the following function:
Code
function getTimestamp(reset) {
// update the timestamp
if (reset == 1) {
setTime()
}
// try-catch structure in order to set the time in the first execution
try {
var time = ScriptProperties.getProperty('time')
}
catch (err) {
setTime()
var time = ScriptProperties.getProperty('time')
}
return time
}
function setTime() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var time = new Date()
ScriptProperties.setProperty('time', time)
}
How it works
Now, you can use it in any cell like another Sheet function. Call the function with =getTimestamp(0). On the first execution, it tries to get the saved property time, but as the property does not exist it generates a timestamp and saves a new property in the project with the key time and the value of the timestamp.
In the following executions, the value obtained by the function when it is recalculated is the same, since the property is not overwritten unless the function is called with a 1 input: =getTimestamp(1). In this case, the timestamp is updated, but if it is not set back to =getTimestamp(0), every time the function is recalculated (which happens automatically every so often) the timestamp will change.
In conclusion, always use =getTimestamp(0). When you want to update the value, change it to =getTimestamp(1) and go back to the original formula.
update
I have updated the answer to explain how to update the timestamp when new values are added:
Use a cell as input to the function, e.g. =getTimeStamp(A1) 2.
Create an onEdit trigger
Check that the range of the e event belongs to new values.
Update the value of A1 to 1 and then to 0 if you have detected new values.
example:
function onEdit(e){
var range = e.range
var cell = SpreadsheetApp.getActiveSpreadsheet().getRange('A4')
if (range.columnStart > 1 && range.rowStart > 10){
cell.setValue(1)
SpreadsheetApp.flush()
cell.setValue(0)
}
}
If new values are added from column 1 and row 10, A1 is updated to 1 and then to 0, thus updating the value of the timeStamp function and saving it permanently until the trigger is executed again.
References:
Custom Functions in Google Sheets
Working with Dates and Times
Apps Script: Extending Google Sheets
Properties Service
Not sure have your question got a solution. I had the same struggle as yours over the year, especially with pasted data, and I found a solution that works for my case nicely (but not by formula, need to run in Apps Script).
Some background for my case:
I have multiple sheets in the spreadsheet to run and generate the
timestamp
I want to skip my first sheet without running to generate timestamp
in it
I want every edit, even if each value that I paste from Excel to
generate timestamp
I want the timestamp to be individual, each row have their own
timestamp precise to every second
I don't want a total refresh of the entire sheet timestamp when I am
editing any other row
I have a column that is a MUST FILL value to justify whether the
timestamp needs to be generated for that particular row
I want to specify my timestamp on a dedicated column only
function timestamp() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const totalSheet = ss.getSheets();
for (let a=1; a<totalSheet.length; a++) {
let sheet = ss.getSheets()[a];
let range = sheet.getDataRange();
let values = range.getValues();
function autoCount() {
let rowCount;
for (let i = 0; i < values.length; i++) {
rowCount = i
if (values[i][0] === '') {
break;
}
}
return rowCount
}
rowNum = autoCount()
for(let j=1; j<rowNum+1; j++){
if (sheet.getRange(j+1,7).getValue() === '') {
sheet.getRange(j+1,7).setValue(new Date()).setNumberFormat("yyyy-MM-dd hh:mm:ss");
}
}
}
}
Explanation
First, I made a const totalSheet with getSheets() and run it
with a for loop. That is to identify the total number of sheets
inside that spreadsheet. Take note, in here, I made let a=1;
supposed all JavaScript the same, starts with 0, value 1 is to
skip the first sheet and run on the second sheet onwards
then, you will notice a function let sheet = ss.getSheets()[a]
inside the loop. Take note, it is not supposed to use const if
your value inside the variable is constantly changing, so use
let instead will work fine.
then, you will see a function autoCount(). That is to make a for
loop to count the number of rows that have values edited in it. The
if (values[i][0] === '') is to navigate the script to search
through the entire sheet that has value, looking at the row i and
the column 0. Here, the 0 is indicating the first column of the
sheet, and the i is the row of the sheet. Yes, it works like a
json object with panda feeling.
then, you found the number of rows that are edited by running the
autoCount(). Give it a rowNum variable to contain the result.
then, pass that rowNum into a new for loop, and use if (sheeet.getRange(j+1,7).getValue() === '') to determine which row
has not been edited with timestamp. Take note, where the 7 here
indicating the 7th column of the sheet is the place that I want a
timestamp.
inside the for loop, is to setValue with date in a specified
format of ("yyyy-MM-dd hh:mm:ss"). You are free to edit into any
style you like
ohya, do remember to deploy to activate the trigger with event type
as On Change. That is not limiting to edit, but for all kinds of
changes including paste.
Here's a screenshot on how it would look like:
Lastly, please take note on some of my backgrounds before deciding to or not to have the solution to work for your case. Cheers, and happy coding~!
You cannot get a permanent timestamp with a spreadsheet formula, even with a named function or an Apps Script custom function, because formula results refreshed from time to time. When the formula gets recalculated, the original timestamp is lost.
The easiest way to insert the current date in a cell is to press Control + ; or ⌘;. See the keyboard shortcuts help page.
You can also use an onEdit(e) script to create permanent timestamps. Search this forum for [google-apps-script] timestamp to find many examples.

Google Sheets - Add to Certain Value Based on Background Color

I've tried making a Google Apps Script, but I was having trouble trying to understand how to set it up. From this it seems like I can create a function that I can call inside the spreadsheet itself like the SUM function provided by Google Sheets. I've taken a look at the getBackground() function, but it seems like it needs some global variables included instead of just functions.
Here's my current spreadsheet:
I want to input a function where it takes in the ranges A2:A1000 and based on the background color of the cell, determine whether it goes into "Work" or "Life" and then adds it onto the cells E4 (Total Work) or F4 (Total Life) accordingly. The cells in column A will always be numbers.
Here's what I've tried, I think I may be off the path completely based off of my single cell approach:
function workTime(input) {
if (input.getBackground() == "#d9ead3") {
input.setFontColor(4285f4)
} else {
input.setFontColor(null)
}
}
//I get errors on line 3 for some reason though...
TL;DR Based on the background colors of the cells, how do I create a function that calculates the sum of the numbers in those specific colors and displays them in different cells under the "Total Work Time" and "Total Life Time" accordingly?
The "custom formula" approach is very limited
The only input you'll get into the custom formulae are the values, not the cell object. The function that is running the formula will never know about its location or formatting. It receives a value or an array of values, and returns a value or am array of values.
Apps Script version
function workTime2() {
let file = SpreadsheetApp.getActive();
let sheet = file.getSheetByName("Sheet1");
let range = sheet.getRange('A1:A16');
let targetColor = "#00ffff"
let values = range.getValues(); // [[1],[2],[3]...]
let colors = range.getBackgrounds(); // [[#ffffff],[#00ffff],[#ffffff]...]
let sum = 0
for (let i = 1; i != values.length; i++){ // starting at 1 to skip first row
let value = values[i][0]
let color = colors[i][0]
if (color == targetColor) {
sum += value
}
}
let resultCell = sheet.getRange('B2');
resultCell.setValue(sum);
}
This script will sum the values in A1:A16 if they are turquoise. Putting the sum in B2.
This is a way to get a sum based of a cell value. This should give you a good starting point to customize to your liking.
Reference
getRange(a1Notation)
getValues()
getBackgrounds()
setValue(value)

setting the time delta format in google sheet

I have a google sheet form response data where column B is a time duration value. But it shows time in AM or PM. I tried different app script but still unable to get the value a time Delta( difference) format
The form response data itself gives time in AM or PM. Help me in this matter.
This is my latest code.
function timeFormat() {
var cell = SpreadsheetApp.getActive().getSheetByName('Sheet4').getRange('B1:B');
cell.setNumberFormat('hh:mm');
}
Is your issue basically with the AM / PM part popping up when you click into the cells formatted as 'hh:mm'? You can fix that by applying the duration format for hours to the entire range (e.g. B2:B)
GAS:
range.setNumberFormat('[hh]:mm');
Or manually under Format -> Number -> More Formats -> Custom number format (a lot easier)
And here's how the end result looks like. Note that you get the 'AM/PM' part popping up when you click into cells formatted as 'hh:mm' but not with the ones formatted as [hh]:mm:
You could use this function as a cell function and then format the cells as a duration and you will get hours:minutes:seconds.
function days(Start,End) {
if(Start && End)
{
var second=1000;
var minute=60*second;
var hour=minute*60;
var day=hour*24;
var t1=new Date(Start).valueOf();
var t2=new Date(End).valueOf();
var d=t2-t1;
return d/day;
}
else
{
return 'Invalid Inputs';
}
}
This essentially returns the date number in the form that the spreadsheet understands as days and fraction of days.

How to set the cell format (as date and time difference formate)

I am new to google app scripts.I have created a google sheets from my FR which parses data first based on condition and then based on type of value. I was able to do this successfully using script but from this newly created google sheets the column B is a time Delta value and column C is cumulative sum of Column B.
The cloumn B and column C format is time Delta format not time in AM or PM. can anyone help me in this issue. i am able to set date format in a cell not able to do the same in whole colum
I am attaching a screen shot of the google sheet.
Here's my code
function format() {
var cell = SpreadsheetApp.getActive().getSheetByName('Sheet4').getRange('A1:A5');
cell.setNumberFormat('dd-mm-yyyy');
}
If I understood correctly, you want to convert the column A dates from dd/MM/yyyy to dd-MM-yyyy
You can do it with the new Date() format:
function format() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Sheet4');
var dates = sheet.getRange('A1:A5').getValues();
for (var i = 0; i < dates.length; i++){
sheet.getRange('A'+(i+1)).setValue(new Date(new Date(dates[i]))).setNumberFormat('dd-MM-yyyy');
}
}
You have to loop through each row and apply setNumberFormat to the formatted value as Date.

Getting an error parsing data in google sheet custom script function

I created a simple custom function to test in google sheets script functions. The function definition is :
/**
* convert duration such as 1:30 to 1.5
*
* #customfunction
*/
function SIMPLETEST(input) {
// simply return the input for now to test.
return input;
}
and in my spread sheet I have a cell A2 that have value 3:30:00. when I apply this function on B2 for example set b2 to: =DURATION_DECIMAL(A2) it returns 12/30/1899 which I believe is base date.
Why is this happening?
It's because you must have the data type for that cell set to "automatic" or "duration", and Google Sheets will guess that "3:30:00" is a type of date/time for automatic, and for duration it converts it to date/time for passing to your function. It lets you keep it in your format (#:##:##), but when you pass it to your custom formula, Sheets first converts it to a Javascript Date object, which your function then returns, and sheets automatically displays as a regular date (12/30/1899). See Google's warning about Date conversions with custom functions here.
The easiest solution is to just explicitly set your input format to "plain text" using the format selection dropdown, and then in your custom function code, you can parse it as you see fit.
For example, I used this StackOverflow answer to write your custom function:
function DURATION_DECIMALS(input){
// https://stackoverflow.com/a/22820471/11447682
var arr = input.split(':');
var dec = parseInt((arr[1]/6)*10, 10);
return parseFloat(parseInt(arr[0], 10) + '.' + (dec<10?'0':'') + dec);
}
And here it is working with format set to plain text:
This works for me:
function durdechrs(dt) {
return Number((dt.valueOf()-new Date(dt.getFullYear(),dt.getMonth(),dt.getDate()).valueOf())/3600000).toFixed(2);
}