Google Apps Script: Reading Duration values is off by X minutes - javascript

I'm working with duration fields on Google Spreadsheet on my timezone
Here is an example of the data I'm working with and my Spreadsheet configuration
Sample data:
actividadID
destinoID
atractivoID
actividadDescripc
actividadDuracion
actividadTipo
da839da6
ae4f25ff
'46432440
Visita
1:00:00 a. m.
EspecĂ­fica
Script Objective
The idea is to loop through a subset of these fields so for that I'm grabbing all the values of the spreadsheet and filtering them:
function TourDuration(tourID, mySS) {
const touractivData = mySS.getSheetByName("TourActividad").getDataRange().getValues();
const activData = mySS.getSheetByName("Actividad").getDataRange().getValues();
var current_activList = touractivData.filter(function(item){
return item[1] == tourID; //Match Bot Numbers && Unprocessed trades
});
Logger.log("current_activList:");
Logger.log(current_activList);
When I check the result of the data I'm grabbing I see data that is off, going into negative not only by hours but also by minutes:
[da839da6, ae4f25ff, 46432440, Visita a la Plaza Mayor de Lima, Sat
Dec 30 01:08:36 GMT-05:00 1899, EspecĂ­fica]
(This one should be the equivalent to 1 hour, but instead I got a negative time)
My goal is to add all the durations that match my filter into a consolidated one without success.
Here is the full code at the moment:
function TourDuration(tourID, mySS) {
const touractivData = mySS.getSheetByName("TourActividad").getDataRange().getValues();
const activData = mySS.getSheetByName("Actividad").getDataRange().getValues();
var current_activList = touractivData.filter(function(item){
return item[1] == tourID; //Match Bot Numbers && Unprocessed trades
});
var current_activList_length = current_activList.length;
var total_duration = 0;
for(var i = 0; i < current_activList_length; i++){
var activDuration = activData.filter(function(item){
return item[0] == current_activList[i][2]; //Match Bot Numbers && Unprocessed trades
});
var duration = Utilities.formatDate(new Date(activDuration[0][4]), "GTM-5", "dd/MM/yyyy HH:mm");
var dur2 = new Date(activDuration[0][4]).toString().substr(25,6)+":00";
dateString = Utilities.formatDate(activDuration[0][4], dur2, "MM/dd/yyyy HH:mm:ss");
Logger.log(duration)
Logger.log(dur2)
Logger.log(dateString)
total_duration = total_duration;
}
}
And this is the result of the log for a register with a 45min duration:
Could it be that the App Script interface has a different timezone?
Although the minutes difference is hard to explain since usually the differences are in full hours.
Also, I don't plan on using getDisplayValue() since I need to consult the whole sheet and getDataRange() would be more efficient.

Explanation:
Also, I don't plan on using getDisplayValue() since I need to
consult the whole sheet and getDataRange() would be more efficient.
getDisplayValues is used instead of getValues() and its purpose is to get the displayed value as it is shown in the sheet.
Please refer to this post for more details:
Difference between getValue() and getDisplayValue() on google app script
On the other hand, getDataRange() is not a sophisticated function.
This expression:
const activSheet = mySS.getSheetByName("Actividad");
const activData = activSheet.getDataRange().getValues();
is identical to this:
const activSheet = mySS.getSheetByName("Actividad");
const activData = activSheet.getRange(1,1,activSheet.getLastRow(),
activSheet.getLastColumn()).getValues();
Towards the solution:
By using getDisplayValues you will be able to get the value as it is displayed in your sheet:
function TourDuration() {
const mySS = SpreadsheetApp.getActive();
const activSheet = mySS.getSheetByName("Actividad");
const activData = activSheet.getDataRange().getDisplayValues();
console.log(activData[1][4]); // 1:00:00
}
However, you can't take the full date from this expression because the date is not specified in the cell. If you expand the change the format of your cells in column E to date:
you will see that you are getting exactly the info that is stored in the cell. So the code works as expected. The issue has to do with the missing information in the sheet itself.
You need to store the correct date in your sheet:
and then if you change the format back to time, the date will still be correct and your current script will be able to get it properly.

Related

My code just decided to stop working and I don't know why. (javascript google appscript)

Thanks in advance to anyone who can help with my issue. I'm not a professionnal I just code when I don't want to do a task anymore.
So I'm running a script to send automaticaly emails 3 days after I see a client. Two days ago, I see that my emails are not been sent at the correct date. So I enter different date to see what's the problem is and the code just send reply for any given date in my google sheet. All date but the 31/05/2022 for some reason.
So I just waited a couple of days to fix it and when I came back the code just stop working completely. Meaning it doesn't send any email anymore.
I'm using the following code with a time trigger and I dont see why it doesnt work anymore.
function MailingAutoJuillet() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Juillet");
var startRow = 3;
var numRows = sheet.getLastRow()-1;
var dataRange = sheet.getRange(startRow, 1, numRows, sheet.getLastColumn());
var data = dataRange.getValues();
var EMAIL_SENT = 'EMAIL_SENT';
var NO_EMAIL = 'NO_EMAIL';
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var date = new Date();
var sheetDate = new Date(row[2]);
Sdate = Utilities.formatDate(date,'Europe/Paris','EEE, MMM d, yyyy')
SsheetDate = Utilities.formatDate(sheetDate,'Europe/Paris','EEE, MMM d, yyyy')
if (Sdate >= SsheetDate+3){
if (row[13] != EMAIL_SENT)
if (row[13] != NO_EMAIL) {
const HTMLTemplate = HtmlService.createTemplateFromFile("HTML Mail de rappel")
const HTMLforemail = HTMLTemplate.evaluate().getContent()
var emailAddress = row[9];
var emailText = "Text";
var subject = "Text";
var option={
htmlBody:HTMLforemail
};
GmailApp.sendEmail(emailAddress,subject,emailText,option);
sheet.getRange(startRow+i,14).setValue("EMAIL_SENT");
}
}
}
}
Attempting to use greater than operator to compare strings
You cannot compare dates in this manner
Sdate = Utilities.formatDate(date,'Europe/Paris','EEE, MMM d, yyyy')
SsheetDate = Utilities.formatDate(sheetDate,'Europe/Paris','EEE, MMM d, yyyy')
The above are strings and cannot be compared with greater than operators
if (Sdate >= SsheetDate+3){
Take a look at the getTime() or valueOf() methods that return milliseconds which can be compared in the manner that you wish.
use the function Logger.log() to know what exactly you code are doing.
Since you don't have any clue I would recommend starting knowing the values of each variable:
Logger.log("varname=",varname);
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Juillet");
ex: Logger.log("sheet=",sheet);
Also, I don't know how exactly is your sheet, but let my try to understand what is happening here:
var sheetDate = new Date(row[2]); //row is expected to be an array
but
var row = data[i]; so... row receive here the values in data? It's data an 2d array?
Also, check your executions. It's the fourth icon on the left panel in the new IDE
You can't compare dates that way.
SsheetDate = Utilities.formatDate(sheetDate,'Europe/Paris','EEE, MMM d, yyyy')
...is going to produce a string value, like "Tue, Jun 7, 2022". You can't add the number three to that to get a date three days later. All you'll be doing is comparing something like "Tue, Jun 7, 2022" to "Tue, Jun 7, 20223", and they will compare as strings, not as dates or numbers.
A day is 86,400,000 milliseconds. When Sdate is three or more days after SsheetDate, Sdate.getTime() > SsheetDate.getTime() + 259200000.
There are more elegant, easier-to-read ways to compare dates, but those methods are more complicated and/or depend on particular date/time libraries.

Google forms to calendar event issues with date format

So I have a very simple form that takes 3 inputs, a title, start and end date. I have tried to use a simple script to produce a calendar event. this can be seen below.
function onFormSubmit(e) {
var title = e.values[1];
var start_time = new Date(e.values[2]);
var end_time = new Date(e.values[3]);
CalendarApp.createEvent(title, start_time, end_time);
}
The issue I have is that as the date string is UK format (e.g. 05/12/2016 12:00:00) it is logging the events as 12th May as opposed to 5th December.
I am new to all of this so am looking for an elegant and simple solution I understand, not just to copy code I don't.
Thanks.
function convertUKDateToUSDate(date) {
const arr = date.split('/');
const temp = arr[0];
arr[0] = arr[1];
arr[1] = temp;
return arr.join('/');
}
will convert a date string with the prefix "DD/MM/" into "MM/DD/YYYY" format. Split turns the string into an array like ["DD", "MM", "YYYY HH:MM:SS"] and then the temporary variable is used to swap the "MM" and "DD" before the array entries are joined back together with the same character that was used to split them. You'll end up with a final onFormSubmit(e) like this:
function onFormSubmit(e) {
var title = e.values[1];
var start_time = new Date(convertUKDateToUSDate(e.values[2]));
var end_time = new Date(convertUKDateToUSDate(e.values[3]));
CalendarApp.createEvent(title, start_time, end_time);
}
Obviously I'm assuming e.values[2] and e.values[3] are strings. If they're Date objects already (or if you just want a shorter solution), then consider using the Moment.js (the premier Date object library) format function to convert between the formats. Normally I'd recommend using Moment anyways but you said you wanted something you could understand instead of copy.

Google Form on Submit get values and format the time

I am using Google Apps Script with a Google form. When the user submits the Google Form I get a value from a question. I then take that value and make it a date object, from what I saw on this post about daylight savings I use that to determine the timezone. I run the date object through Utilities.formatDate and want to get the correctly formatted date.
example: 9:00 AM
But instead I am getting a completely different time than expected.
My question is: Can someone help me understand why the below code is outputting a time that is 3 hours different?
function onSubmit(e) {
var values = e.values;
Logger.log(values);
try {
var start1 = new Date(values[3]);
var startN = new Date(start1).toString().substr(25,6)+"00";
var startT = Utilities.formatDate(start1, startN, "h:mm a");
Logger.log(startT);
} catch(error) {
Logger.log(error);
}
}
The assumption that Utilities formatDate does not support GMT... parameter is not true.
The post you mentioned in reference is used to get calendar events and is a useful way to get the right value when you get events from another daylight saving period (getting the TZ info from the calendar event itself), for example events for next month will be in "summer time" while we are still in "winter time"...
Your issue might come from different sources depending on time zone settings of your script vs timezone of the source. Could you describe the exact configuration in which you use this script ?
In the mean time, here is a small code that demonstrates how the code is working + the logger results :
function testOnSubmit() {
var eventInfo = {};
var values = {};
values['3'] = new Date();
eventInfo['values'] = values;
Logger.log('eventInfo = '+JSON.stringify(eventInfo)+'\n\n');
onSubmit(eventInfo);
}
function onSubmit(e) {
var values = e.values;
try {
var start1 = new Date(values[3]);
Logger.log('onSubmit log results : \n');
Logger.log('start1 = '+start1)
var startN = new Date(start1).toString().substr(25,6)+"00";
Logger.log('startN = '+startN);
var startT = Utilities.formatDate(start1, startN, "h:mm a");
Logger.log('result in timeZone = '+startT);
} catch(error) {
Logger.log(error);
}
}
EDIT : additionally, about the 30 and 45' offset, this can easily be solved by changing the substring length like this :
var startN = new Date(start1).toString().substr(25,8);
the result is the same, I had to use the other version a couple of years ago because Google changed the Utilities.formatDate method at some moment (issue 2204) but this has been fixed.
EDIT 2 : on the same subject, both methods actually return the same result, the GMT string has only the advantage that you don't have to know the exact timezone name... there is also the Session.getScriptTimeZone() method. Below is a demo script that shows the resulst for 2 dates in January and July along with the log results :
function testOnSubmit() {
var eventInfo = {};
var values = {};
values['3'] = new Date(2014,0,1,8,0,0,0);
eventInfo['values'] = values;
Logger.log('eventInfo = '+JSON.stringify(eventInfo)+'\n\n');
onSubmit(eventInfo);
values['3'] = new Date(2014,6,1,8,0,0,0);
eventInfo['values'] = values;
Logger.log('eventInfo = '+JSON.stringify(eventInfo)+'\n');
onSubmit(eventInfo);
}
function onSubmit(e) {
var values = e.values;
var start1 = new Date(values[3]);
Logger.log('onSubmit log results : ');
Logger.log('start1 = '+start1)
var startN = new Date(start1).toString().substr(25,8);
Logger.log('startN = '+startN);
Logger.log('result in timeZone using GMT string = '+Utilities.formatDate(start1, startN, "MMM,d h:mm a"));
Logger.log('result in timeZone using Joda.org string = '+Utilities.formatDate(start1, 'Europe/Brussels', "MMM,d h:mm a"));
Logger.log('result in timeZone using Session.getScriptTimeZone() = '+Utilities.formatDate(start1, Session.getScriptTimeZone(), "MMM,d h:mm a")+'\n');
}
Note also that the Logger has its own way to show the date object value ! it uses ISO 8601 time format which is UTC value.
Try this instead:
var timeZone = Session.getScriptTimeZone();
var startT = Utilities.formatDate(start1, timeZone, "h:mm a");
The Utilities.formatDate function expects a time zone that is a valid IANA time zone (such as America/Los_Angeles), not a GMT offset like GMT+0700.
I am making the assumption that Session.getScriptTimeZone() returns the appropriate zone. If not, then you might need to hard-code a specific zone, or use a different function to determine it.
Additionally, the +"00" in the script you had was assuming that all time zones use whole-hour offsets. In reality, there are several that have 30-minute or 45-minute offsets.

add time from cells programmatically

I have a a spreadsheet which I use to note how I spent my time on a project. In that spreadsheet I have a couple of columns, one of which is the time spent doing something and the other is the category of what that something is (for example meetings or accounting or calling customers). I am trying to write a script which I pass the name of the category and it then loops though all the rows to see if the category equals the category I passed it, and if so it adds the time to the counter. I am however having trouble adding the time together. What I have so far is this:
function getTimeInCat(cat){
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var time = new Date();
var counter = 0;
for(var i = 6; i < numRows; i++){
var carCell = "F" + i;
var cellName = "E" + i;
if(sheet.getRange(i, 6).getValue() == cat){
time += sheet.getRange(i, 5).getValue();
counter++;
}
}
return time;
}
Instead of what I want I get this in return:
Thu Jan 30 2014 18:29:22 GMT-0000 (GMT)Sat Dec 30 1899 00:50:39 GMT-0000 (GMT)Sat Dec 30 1899 00:05:39 GMT-0000 (GMT)
EDIT: It is giving the right amount of rows (The remnants of that test are still in the code)
Try var time = 0. Date() I think will transform your time to a date.
The values of those cells are actually Date objects. This is why you're getting a long string of timestamps when you add the values of the cells together. For whatever reason, Google decided that when you're entering a duration, it will actually be an offset of HH:mm:ss after 12/30/1899. Try changing the format of those times to a date (Format > Number > Date) and you'll see it changes to that date.
Luckily, with Date objects, you can use the getTime() function to get your total duration. getTime() gives you the number of milliseconds since 1/1/1970 so it will actually return very large negative numbers for those cells but that's easy enough to manage. You just need to create a Date object with 12/30/1899 as the date and subtract that from the value of the cell you want.
var base = new Date("12/30/1899").getTime(); // -2.2091436E12
var cell = sheet.getRange(row,col).getValue().getTime(); // some other large negative number
var delta = cell - base; // this should be your duration in milliseconds

apps-script: copying a spreadsheet cell to a table, with formatting

I'm writing a script which opens an external Google spreadsheet via a URL. The script needs to copy cells into a FlexTable, and display them.
The problem is that the spreadsheet cells have a custom display format, to show them as elapsed times (m:ss.SS). If I just load up the table element with table.setWidget(x, y, app.createLabel(z)), the label just displays a date in 1899. Any idea how I can copy over the formatting?
Thanks.
EDIT
This nearly does it:
date = new Date(z); // 'z' from 'getValues'
elapsed = Utilities.formatDate(date, "GMT", "m:ss.SS");
table.setWidget(x, y, app.createLabel(elapsed));
Unfortunately, "m:ss.SS"doesn't work; it always displays the milliseconds as 0. Any ideas?
ANOTHER EDIT
apps-script seems to have completely messed this up. This code:
date = new Date();
elapsed = Utilities.formatDate(date, "GMT", "m:ss.SS");
table.setWidget(x, y, app.createLabel(elapsed));
Correctly shows the minutes and seconds portion of the current time, but there are 3 decimal places shown, not 2. This code:
date = new Date(z); // 'z' from 'getValues'
Doesn't work. When z is displayed in the spreadsheet with a non-zero number of milliseconds, this constructor always sets the number of ms to 0 (getMilliseconds returns 0).
Anyone have a work-around? I need apps-script to handle athletics event times, which are generally seconds to 2 decimal places, and possibly a few minutes.
I've noticed that these correspond to dates on December 30, 1899. This is odd - shouldn't this date have a negative time value? The GWT source code for formatDate handles negative values as a special case, and it's difficult to see what it's doing.
There are multiple related and unfixed bugs in this area; see here, for example. It's a bad, bad, idea to let Google sheets handle elapsed times. My eventual solution was to use "integers" to hold the times, and do all the required formatting and processing in GAS (carefully, because they're actually inexact floats, of course). This is pretty easy, and is much better than battling with Date. The only complication is if you need to import Dates from Excel. I had to modify the Excel spreadsheet to convert the dates to ms, and import those instead.
here is a possible workaround to show time the way you want : testsheet (please don't modify)
I used an Ui to define the time value but converted it to a date object in the script to verify how it works with a "real" date object.
EDIT : following the different answers to this post and this other by AdamL, I finally get something that allows displaying and calculating with short time values in hundreds of seconds with help of a UI and custom formatting.
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [ {name: "Change cell format", functionName: "EditFormat"},
{name: "Reset format to default", functionName: "ResetFormat"},
{name: "show UI test", functionName: "sportChrono"},
]
ss.addMenu("Custom Format", menuEntries);
}
function EditFormat() {
var oldname = SpreadsheetApp.getActiveRange().getNumberFormat();
var name = Browser.inputBox("Current format (blank = no change):\r\n"+ oldname);
SpreadsheetApp.getActiveRange().setNumberFormat((name=="")?oldname:name);
}
function ResetFormat() {
SpreadsheetApp.getActiveRange().setNumberFormat("0.###############");
}
function sportChrono() {
var app = UiApp.createApplication().setTitle('Show Time in mm:ss.SS');
var main = app.createGrid(3, 4).setWidth('100');
var button = app.createButton('validate')
var btn2 = app.createButton('send to Cell').setId('btn2').setVisible(false)
var time = app.createTextBox().setId('time').setName('time')
var cellist = app.createListBox().addItem('A1').addItem('A2').addItem('A3').addItem('A4').addItem('A5').setVisible(false).setId('cellist').setName('cellist')
var min = app.createTextBox().setName('min');
var sec = app.createTextBox().setName('sec');
var Msec = app.createTextBox().setName('Msec');
main.setText(0,0,'minutes').setText(0,1,'secs').setText(0,2,'millisecs')
main.setWidget(1,0,min).setWidget(1,1,sec).setWidget(1,2,Msec);
main.setWidget(1,3,button).setWidget(2,0,time).setWidget(2,1,cellist).setWidget(2,2,btn2)
var handler = app.createServerHandler('show').addCallbackElement(main)
button.addClickHandler(handler)
var handler2 = app.createServerHandler('writeToSheet').addCallbackElement(main)
btn2.addClickHandler(handler2)
app.add(main)
ss=SpreadsheetApp.getActive()
ss.show(app)
}
function show(e){
var ss=SpreadsheetApp.getActive();
var app = UiApp.getActiveApplication();
var min = e.parameter.min
var sec = e.parameter.sec
var Msec = e.parameter.Msec
var time = new Date()
time.setHours(0,min,sec,Msec)
var nmin = digit(time.getMinutes())
var nsec = digit(time.getSeconds())
var nMsec = digit(time.getMilliseconds())
app.getElementById('time').setText(nmin+':'+nsec+'.'+nMsec)
var btn2 = app.getElementById('btn2').setVisible(true)
var cellist = app.getElementById('cellist').setVisible(true)
return app
}
function writeToSheet(e){
var range = e.parameter.cellist
var val = e.parameter.time
var ss=SpreadsheetApp.getActive();
ss.getRange(range).setFormula('=value("0:'+val+'")');
}
function digit(val){
var str
if(val<10){
str='0'+val}
else if(val>9&&val<99){
str=val.toString()}
else{
str=Math.round(val/10).toString()
}
return str
}

Categories

Resources