Invalid argument in getContactsByCustomField - google-apps-script

I'm writing a google apps script to keep my contacts in sync with a spreadsheet of contacts. We have a lot of contacts with no emails, so I needed to create a custom unique id. As these contacts are added via a Google form (by one staff member only), it made sense (to me) to just use the timestamp as the unique id.
So in my script, I'm checking for matching contacts using this custom field, as follows:
//get the time stamp from the spreadsheet
var timeStamp = sheet.getRange(i+2, 1, 1, 1).getValue();
//find contacts with that timestamp in their Time Stamp field
var contacts = ContactsApp.getContactsByCustomField(timeStamp, 'Time Stamp');
The second line (20th line in my function) throws this error:
Invalid argument (line 20, file "Code")
I notice in the google API reference that the second parameter to getContactsbyCustomField needs to be an 'extended field', but the example provided suggests I can name this what I want.
I'm a bit lost on how to fix this. 10 years since I last did any decent coding, and that was mostly PHP/MySQL stuff, so feeling like I'm back at coding pre-school at the moment!

This is happening because when you get the Timestamp with the following line of code it is casting it to a Date object;
var timeStamp = sheet.getRange(i+2, 1, 1, 1).getValue();
You would expect a value like 12/14/2015 9:50:03 but you are really getting a value like Mon Dec 14 2015 09:50:03 GMT-0500 (EST)
I'm not sure if this is working as intended or if an issue needs to be raised.
I was able to fix this by converting the date when I added it as a custom field.
var c = ContactsApp.createContact(e.namedValues["First Name"],e.namedValues["Last Name"] , e.namedValues["Email"]);
c.addCustomField("Time Stamp", new Date(e.namedValues["Timestamp"]).toString());
You will also need to convert the read in value to a string also.
//get the time stamp from the spreadsheet
var timeStamp = sheet.getRange(i+2, 1, 1, 1).getValue();
//find contacts with that timestamp in their Time Stamp field
var contacts = ContactsApp.getContactsByCustomField(timeStamp.toString(), 'Time Stamp');

Related

Convert a string to a date format using a script or a Google Sheets built-in formula

DESCRIPTION:
I want to convert a DD/MM/YYYY HH:mm or 25/01/2022 11:00 string, in an accepted date format.
Doesn't matter which one, it just has to be recognized by Apps Script and Google Sheets and be able to work with it.
If you can provide an Apps Script's code (not a formula in Google Sheets like I attempted to do) that converts the string into a date and then set the values in another range, to work with them as dates, I would be grateful, thanks.
If it's a Google Sheet formula no problem, as long as it works.
TRIED:
After many attempts, I tried to build a custom formula putting pieces together around the web but it doesn't function
//formula is translated from italian
=ARRAYFORMULA(IF(F10:F="",,TEXT(DATE(
IF.ERROR(REGEXEXTRACT(F10:F, "/(\d+) "), YEAR(F10:F))*1,
IF.ERROR(REGEXEXTRACT(F10:F, "/(\d+)"), MONTH(F10:F))*1,
IF.ERROR(REGEXEXTRACT(F10:F, "\d+"), DAY(F10:F))*1)+
IF.ERROR(TIME.VALUE(F10:F), REGEXEXTRACT(F10:F, "\d+:\d+")+
IF(REGEXMATCH(F10:F, "PM"), 0.5, 0)), "yyyy-mm-dd hh:mm")))
It gives a #VALUE error, which says "'11:00' is a string and can't be recognized as a date" (11:00 is an example).
I've also got the Regular Expression, but I don't know if it's correct and how to use it in code:
/([\d])\w+\/([\d])\w+\/([\d])\w+\s([\d])\w+\:([\d])\w+/g
I also tried changing the time zone but it didn't work.
Keep in mind I'm using the Italian time zone, if it's possible I'd rather keep it as it is.
Table example (like I said, what's important is that dates are accepted as dates):
F: Column source strings
Q: Column desired dates recognizable as dates by Sheets
(Q because it's the real column where I want to put the formula)
F
..
Q
16/02/2023 16:00
16/02/2023 16:00:00
25/11/2022 15:00
25/11/2022 15:00:00
For #Cooper and the solution based on the script.
I've customized the script, but it doesn't recognize the split function anymore (copy and paste of your function logs what it expects in Apps Script), and doesn't get any results in overwriting the existing string dates.
let dateStringed; //source wrong dates
var i = 0;
var flatArray;
function expired() {
//bLast is the range Last Row
dateStringed = gen.getRange(10, 6, bLast, 1).getValues();
flatArray = [].concat.apply([], dateStringed);
while (i <= bLast) {
i++;
convert();
};
Logger.log(flatArray);
gen.getRange(10, 6, bLast, 1).setValues(flatArray);
};
function convert(s=flatArray[i]) { //instead of "25/01/2022 11:00"
let [d,m,y,hr,mn] = s.split(/[\/ :]/)
Logger.log('y: %s m: %s d: %s hr: %s mn: %s',y,m,d,hr,mn);
Logger.log(new Date(y,m - 1,d,hr,mn).toLocaleString());
//don't know if it's correct, but it logs the dates
//in an easier syntax
};
For #doubleunary solution:
Demo SHEET ITA
In the sheet I copied and pasted the first column of my private original sheet, the F column with the text dates, and the Q10 cell I've pasted the formula as it is
I made sure to set local to Italy but to display english name formulas.
I don't know why, here it colors green and it doesn't give me a result.
But I did a test, and set the sheet tu US time and it functions. Any idea on how to make it function in Italian version?
Demo SHEET US
Solved: I used this script
function dateCorrected(){
gen.getRange('N10:N').clearContent();
//get the formula from another code sheet:
//'=arrayformula( SE.ERRORE( 1 / VALORE(
//regexreplace( to_text(F10:F);
//"(\d+)/(\d+)/(\d+) (\d+):(\d+)"; "$3-$2-$1 $4.$5" ) ) ^ -1 ) )'
var dateCorr = codeSheet.getRange('T1').getFormula();
Logger.log(dateCorr);
gen.getRange('N10').setFormula(dateCorr);
gen.getFilter().sort(14, false);
gen.getRange('N10:N').clearContent();
gen.getRange('N10').setFormula(dateCorr);
}
And this gives me the possibility to delete rows that meet a certain date condition. Thank you all for the support.
It is usually easiest to do the text string to datetime conversion using a spreadsheet formula. You can convert text strings like 25/01/2022 11:00 to dates with this formula in cell G10:
=arrayformula( iferror( 1 / value( regexreplace( to_text(F10:F); "(\d+)/(\d+)/(\d+) (\d+):(\d+)"; "$3-$2-$1 $4.$5" ) ) ^ -1 ) )
Format the result column as Format > Number > Date time.
In the event you need to "fix" those datetime values in place, you can replace the formula results with static values with Control+C to copy and Control+Shift+V to paste values only, or do the same with a simple range.setValues(range.getValues()) script bit.
In the event you need to pass those datetime values to Apps Script, it is usually easiest to get them as Date objects rather than text strings. The Date objects will refer to the same moment in time (in UTC) as the date times in the spreadsheet (in the spreadsheet's time zone).
You should note that Apps Script is JavaScript which means that Date objects are always in the UTC timezone. If you log them or output them in some other way, they will not be shown in the Italian timezone as you expect.
There are two easy ways to present such dates in a human-readable format in the spreadsheet's timezone. The first is to directly get the data as a text string in the format that it is shown in the spreadsheet:
function test1() {
const ss = SpreadsheetApp.getActive();
const dateStrings = ss.getRange('Sheet1!G10:G')
.getDisplayValues()
.flat()
.filter(String);
console.log(dateStrings);
}
The second is to get the data as Date objects and convert them to text strings using the spreadsheet's timezone, like this:
function test2() {
const ss = SpreadsheetApp.getActive();
const timezone = ss.getSpreadsheetTimeZone();
const dates = ss.getRange('Sheet1!G10:G')
.getValues()
.flat()
.filter(String)
.map(date =>
Object.prototype.toString.call(date) === '[object Date]'
? Utilities.formatDate(date, timezone, 'dd/MM/yyyy HH:mm')
: date
);
console.log(dates);
}
Convert String to Date:
function convert(s="25/01/2022 11:00") {
let [d,m,y,hr,mn] = s.split(/[\/ :]/)
Logger.log('y: %s m: %s d: %s hr: %s mn: %s',y,m,d,hr,mn);
Logger.log(new Date(y,m - 1,d,hr,mn));
}
Execution log
10:58:11 AM Notice Execution started
10:58:12 AM Info y: 2022 m: 01 d: 25 hr: 11 mn: 00
10:58:12 AM Info Tue Jan 25 11:00:00 GMT-07:00 2022
10:58:13 AM Notice Execution completed
To convert a string to a Date object in Google Apps Script use Utilities.parseDate.
Example:
function myFunction(){
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const timeZone = spreadsheet.getSpreadsheetTimeZone();
const date = Utilities.parseDate('25/01/2022 11:00',timeZone, 'dd/MM/yyyy HH:mm');
return date;
}
Using the above as a custom function might not make sense for some use cases since the same result might be achieved by using built-in functions which are more efficient and less prone to have problems.
The options for using built-in functions depends on the spreadsheet settings, i.e. DATEVALUE might return different results for ambiguos dates like 25/01/2022 as for certain regions the month goes first and for others the day of the month goes first.
=DATEVALUE("25/01/2022") works correctly when the spreasheet region is set to Italy. You might have to manually set the cell formatting to date in order to make it show a date instead of the time serialized value (a number).
To convert 25/01/2022 11:00 using formulas in the above spreadsheet, use
=INDEX(SPLIT("25/01/2022 11:00";" ");1) + SUBSTITUTE(INDEX(SPLIT("25/01/2022 11:00";" ");2);":";".")
The above formula has two main parts joined by using +. The first part returns the time serialized value corresponding to the date, the second part returns the time serialized value corresponding to the time.
Array formula
=ArrayFormula(DATEVALUE(REGEXEXTRACT(F10:F;"^([ˆ\d/]+) "))+TIMEVALUE(SUBSTITUTE(REGEXEXTRACT(F10:F;" ([ˆ\d/:]+)$");":";".")))
The same concept as the previous formula, but instead of INDEX it uses REGEXEXTRACT.
Google has a Utility to do just that!
let dateTime = '2022-12-16 13:00:00';
let timeZone = 'GMT';
let convertedDateTime = Utilities.formatDate(dateTime, timeZone, 'dd/MM/yyyy HH:ss')
check out Class Utilities for more info.

Trying to generate a unique code for every form reply

I am having some trouble getting a script that can generate a unique code for every form reply entry to work. Now, when I first tried writing it, I used the function name onFormReply(e) since I had read it somewhere, but turn out it didn't really work, so I'm trying to use onEdit(e), but it marks most values of the variables I wrote as undefined, even the argument (e) of the function itself (which is theoretically suposed to be custom and made to resemble the cell/s where the edit took place). Here is the code:
function onEdit(e) {
const ss = SpreadsheetApp.getActiveSheet();
const row = e.getRow();
var date = ss.getRange(row,1).getValue();
var department = ss.getRange(row,6).getValue();
department = department[0] + department[1]
var uniqueNumber = ss.getLastRow()
var finalCode = department + finalDate + uniqueNumber
ss.getRange(row,15).setValue(finalCode)
}
If you are dealing with Google Form responses, you can use the timestamp in column A as a unique ID. These timestamps are accurate down to the millisecond and will for all practical purposes always be unique.
The great benefit of this is that you do not need a script to get those unique IDs. Instead, use this array formula in row 1 of a free column in the right in the form responses sheet:
=arrayformula(
{
"Unique ID";
iferror( text( 1 / A2:A ^ -1, "yyyy-MM-dd_HH-mm-ss-000") )
}
)
The formula will fill the whole column automatically and will continue giving more results as new form responses are submitted.

Google scripts converting text parameter to acceptable date object

I have scoured SO and the interwebs and discovered the horror that is date handling in Google Apps Script. I have found conflicting answers about date() and formatDate(), and have yet to find a definitive, working guide that shows the exact steps to take arbitrary text and create a date object
I have a simple ui.Prompt() that asks for a date in human-friendly terms, like "MM-DD-YYYY". I want to do date math on it, so ...
// result from ui.Prompt(), ie "03/01/2019" march 1st
var mytextdate = result.getResponseText();
//I want to do date math, so need a date object ...
var year_num = +mytextdate.substring(6,10);
var month_num = +mytextdate.substring(0,2);
var day_num = +mytextdate.substring(3,5);
var date_mytextdate = new date(year_num, month_num -1, day_num);
The script fails with 'date is undefined' at this point, before I can even do my date math. However I can retrieve the values for year_num, month_num, and day_num without a problem. What else do I need to make `date()' valid?
Make you last line:
var date_mytextdate = Utilities.formatDate(new Date(year_num, month_num -1, day_num),Session.getScriptTimeZone(),"MM-dd-yyyy");
Utilities.formatDate()

getValue formatting is off when pulling a date from spreadsheet

I'm using a script to convert a spreadsheet to a PDF.
To define the title of the PDF, I'm pulling in data from a few specific cells, one of which contains a date. The problem is, the date comes out in a long format in the title. Here's a chunk of my code:
var ss = SpreadsheetApp.openById("XXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); // Opens spreadsheet to pull name from, by ID.
var sheet = ss.getSheetByName('PurchaseOrderForm'); // Sets the sheet
var ponumber = sheet.getRange("G3:G3"); // Define ranges/cells here + below.
var date = sheet.getRange("G2:G2") // Custom cell to pull from
var companyname = sheet.getRange("A11:A11") // These are then added together in line 30
var name = ponumber.getValue()+" | "+date.getValue()+" | "+companyname.getValue()+".pdf"; // Output of the final PDF name. A composition of the above vars, plus some custom text.
This creates the PDF name almost as I want it, except for the date part. This is an example of the PDF titles I get:
14PO051 | Tue Jan 28 2014 00:00:00 GMT-0000 (GMT) | Micronclean.pdf
The date comes out, but not in a format I want/need. In the spreadsheet the date cell, G2, is formatted DD/MM/YY. I need something like this to come out in my title, otherwise it's just too long to be of any use.
If anyone knows how to get it down to DD/MM/YY or equivalent, I'd be grateful to know!
One workaround would be to not have the dates formatted, and just manually input them into the spreadsheet. I hope it doesn't come to that, my colleagues like not having to type the whole thing out.
var date = Utilities.formatDate(sheet.getRange("G2:G2").getValue(), ss.getSpreadsheetTimeZone(), "dd/MM/YY");
?
To get the values from the sheet as they appear (using sheet formatting), use getDisplayValue or getDisplayValues
https://developers.google.com/apps-script/reference/spreadsheet/range#getdisplayvalues
(use the plural function anytime you're getting more than 1 cell, if possible; batch ops are faster!)

Calculating runtime minus Timestamp

I have a form which activates a procedure via an "On form submit" trigger. At the end of this routine I want to insert the difference in time between the form's Timestamp and the current time at the end of the routine (the difference of which is only a matter of a few seconds).
I've tried many things so far, but the result I typically receive is NaN.
I thought that my best bet would be to construct the runtime elements (H,M,S) and similarly deconstruct the time elements from the entire Timestamp, and then perform a bit of math on that:
var rt_ts = Math.abs(run_time - ts_time);
(btw, I got that formula from somewhere on this site, but I'm obviously grasping at anything at this point. I just can't seem to find a thread where my particular issue is addressed)
I've always found that dealing with dates and time in Javascript is tricky business (ex: the quirk that "month" start at zero while "date" starts at 1. That's unnecessarily mind-bending).
Would anyone care to lead me out of my current "grasping" mindset and guide me towards something resembling a logical approach?
You can simply add this at the top of your onFormSubmit routine :
UserProperties.setProperty('start',new Date().getTime().toString())
and this at the end that will show you the duration in millisecs.
var duration = new Date().getTime()-Number(UserProperties.getProperty('start'))
EDIT following your comment :
the time stamp coming from an onFormSubmit event is the first element of the array returned by e.values see docs here
so I don't really understand what problem you have ??
something like this below should work
var duration = new Date().getTime() - new Date(e.values[0]).getTime();//in millisecs
the value being a string I pass it it 'new Date' to make it a date object again. You can easily check that using the logger like this :
Logger.log(new Date(e.values[0]));//
It will return a complete date value in the form Fri Mar 12 15:00:00 GMT+01:00 2013
But the values will most probably be the same as in my first suggestion since the TimeStamp is the moment when the function is triggered...
I have a function which can show the times in a ss with timestamps in column A. It will also add the time of the script itself to the first timestamp (in row 3) and show this in the Log.
Notice that the google spreadsheet timestamp has a resolution in seconds and the script timestamp in milliseconds. So if you only add, say, 300 milliseconds to a spreadsheet timestamp, it might not show any difference at all if posted back to a spreadsheet. The script below only takes about 40 milliseconds to run, so I have added a Utilities.sleep(0) where you can change the value 0 to above 1000 to show a difference.
function testTime(){
var start = new Date().getTime();
var values = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
for(var i = 2; i < values.length ; i++){
Logger.log(Utilities.formatDate(new Date(values[i][0]),Session.getTimeZone(),'d MMM yy hh:mm:ss' )); // this shows the date, in my case same as the ss timestamp.
Logger.log( new Date(values[i][0]).getTime() ); // this is the date in Milliseconds after 1 Jan 1970
}
Utilities.sleep(0); //you can vary this to see the effects
var endTime = new Date();
var msCumulative = (endTime.getTime() - start);
Logger.log(msCumulative);
var msTot = (msCumulative + new Date(values[2][0]).getTime());
Logger.log('script length in milliseconds ' + msTot );
var finalTime = Utilities.formatDate(new Date(msTot), Session.getTimeZone(), 'dd/MM/yyyy hh:mm:ss');
Logger.log ( finalTime); //Note that unless you change above to Utilities.sleep(1000) or greater number , this logged date/time is going to be identical to the first timestamp since the script runs so quickly, less than 1 second.
}