I am working on Google Spreadsheet's project where I am adding/deleting TimeBased triggers through scripting.
All triggers fired will run one function, which will check its Id and run function accordingly.
I am saving starting trigger and saving it's id with this code.
function startTimer(rn) {
var triggerid = ScriptApp.newTrigger('mainFunction')
.timeBased()
.everyMinutes(1)
.create().getUniqueId();
SpreadsheetApp.getActiveSheet().getRange(1, 8).setValue(triggerid);
}
This functions save's trigger id in this format '6295890543675972447'
Now when trigger is fired and runs function 'mainFunction'.
Using this code I am trying to get the id of trigger.
function mainFunction(e) {
var triggerId = e.triggerUid;
Logger.log(triggerId);
}
At this point, I get the trigger id in this format '6.29589E+18'
If I try to change format with toString() method , the format changes to '6295890543675973000'
I cant match with both formatting to my saved id.
Any idea how can I get id back in format it was when adding trigger?
Thanks
I spent some hours on this, got some hair off.
And I found this page. It has a piece of code that strangely works when you pass e.triggerUid in it, and not when using it directly :
function getFileByTriggerId(triggerId){
var triggers = ScriptApp.getProjectTriggers();
for(var i =0; i<triggers.length; i++){
if(triggers[i].getUniqueId() == triggerId){
return triggers[i].getTriggerSourceId();
}
}
}
Hope it will help someone
When you request the id of a trigger object using its getUniqueId() method, the value returned is a numerical String.
However when a function is called by the trigger, the triggerUid property of the event object will be that string converted to a Number.
The issue is that the string id often (but not always) expresses an integer value larger than the maximum integer value Apps Script can handle safely. When this happens the conversion from string to number results in the zeros in the least significant digits.
So, to compare the two properly you just need to convert the string id to a number, this ensures the same zeroing will occur.
someTrigger.getUniqueId() === event.triggerUid
// false - because the left side is a string, and the right is a number.
Number(someTrigger.getUniqueId()) === event.triggerUid
// true - both sides are now numbers, the same number
someTrigger.getUniqueId() == event.triggerUid
// true - because using the standard comparison operator the string is automatically converted to a number in the background
Here's a note about strict v.s. standard comparison operations in Javascript.
Here's some Apps Script that demonstrates all of the above:
// Run onceOffClockTimer() and wait up to 1 min for the event to fire, then check your log. You might have to run it a few times to see a trigger id where the zeroing occurred
function onceOffClockTimer() {
var newTriggerId = ScriptApp.newTrigger('timerCallbackFn')
.timeBased()
.after(5000)
.create()
.getUniqueId();
}
function timerCallbackFn(triggerEvent) {
var eventTriggerId = triggerEvent.triggerUid,
triggers = ScriptApp.getProjectTriggers();
Logger
.log('### Event Object ###')
.log('id: %s', eventTriggerId)
.log('id type: %s', typeof eventTriggerId)
.log('id as String: %s\n', eventTriggerId.toString());
var largestSafeInteger = 9007199254740991; // Obtained by Number.MAX_SAFE_INTEGER in a Chrome console
Logger
.log('### Interesting Fact ###')
.log('In Apps Script the largest safe integer is: %s\n', largestSafeInteger);
for (var i = 0, x = triggers.length; i < x; i++) {
var triggerID = triggers[i].getUniqueId();
Logger
.log('### Trigger Object [%s] ###', i)
.log('id: %s', triggerID)
.log('id type: %s', typeof triggerID)
.log('id as Number: %s\n', Number(triggerID))
Logger
.log('### Comparisons ###')
.log('Strict (Trigger Event ID === Trigger ID) is %s ', eventTriggerId === triggerID)
.log('Strict (Trigger Event ID === Trigger ID as Number) is %s', eventTriggerId === Number(triggerID))
.log('Direct (Trigger Event ID == Trigger ID) is %s', eventTriggerId == triggerID);
}
}
Logger.log('' + triggerId);;;;
The trigger Id could be a large number (long 8 byte integer, or an even bigger number stored as an object), to overcome this, convert it to binary and then to 64 bit encoded string. This way you can safely store, and compare as well.
var v = Utilities.base64Encode(Utilities.newBlob(e.triggerUid).getBytes())
or to compare
if(Utilities.base64Encode(Utilities.newBlob(e.triggerUid).getBytes()) === Utilities.base64Encode(Utilities.newBlob(triggers[i].getUniqueId()).getBytes()))
As triggerId is a Number object and as such it is automatically converted to the exponential notation.
To handle this you can extend the Number.prototype to handle the correct representation.
Object.defineProperty(Number.prototype, 'toLongString', {
value: function() {
var parts = this.toString().split("e+");
var first = parts[0].replace('.', "");
var zeroes = parseInt(parts[1], 10) - (first.length - 1);
var longString = first;
for (var i = 0; i < zeroes; i++) {
longString += "0";
}
return longString;
}
});
function mainFunction(e) {
var triggerId = e.triggerUid.toLongString();
Logger.log(triggerId);
}
I had this same problem. The solution that worked for me was: toPrecision([numberOfDigits]).
For example:
([largenumber]).toPrecision(27)
Here is the source:
https://teamtreehouse.com/community/covert-scientific-notation-within-variable-to-javascript-number-when-scientific-notation-is-yet-unknown
I hope that this helps someone!
Related
I am working on a project where I require to format incoming numbers in the following way:
###.###
However I noticed some results I didn't expect.
The following works in the sense that I don't get an error:
console.log(07);
// or in my case:
console.log(007);
Of course, it will not retain the '00' in the value itself, since that value is effectively 7.
The same goes for the following:
console.log(7.0);
// or in my case:
console.log(7.000);
JavaScript understands what I am doing, but in the end the actual value will be 7, which can be proven with the following:
const leadingValue = 007;
const trailingValue = 7.00;
console.log(leadingValue, trailingValue); // both are exactly 7
But what I find curious is the following: the moment I combine these two I get a syntax error:
// but not this:
console.log(007.000);
1) Can someone explain why this isn't working?
I'm trying to find a solution to store numbers/floats with the exact precision without using string.
2) Is there any way in JS/NodeJS or even TypeScript to do this without using strings?
What I currently want to do is to receive the input, scan for the format and store that as a separate property and then parse the incoming value since parseInt('007.000') does work. And when the user wants to get this value return it back to the user... in a string.. unfortunately.
1) 007.000 is a syntax error because 007 is an octal integer literal, to which you're then appending a floating point part. (Try console.log(010). This prints 8.)
2) Here's how you can achieve your formatting using Intl.NumberFormat...
var myformat = new Intl.NumberFormat('en-US', {
minimumIntegerDigits: 3,
minimumFractionDigits: 3
});
console.log(myformat.format(7)); // prints 007.000
Hi
You can use an aproach that uses string funtions .split .padStart and .padEnd
Search on MDN
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
Here you have an example:
const x = 12.1;
function formatNumber( unformatedNumber) {
const desiredDecimalPad = 3;
const desiredNonDecimalPad = 3;
const unformatedNumberString = unformatedNumber.toString();
const unformatedNumberArr = unformatedNumberString.split('.');
const decimalStartPadded = unformatedNumberArr[0].padStart(desiredDecimalPad, '0');
const nonDecimalEndPadded = unformatedNumberArr[1].padEnd(desiredNonDecimalPad, '0');
const formatedNumberString = decimalStartPadded + '.' + nonDecimalEndPadded;
return formatedNumberString;
}
console.log(formatNumber(x))
I am trying to load an existing answer and then add a number to it. The answerTotal variable has been set to the value of the recorded answer.
This then should be increasing by 12.5 each time the if statement actions. The problem is that this is not what is happening.
The number is being added on to the end of the loaded answer, for example if the load answer is 25, the output would be 2512.5 when it should be 37.5.
I have seen answers on here mentioning parseInt but it either doesnt work or im not using it correctly. parse answer
Here is what I have at the moment:
var answerTotal = 0;
window.parent.LoadAnswerReturned = function (Reference, Value) {
answerTotal = Value;
}
setTimeout(function(){
window.parent.LoadAnswer('TEST');
}, 100);
function checkAnswer(clicked) {
if(...){
...
answerTotal += 12.5
}
}
Please let me know if any more information is needed. Cheers.
It seems that the variable answerTotal is a string. You need to convert it to number using Unary Plus and then add it other number.
function checkAnswer(clicked) {
if(...){
...
answerTotal = +answerTotal + 12.5
}
}
The unexpected result is because by the type of the variable data that is string rather than number. This in turn means that string addition is performed:
"25" + "12.5" = "2512.5"
Instead, you should update you code to ensure arithemtic addition is performed instead:
function checkAnswer(clicked) {
if(...){
...
/* Use parseFloat to ensure two numbers are added, rather than a string
and a number */
answerTotal = Number.parseFloat(answerTotal) + 12.5;
}
}
You should parse your float variable, so you ensure you are using float variables:
answerTotal = parseFloat(answerTotal) + 12.5;
I am currently trying to take all changes made to a form and put it into a JSON. If there are no changes than the JSON is empty. The form contains values that are strings, ints, and floats. So, I cannot cast them all as a specific type.
This wasn't an issue until I ran into the result form the console.log statement batchsize:string 1.0 does not equal string 1. Obviously this is correct in saying the two strings are not equal, but I am having trouble with finding a way that allows me to compare them without this being an issue. Does anyone have any advice
function getChanges()
{
//Get All User made changes form the website
var returnJSON = "{ ";
$('#form *').filter(' input:not([type="submit"])').each(function(){
var current = this.value;
var original = this.getAttribute('value')
var id = $(this).attr('id');
if((id!=="prod")&&(id!=="prodamt")&&(id!=="subtotal")&&(id!=="matlamt")&&(id!=="tax")&&(id!=="total")&&(id!=="matl")&&(id!=="prod-detail-formula-price")&&(id!=="prod-detail-formula-taxable")) //this ones for you zoe
if(current !== original)
{
returnJSON += '"'+id+'" : { "original":"'+original+'", "modified":"'+current+'"},';
console.log(id+":"+typeof original+ original +" does not equal " +typeof current+current);
}
});
returnJSON = returnJSON.substr(0, returnJSON.length-1);
returnJSON += '}';
return returnJSON;
}
use $.isNumeric() and if both are numeric check are they equal as a numbers using parseFloat or parseInt to convert to numeric
I have a script I have been using in my test environment to programmically create a tracking number by parsing the year from timestamp and padding the response index.
function setTrackingNumber(ss, lastRowInx, createDateColumn) //This block generates and stores a tracking number in Column AU on the backend
{
var padTrackNo = "" + lastRowInx;
var trackSize = 4;
var trackingNumberColumn = createDateColumn-3; //trackingNumberColumn is currently in AU (Column 47) Calculating using it's relative position to createDateColumn Position
if (ss.getRange(lastRowInx, trackingNumberColumn).getValue() == "") // so that subsequent edits to Google Form don't overwrite original tracking number
{
if (padTrackNo > trackSize)
{
var padTrackNo = pad(padTrackNo, trackSize);
}
else {} //do nothing
var shortYear = setShortYear(ss, lastRowInx, createDateColumn);
var trackingNumber = shortYear + "-" + padTrackNo;
var createTrackingNumber = ss.getRange(lastRowInx, trackingNumberColumn);
createTrackingNumber.setValue(trackingNumber);
}
else {} //should do nothing
return;
}//This is the end of the setTrackingNumber function
function setShortYear(ss, lastRowInx, createDateColumn)
{
var newCreateDate = ss.getRange(lastRowInx,createDateColumn).getValue();
var strDate = "" + newCreateDate;
var splitDate = strDate.split(" ");
var trimYear = splitDate[3];
var shortYear = trimYear;
return shortYear;
}//This is the end of the shortYear function
function pad(padTrackNo, trackSize)
{
while (padTrackNo.length < trackSize)
{
padTrackNo = "0"+padTrackNo;
}
return padTrackNo;
}//This is the end of pad function
That gets me test result which is as expected ex. 2016-0005. However when we added it to another production sheet it seemed to work with test data and then production data showed up like a date 3/1/2016. production result - first cell.
I thought it must just be formatting the string as a date because of the numbers so I tried formatted the column as plain text but that just changed the date to a plain text version of the date.
I thought this might be similar to needing to specify the format like I did in this question Appending initial timestamp from Google Form to end of record in order to permanently store create date onFormSubmit at #SandyGood 's suggestion so I tried setting the number format as [0000-0000] by changing
createTrackingNumber.setValue(trackingNumber);
to
createTrackingNumber.setValue(trackingNumber).setNumberFormat("0000-0000");
which resulted in the [production result - second cell] which again doesn't match the expected result.
Oddly, some submissions seem to work just fine like [production result - third cell]. Over the past 3 days and approximately 10 records it has been fine, then hinky, then fine, they hinky, then fine again. I am not really sure what else to try to debug this odd behaviour.
Note: I had to parse the date as a string as I was having trouble getting it to parse the date correctly from the create date which is taken from initial timestamp.
To my understanding, "2016-0005" is not a number but a string, so the cell containing it should be formatted as plain text. With a script, this can be done by
range.setNumberFormat('#STRING#')
(source), and this must be done before you set the value to the cell. Like this:
createTrackingNumber.setNumberFormat('#STRING#').setValue(trackingNumber);
How would it be a nice way of handling this?
I already thought on removing the comma and then parsing to float.
Do you know a better/cleaner way?
Thanks
parseFloat( theString.replace(/,/g,'') );
I don't know why no one has suggested this expression-
parseFloat( theString.replace(/[^\d\.]/g,'') );
Removes any non-numeric characters except for periods. You don't need custom functions/loops for this either, that's just overkill.
Nope. Remove the comma.
You can use the string replace method, but not in a one liner as a regexp allows.
while(str.indexOf(',')!=-1)str= str.replace(',','');
parseFloat(str);
Or to make a single expression without a regexp=
return parseFloat(str.split(',').join(''));
I'd use the regexp.
I don't have enough reputation to add a comment, but for anyone wondering on the performance for regex vs split/join, here's a quick fiddle: https://jsfiddle.net/uh3mmgru/
var test = "1,123,214.19";
var t0 = performance.now();
for (var i = 0; i < 1000000; i++)
{
var a = parseFloat(test.replace(/,/g,''));
}
var t1 = performance.now();
document.write('Regex took: ' + (t1 - t0) + ' ms');
document.write('<br>')
var t0 = performance.now();
for (var i = 0; i < 1000000; i++)
{
var b = parseFloat(test.split(',').join(''));
}
var t1 = performance.now();
document.write('Split/join took: ' + (t1 - t0) + ' ms');
The results I get are (for 1 million loops each):
Regex: 263.335 ms
Split/join: 1035.875 ms
So I think its safe to say that regex is the way to go in this scenario
Building on the idea from #kennebec, if you want to make sure that the commas are correct, and you don't want to replace commas, you could try something like this:
function myParse(num) {
var n2 = num.split(",")
out = 0
for(var i = 0; i < n2.length; i++) {
out *= 1000;
out += parseFloat(n2[i])
}
return out
}
alert(myParse("1,432,85"));
// Returns 1432085, as the comma is misplaced.
It may not be as fast, but you wanted alternatives :)
What about a simple function to solve most of the common problems?
function getValue(obj) {
Value = parseFloat( $(obj).val().replace(/,/g,'') ).toFixed(2);
return +Value;
}
The above function gets values from fields (using jQuery) assuming the entered values are numeric (I rather validate fields while user is entering data, so I know for sure field content is numeric).
In case of floating point values, if well formatted in the field, the function will return a float point value correctly.
This function is far from complete, but it quickly fix the "," (comma) issue for values entered as 1,234.56 or 1,234,567. It will return valid number as far the content is numeric.
The + (plus) sign in front of the variable Value in the return command is a "dirty trick" used in JavaScript to assure the variable content returned will be numeric.
it is easy to modify the function to other purposes, such as (for instance), convert strings to numeric values taking care of the "," (comma) issue:
function parseValue(str) {
Value = parseFloat( str.replace(/,/g,'') ).toFixed(2);
return +Value;
}
Both operations can even be combined in one function. I.e.:
function parseNumber(item,isField=false) {
Value = (isField) ? parseFloat( $(item).val().replace(/,/g,'') ).toFixed(2) : parseFloat( item.replace(/,/g,'') ).toFixed(2)
return +Value;
}
In such case, if function is called result = parseNumber('12,092.98'); it will parse the value as it is a String. But if called as result = parseNumber('#MyField', true); it will try to obtain the value from '#MyField'.
As I said before, such functions are far from complete, and can be expanded in many ways. One idea is to check the first character of the given parameter (string) and decide based on the string format where to obtain the value to be parsed (if 1st character is = '#' then it is an ID from a DOM object, otherwise, if it begins with a number, it must be a string to be parsed).
Try it... Happy coding.