Add future date in Google script and Goggle Sheet - date

I use Google sheet and Cryptofinance.ai to retreive cryptocurrency prices. I have one script that works well : it appends data periodically from one row in tab A to save them in a tab B so I can make graphs and charts.
Now I'd like to add date + 1 year in the next row. Idealy, each time the script is triggered, it make two new rows : The one with the data as it is now and the one with the date + 1 year.
If you curious and want to know why I want to do that is is to make projection price using this
formula in another tab : =TREND(filter(B2:B,B2:B<>""),filter(A2:A,B2:B<>""),filter(A2:A,(N(B2:B)=0)*(A2:A>0)))
Here is my script now:
// [START modifiable parameters]
var rangeToLog = 'Portefeuille!A28:L28';
var sheetToLogTo = 'Archive BTC/USD KRAKEN';
// [END modifiable parameters]
////////////////////////////////
/**
* Appends a range of values to the end of an archive sheet.
* A timestamp is inserted in column A of each row on the archive sheet.
* All values in rangeToLog go to one row on the archive sheet.
*
* #OnlyCurrentDoc
*/
function appendValuesToArchiveBTCUSDKRAKENSheet() {
// version 1.4, written by --Hyde, 30 January 2020
// - use Array.prototype.some() to skip empty rows when concating
// - see https://support.google.com/docs/thread/27095918?msgid=27148911
// version 1.3, written by --Hyde, 26 January 2020
// - see https://support.google.com/docs/thread/26760916
var ss = SpreadsheetApp.getActive();
var valuesToLog = ss.getRange(rangeToLog).getValues();
var logSheet = ss.getSheetByName(sheetToLogTo);
if (!logSheet) {
logSheet = ss.insertSheet(sheetToLogTo);
logSheet.appendRow(['Date time', 'Data']);
}
var rowToAppend = [new Date()].concat(
valuesToLog.reduce(function concatArrays_(left, right) {
var arrayContainsData = right.some(function isNonBlanky_(element, index, array) {
return element !== null && element !== undefined && element !== '';
});
return arrayContainsData ? left.concat(right) : left;
})
);
logSheet.appendRow(rowToAppend);
}
NOW :
What I want to do:

The easy fix is to simply add another appendRow() call at the end of your function with the one year from now value.
function appendValuesToArchiveBTCUSDKRAKENSheet() {
// ...
logSheet.appendRow(rowToAppend);
logSheet.appendRow([new Date(new Date().setFullYear(new Date().getFullYear() + 1))]);
}
A more complex solution, but with better execution time, would have you print both rows in a single setValues() call. This follows the best practice of using batch operations, but I suspect that the easier solution above is adequate for your purpose. I do encourage you, however, to try implementing the batch operation if you want to improve your apps script skills.

Finally, after some research I came to this :
function expCalc(){
delLastNRows();
appendValuesToArchiveBTCUSDKRAKENSheet();
}
function delLastNRows(n){
var n=n || 1;//allows you to delete last three rows without passing function a parameter.
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Archive BTC/USD KRAKEN');
var lr=sh.getLastRow();
if(lr>=n){
for(var i=0;i<n;i++){
sh.deleteRow(sh.getLastRow());
}
}
}
// [START modifiable parameters]
var rangeToLog = 'Portefeuille!A28:L28';
var sheetToLogTo = 'Archive BTC/USD KRAKEN';
// [END modifiable parameters]
////////////////////////////////
/**
* Appends a range of values to the end of an archive sheet.
* A timestamp is inserted in column A of each row on the archive sheet.
* All values in rangeToLog go to one row on the archive sheet.
*
* #OnlyCurrentDoc
*/
function appendValuesToArchiveBTCUSDKRAKENSheet() {
// version 1.4, written by --Hyde, 30 January 2020
// - use Array.prototype.some() to skip empty rows when concating
// - see https://support.google.com/docs/thread/27095918?msgid=27148911
// version 1.3, written by --Hyde, 26 January 2020
// - see https://support.google.com/docs/thread/26760916
var ss = SpreadsheetApp.getActive();
var valuesToLog = ss.getRange(rangeToLog).getValues();
var logSheet = ss.getSheetByName(sheetToLogTo);
var sheet = ss.getSheets()[0]
if (!logSheet) {
logSheet = ss.insertSheet(sheetToLogTo);
logSheet.appendRow(['Date time', 'Data']);
}
var rowToAppend = [new Date()].concat(
valuesToLog.reduce(function concatArrays_(left, right) {
var arrayContainsData = right.some(function isNonBlanky_(element, index, array) {
return element !== null && element !== undefined && element !== '';
});
return arrayContainsData ? left.concat(right) : left;
})
);
logSheet.appendRow(rowToAppend);
logSheet.appendRow([new Date(new Date().setFullYear(new Date().getFullYear() + 1))]);
}
Works like a charm !

Related

Comparing Dates in Google Scripts with Sheets Input

I am trying to create a pop up to warn a user they must update a part in inventory if the part date is more than 90 days old. The date on the sheet (Cell Q5) is autofilled from another sheet, but that shouldn't matter. The value for the cell on the spreadsheet is 9/2/2021. I've tried many things, but currently I am getting the value for Q5 showing up as NaN .
function CheckInvDate() {
var ss = SpreadsheetApp.getActive().getId();
var partsrange = Sheets.Spreadsheets.Values.get(ss, "BOM!A5:Q5");
var currentDate = new Date();
var parthist = new Date();
parthist.setDate(currentDate.getDate() -90);
for (var i = 0; i < partsrange.values.length; i++){
var name = partsrange.values [i][1]
var partdate = partsrange.values [i][16]
var parthisttime = new Date(parthist).getTime();
var partdatetime = new Date(partdate).getTime();
Logger.log("History " + parthisttime)
Logger.log("Current " + partdatetime)
SpreadsheetApp.flush()
// if (parthist > partdate == "TRUE") {
// SpreadsheetApp.getUi().alert('The price on '+ name + ' is out of date. Please update price and try again.')
// }
}
}
My last log was
[22-07-06 11:50:55:851 EDT] History 1649346655850
[22-07-06 11:50:55:853 EDT] Current NaN
I've seen a number of responses on Stack Overflow, but I can't understand them. They seem to refer to variables that I don't see in code, or commands I haven't seen before and I'm not sure if they are in date.
Try this:
function CheckInvDate() {
const ss = SpreadsheetApp.getActive();
const vs = Sheets.Spreadsheets.Values.get(ss.getId(), "BOM!A5:Q5").values;
let d = new Date();
d.setDate(d.getDate() - 90)
const dv = d.valueOf();
const oldthan5 = vs.map(r => {
if (new Date(r[16]).valueOf() < dv) {
return r;
}
}).filter(e => e);
SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutput(`<textarea rows="12" cols="100">${JSON.stringify(oldthan5)}</textarea>`).setWidth(1000), "Older Than 90 Days");
}
This outputs a dialog with the rows older than 90 days
I went to try this on my script again after lunch, and for whatever reason I am no longer getting the NaN value. I made one change on the if statement to fix the logic, and now it is working correctly. Not sure what I did, but apparently the coding gods were unhappy with me before.
The only part I changed was
if (parthisttime > partdatetime) {
SpreadsheetApp.getUi().alert('The price on '+ name + ' is out of date. Please update price and try again.')
}

apply FilterCriteria "whenDateEqualToAny(dates)" - What is the correct form of the date array (dates) to parse?

I want to add some quick filters using the ui of google sheets. Currently I want to allow the user to click "show last month" to only see the data of the last month. The dates are written in the first column.
Now I prefer to use the filter of google sheets before just printing the values into the sheet, to allow the user to further modify that filter.
Thus I am trying to build filterCriteria using SpreadsheetApp.newFilterCriteria().whenDateEqualToAny(dates) and I am parsing an array of valid dates. In the documentation it says I have to put a "Date[]" - doesn't that mean an array of dates?
Below the error message and my code:
Error message (linked to the line "var filterCriteria..."):
"Exception: The boolean condition can not have multiple values for equality checks for non-data source objects"
My code:
function showLastMonth() {
var ss = SpreadsheetApp.getActive()
var sheet = ss.getSheetByName('evaluation')
var now = new Date()
var thisYear = now.getFullYear()
var thisMonth = now.getMonth()
if(thisMonth == 0){var startMonth = 11; var startYear = thisYear - 1}
else{var startMonth = thisMonth - 1; var startYear = thisYear}
var startDate = new Date(startYear, startMonth, 1)
var endDate = new Date(thisYear, thisMonth, 0)
var dates = getDateArray(startDate, endDate)
var filter = sheet.getFilter()
if(filter == null ){
var range = sheet.getDataRange()
var filter = range.createFilter()
}
var filterCriteria = SpreadsheetApp.newFilterCriteria().whenDateEqualToAny(dates)
filter.setColumnFilterCriteria(1, filterCriteria)
}
getDateArray = function(startDate, endDate){
var startYear = startDate.getFullYear()
var startMonth = startDate.getMonth()
var dateArray = []; dateArray.push(startDate)
var date = startDate; var day = date.getDay()-1
while(date<endDate){
day++
date = new Date(startYear, startMonth, day)
if(date<=endDate){dateArray.push(date)}
}
return dateArray;
}
I believe your goal as follows.
You want to hide the rows of the values except for dates using the basic filter.
You want to achieve this using Google Apps Script.
Issue and workaround:
In the current stage, it seems that array of whenDateEqualToAny(array) is required to be the length of 1. I think that this is the reason of your issue. So for example, when var filterCriteria = SpreadsheetApp.newFilterCriteria().whenDateEqualToAny([dates[0]]) is used, no error occurs. This situation is the same with the setBasicFilter request of Sheets API. Unfortunately, it seems that this is the current specification. But, the official document says The acceptable values. which uses the plural form. Ref So I also think that this is not correct for the actual situation as mentioned by TheMaster's comment.
In order to achieve your goal, in this case, I would like to propose the following 2 patterns.
Pattern 1:
In this pattern, using setHiddenValues(), the values except for the values of dates in your script are set as the hidden values.
Modified script:
When your script is modified, please modify as follows.
From:
var filterCriteria = SpreadsheetApp.newFilterCriteria().whenDateEqualToAny(dates)
To:
var obj = dates.reduce((o, e) => Object.assign(o, {[`${e.getFullYear()}\/${e.getMonth() + 1}\/${e.getDate()}`]: true}), {});
var range = sheet.getRange("A1:A");
var dispValues = range.getDisplayValues();
var hiddenValues = range.getValues().reduce((ar, [a], i) => {
if (a instanceof Date && !obj[`${a.getFullYear()}\/${a.getMonth() + 1}\/${a.getDate()}`]) {
ar.push(dispValues[i][0]);
}
return ar;
}, []);
var filterCriteria = SpreadsheetApp.newFilterCriteria().setHiddenValues(hiddenValues).build();
Pattern 2:
In this pattern, using whenNumberBetween(), the values of dates in your script are shown. In this case, it is required to convert the date object to the serial number.
Modified script:
When your script is modified, please modify as follows.
From:
var filterCriteria = SpreadsheetApp.newFilterCriteria().whenDateEqualToAny(dates)
To:
var filterCriteria = SpreadsheetApp.newFilterCriteria().whenNumberBetween(
(dates[0].getTime() / 1000 / 86400) + 25569,
(dates.pop().getTime() / 1000 / 86400) + 25569
).build();
The conversion from the date object to the serial number was referred from this thread.
References:
setHiddenValues(values)
whenNumberBetween(start, end)

Google Apps Script using wrong date format in excel

So I am working on a Google Apps script that pulls an email address, subject, and body from a Google sheet file. This info is used to send an email out. Right now in my Subject column for the Google Sheets file I have =TODAY() so that the date is pulled. My script updates this column everyday so the date is always current.
The issue is that when the email comes the subject line shows
"Sat Aug 11 2018 00:00:00 GMT-0700 (PDT) "
Instead of...
08/11/18 like its setup for in Google Sheets
Not sure why this could be, My code is below.
/**
* Creates a two time-driven triggers.
*/
function createTimeDrivenTriggers() {
// Trigger every 6 hours.
ScriptApp.newTrigger('adddate')
.timeBased()
.atHour(21)
.everyDays(1)
.inTimezone("America/Los_Angeles")
.create()
ScriptApp.newTrigger('sendEmails2')
.timeBased()
.atHour(22)
.everyDays(1)
.inTimezone("America/Los_Angeles")
.create()
}
/**
* This is my hacky way to make sure sheets has today's date.
*/
function adddate() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var cell = sheet.getRange(2,2);
cell.setValue('=TODAY()');
}
// This constant is written in column C for rows for which an email
// has been sent successfully.
var EMAIL_SENT = 'SUCCESSFULLY SENT';
/**
* Sends non-duplicate emails with data from the current spreadsheet.
*/
function sendEmails2() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // First row of data to process
var numRows = 2; // Number of rows to process
// Fetch the range of cells A2:B3
var dataRange = sheet.getRange(startRow, 1, numRows, 3);
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var emailAddress = row[0]; // First column
var subject = row[1]; // Second column
var message = row[2]; // Third column
var emailSent = row[3]; // Fourth column
if (emailSent != EMAIL_SENT) { // Prevents sending duplicates
MailApp.sendEmail(emailAddress, subject, message);
sheet.getRange(startRow + i, 3).setValue(EMAIL_SENT);
// Make sure the cell is updated right away in case the script is interrupted
SpreadsheetApp.flush();
}
}
}
The problem you are encountering is that your cell is formatted as a date and you are passing it as a parameter that expects a string. At that point you are no longer controlling the conversion, and you are getting way more than you wanted. Two ways to address it.
1) Make it a string in the first place with
cell.setValue('=text(TODAY(),"mm/dd/yy"');
which does run the risk of ruining any date processing you do on it at first (though I see none, so probably fine).
So probably better is (not 1, just this)
2) is to get the displayed string while leaving the underlying date like you had it with var subject = row[1].getDisplayValue();
2) has the added benefit of going with the date format for which sheets is set up.

Copied value disappears when row that contained source value is deleted in Google spreadsheets

I wrote this script that is used as a trigger onEdit in a sheet. The idea is to pick a value from a worksheet, copy it into another worksheet based on some logic, and then delete the source row that contained the original value.
When run, often times, the copy will take place, but on delete, the copied value will disappear. One way I noticed fixes the problem is if I delete the trigger, save, and create it again...
How can I avoid this behavior?
function onEdit(e) {
var range = e.range;
var entry = range.getSheet();
var sss = entry.getParent();
if (sss.getName() != "Weight Tracker")
return;
if (entry.getName() != "Entry")
return;
Logger.log("CopyData is running...."+range.getCell(1,2).getValue());
var weight = range.getCell(1,2).getValue();
Logger.log("weight = "+weight);
var details = sss.getSheetByName('Details');
var trange = details.getRange(3, 1, 200);
var data = trange.getValues();
var today = new Date().setHours(0,0,0,0);
for(var n=0;n<data.length;n++) {
var date = new Date(data[n]).setHours(0,0,0,0);
Logger.log("date = "+date+" =? "+today);
if(date == today) {
break
};
}
Logger.log("n = "+n+" today: "+today);
// n is 0 based, sheet is 1 based + 2 headers = 3, 5 is Jim's weight
details.getRange(n+3,5).setValue(weight);
// get rid of the row so next addition arrives to the top row
Logger.log("deleting row...");
// for some reason deleting the road removes the value entered...
range.getSheet().deleteRow(1);
}

Set Time Range of a Zend Dojo TextTimeBox

Hi is it possible to set the time range of a Dojo textTimeBox to 09:00 - 18:30.
I can't find anything in either the Zend or Dojo documentation that show how this can be done or if it can be done.
Many thanks in advance.
You can set max and min constraints for widget:
new dijit.form.TimeTextBox({
name: "prog_val",
value: new Date(),
constraints: {
timePattern: 'HH:mm:ss',
clickableIncrement: 'T00:15:00',
visibleIncrement: 'T00:15:00',
visibleRange: 'T01:00:00',
min:'T09:00:00',
max:'T18:30:00'
}
},
"prog_val");
It does not allow the user to enter data beyond the allowed values.
However this still allows user to scroll to the disabled times, user just cannot select them.
For hiding disabled times you should do some hack :)
You should override _getFilteredNodes method of dijit._TimePicker. For example :
dojo.declare("my._TimePicker", dijit._TimePicker, {
// extend the default show() method
_getFilteredNodes: function (/*number*/start, /*number*/maxNum, /*Boolean*/before) {
// summary:
// Returns an array of nodes with the filter applied. At most maxNum nodes
// will be returned - but fewer may be returned as well. If the
// before parameter is set to true, then it will return the elements
// before the given index
// tags:
// private
var nodes = [], n, i = start, max = this._maxIncrement + Math.abs(i),
chk = before ? -1 : 1, dec = before ? 1 : 0, inc = before ? 0 : 1;
do {
i = i - dec;
var date = new Date(this._refDate);
var incrementDate = this._clickableIncrementDate;
date.setHours(date.getHours() + incrementDate.getHours() * i,
date.getMinutes() + incrementDate.getMinutes() * i,
date.getSeconds() + incrementDate.getSeconds() * i);
if (!this.isDisabledDate(date)) {
n = this._createOption(i);
if (n) { nodes.push(n); }
}
i = i + inc;
} while (nodes.length < maxNum && (i * chk) < max);
if (before) { nodes.reverse(); }
return nodes;
}
});
And you need to set this new class ('my._TimePicker') as a popupClass property of your text time box:
dojo.addOnLoad(function () {
dijit.byId("prog_val").popupClass = "my._TimePicker";
});
And you can see : it works!