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

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)

Related

Send single email containing a table based on a condition to the recipients when the names are repetitive using google app script

This is the extended version of my previous question.
I want to send email once in a week to the recipients based on the status column.
Sheet Link: https://docs.google.com/spreadsheets/d/1GC59976VwB2qC-LEO2sH3o2xJaMeXfXLKdfOjRAQoiI/edit#gid=1546237372
The previous code is attached in the sheet.
From the sheet, When the Status column will be new and ongoing, a table will be generated with column Title, Link and due date and send a single email to the recipients even they are repeated.
In the sheet, For resource Anushka, Status New appeared twice and Ongoing once. The table will be like-
Anushka || New || 10/25/2022
Anushka || New || 10/25/2022
Anushka || Ongoing || 10/25/2022
And after creating it, it will send single email to each recipients though they have appeared several times.
I have done it for getting multiple emails whatever the status is with the help of another commenter from stackflow but I want to modify it and change it. The code for this one is a bit longer as I have two helper gs file, html table code and the main one. That's why I am not writing all the codes here. But in the sheet from the extension, one can see my code.
If anyone give me suggestions how to change or modify the logic, it will be appreciated.
Code
function macro(){
// get range of cell with data from A1 to any cell near having value (call data region)
var table = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet3").getRange("A1").getDataRegion();
var header=table.getValues()
var resource=header[0][1]
var status_r=header[0][3]
var due_date_r=header[0][5]
var link_r=header[0][10]
// create custom table filtered by the column having header "State" where the value match "New"
var filterTable = new TableWithHeaderHelper(table)
.getTableWhereColumn("Status").matchValueRegex(/(New)/);
// for each row matching the criteria
for(var i=0; i< filterTable.length() ; i++){
// Get cell range value at column Mail
var mail = filterTable.getWithinColumn("Email").cellAtRow(i).getValue();
// Any other value of column Target Value
var resource_col = filterTable.getWithinColumn("Resource").cellAtRow(i).getValue();
var status_col = filterTable.getWithinColumn("Status").cellAtRow(i).getValue();
var due_date_col = filterTable.getWithinColumn("Due Date").cellAtRow(i).getValue();
var link_col = filterTable.getWithinColumn("Link").cellAtRow(i).getValue();
var new_data=[[resource_col,status_col,due_date_col,link_col]]
var htmltemplate=HtmlService.createTemplateFromFile("email")
htmltemplate.resource=resource
htmltemplate.status_r=status_r
htmltemplate.due_date_r=due_date_r
htmltemplate.link_r=link_r
htmltemplate.new_data=new_data
var htmlformail=htmltemplate.evaluate().getContent()
var subjectMail = "Automation Support Services Actions Items";
var dt1 = new Date()
var dt2 = due_date_col
// get milliseconds
var t1 = dt1.getTime()
var t2 = dt2.getTime()
var diffInDays = Math.floor((t1-t2)/(24*3600*1000));
// 24*3600*1000 is milliseconds in a day
console.log(diffInDays);
// Send email
MailApp.sendEmail({
to:mail ,
subject: subjectMail,
htmlBody:htmlformail,
});
}
}```
2 loops can make the job.
// get range of cell with data from A1 to any cell near having value (call data region)
var table = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet3").getRange("A1").getDataRegion();
// init html header data
var header=table.getValues()
var resource=header[0][1]
var status_r=header[0][3]
var due_date_r=header[0][5]
var link_r=header[0][10]
var listOfEmails = [];
var tableWithHeader = new TableWithHeaderHelper(table)
// get all email
for(var i=0; i< tableWithHeader.length() ; i++){
var mail = tableWithHeader.getWithinColumn("Email").cellAtRow(i).getValue();
listOfEmails.push(mail)
}
// filter all email to get unique liste of email
var uniqueMailList = listOfEmails.filter((c, index) => {
return listOfEmails.indexOf(c) === index;
});
for(var i=0; i< uniqueMailList.length; i++){
// get mail of target i
var mail = uniqueMailList[i]
// filter table using mail of target i and status
var mailTable = new TableWithHeaderHelper(table)
.getTableWhereColumn("Status").matchValueRegex(/(New)/)
.getTableWhereColumn("Email").matchValue(mail);
// initialise html template
var htmltemplate=HtmlService.createTemplateFromFile("email")
htmltemplate.resource=resource
htmltemplate.status_r=status_r
htmltemplate.due_date_r=due_date_r
htmltemplate.link_r=link_r
var new_data = []
var htmlformail
// loop into the filtered table of target i only
for(var j=0; j< mailTable.length() ; j++){
// Any other value of column Target Value
var resource_col = mailTable.getWithinColumn("Resource").cellAtRow(j).getValue();
var status_col = mailTable.getWithinColumn("Status").cellAtRow(j).getValue();
var due_date_col = mailTable.getWithinColumn("Due Date").cellAtRow(j).getValue();
var link_col = mailTable.getWithinColumn("Link").cellAtRow(j).getValue();
new_data.push([resource_col,status_col,due_date_col,link_col])
}
htmltemplate.new_data=new_data
htmlformail=htmltemplate.evaluate().getContent()
var subjectMail = "Automation Support Services Actions Items";
var dt1 = new Date()
var dt2 = due_date_col
// get milliseconds
var t1 = dt1.getTime()
var t2 = dt2.getTime()
var diffInDays = Math.floor((t1-t2)/(24*3600*1000));
// 24*3600*1000 is milliseconds in a day
console.log(diffInDays);
// Send email
MailApp.sendEmail({
to:mail ,
subject: subjectMail,
htmlBody:htmlformail,
});
}
I'm not confident on the new_data.push([resource_col,status_col,due_date_col,link_col]), it's seems to be corect but I have no no way to verify that
Anyway thanks for using the utils script at https://github.com/SolannP/UtilsAppSsript, glad to see it help 👍

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.')
}

App Scripts stepping months and date formatting

I'm new to App Script, please be gentle!
I have a table with a start date and a number of months, I need to list each recurring month from the start date to the number of months.
I'm struggling with 2 issues:
The first date placed in the array seems to be updating as I cycle the dates
The formats are in UNIX and I can't get them to be useful despite an awful lot of reading!
Thank you so much for any help!
Input file (https://i.stack.imgur.com/2EJ65.png)
Output file(https://i.stack.imgur.com/0DH5R.png)
Console log (https://i.stack.imgur.com/NVqck.png)
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Get Data sheet
var rawData = ss.getSheetByName('Sheet3');
// Get a date and set it as a date format
var start = new Date(rawData.getRange("B2").getValues());
console.log(start);
// Get Months
var months = rawData.getRange("C2").getValues();
// Define dates array
var dates = [];
// Add first date
const startDate = start
dates.push([startDate]);
//Add rest of dates dates in a loop
for (var run = 1;run < months;run++) {
//get the last pasted month name
var lastMonth = start.getMonth();
//push next month
dates.push([start.setMonth(lastMonth+1)]);
}
// Get processedData sheet
var processedData = ss.getSheetByName('sheet2');
// Post the outputArray to the sheet in a single call
console.log(start);
console.log(startDate);
console.log(dates);
console.log(dates.length);
console.log(dates[0].length);
processedData.getRange(1,1,6,1).setValues(dates);
}
Incrementing Dates by month and setting format
function lfunko() {
const ss = SpreadsheetApp.getActive();
let dt = new Date();
let start = new Date(dt.getFullYear(),dt.getMonth(),dt.getDate());//need to change back to your value
const months = 10;//need to change back to your value
let dates = [];
dates.push([start]);
for (let run = 1; run < months; run++) {
dates.push([new Date(start.getFullYear(),start.getMonth() + run, start.getDate())])
}
ss.getSheetByName('Sheet0').getRange(1, 1, dates.length).setValues(dates).setNumberFormat("mm/dd/yyyy");//changed sheet name
Logger.log(dates);
}

Inputting Date without time

Using the code below, the google sheet enters the Date in column 6 when a cell in column 1 is edited. It also includes the timestamp.
How can this code be altered so that just the date (day-month-year) is entered? This would allow searches by date on the sheet.
function onEdit(e) {
var sheetName = 'Cases Allocation'; //name of the sheet the script should work on
var colToWatch = 1
var colToStamp = 6 //timestamp in col A
if (e.range.columnStart !== 1 || e.source.getActiveSheet()
.getName() !== sheetName || e.value == '') return;
var writeVal = e.value !== "" ? new Date(),: '';
e.source.getActiveSheet()
.getRange(e.range.rowStart, colToStamp)
.setValue(writeVal);
}
You can try using Utilities.formatDate. This method formats date according to specification described in Java SE SimpleDateFormat class. You can visit the date and time patterns here. An example of usage is:
// This formats the date as Greenwich Mean Time in the format
// year-month-dateThour-minute-second.
var formattedDate = Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'");
Logger.log(formattedDate);
Do take note that using Utilities.formatDate has the following effects as noted in this post:
Utilities.formatDate does not change the cell format; it converts
its content to text. If you are going to use the date value in
computations, you don't want it to be converted to text.
Utilities.formatDate requires one to specify a timezone. If you put
something like "GMT+1" and your country observes Daylight Saving
Time, you can expect hard-to-track bugs to come up a few months
later. With setNumberFormat, the date value remains consistent with
all other date values in the spreadsheet, governed by its timezone
setting.
To avoid these, you can also use setNumberFormat(numberFormat) to set the date or number format. The accepted format patterns are described in the Sheets API documentation. And an example usage is:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var cell = sheet.getRange("B2");
// Always show 3 decimal points
cell.setNumberFormat("0.000");
Thanks all - tried format etc, but then just decided to add a column and split the date and time, with just the date going into a new column.
function onEdit(e) {
var sheetName = 'Cases Allocation'; //name of the sheet the script should work on
var colToWatch = 1
var colToStamp = 6 //timestamp in col A
if (e.range.columnStart !== 1 || e.source.getActiveSheet()
.getName() !== sheetName || e.value == '') return;
//var writeVal = e.value !== "" ? new Date(),: '';
e.source.getActiveSheet()
.getRange(e.range.rowStart, colToStamp)
.setValue(new Date(new Date().setHours(0,0,0,0))).setNumberFormat('MM/dd/yyyy');
}
The below code will save the date without time. You can change the format as required. The field remains a date which can be used as required.
setValue(new Date(new Date().setHours(0,0,0,0))).setNumberFormat('MM/dd/yyyy');

How to test a Angular js date picker from Protractor

I'm new to Protractor and here I'm trying to test an angularjs date picker from Protractor.
I tried to find a way to do this and this article was the only thing I found and It is not very clear to use
If someone know how to test please help.
What I need is to select today's date.
Thanks in advance :)
edit -
alecxe, here is the screen shot of my date picker. Unfortunately cannot provide the link of the page. :(
<input
class="form-control ng-pristine ng-valid ng-not-empty ng-touched"
ng-model="invoice.fromdate"
data-date-format="yyyy-MM-dd"
data-date-type="string"
data-max-="" data-autoclose="1"
bs-datepicker=""
ng-change="dateRange()"
type="text">
I think you can avoid manipulating the datepicker manually and instead set the date either by just sending the keys with a today's date value:
var picker = element(by.model("invoice.fromdate"));
// get today's date
var today = new Date();
var dd = today.getDate();
var mm = today.getMonth()+1; //January is 0!
var yyyy = today.getFullYear();
if(dd<10) {
dd='0'+dd
}
if(mm<10) {
mm='0'+mm
}
today = mm+'/'+dd+'/'+yyyy;
picker.clear();
picker.sendKeys(today);
Or, by setting the associated model's value directly:
picker.evaluate("invoice.fromdate= '" + today + "'");
Two methods have been suggested so far: 1. sendKeys() 2. evaluate()
I'm a bit new to protractor but I think both of these have issues in the case of not having an input element that spawns the calendar, i.e.:
Sendkeys() works only if date is on an element and the uib-datepicker is a dropdown sort of deal. This didn't help me because my datepicker element is a standalone and isn't paired with an input element.
evaluate() doesn't update angular's actual model in the browser (which begs the question of how useful evaluate actually is...). According protractor docs, evaluate, "Evaluates the input as if it were on the scope of the current underlying elements." In my case I want to test whether the date generated by the datepicker gets to my enpdpoint via a post request and then back again (hence e2e) without getting effed (corrupted), therefore, I need my date to be on my angular model in the browser instance, not just in the browser-driver environment or whatever the runtime environment of the protractor test is. I could be wrong about this.
This expect() passes but the ng-form is invalid (i'm assuming b/c model in browser wan't actually updated to receive the date I'm trying to pass in.):
function convertToPickerDate(date) {
var date = new Date(date);
var dd = date.getDate();
var mm = date.getMonth() + 1; //January is 0!
var yyyy = date.getFullYear();
var yy = yyyy.toString().slice(2);
return mm + '/' + dd + '/' + yy;
}
// expect passes, but form is invalid - DON'T USE for standalone cal
it('should enter start date in date picker', function () {
offerStart = convertToPickerDate(myData.startDate);
var offerStartPicker = element(by.model('current.startDate'));
offerStartPicker.evaluate("current.startDate = '" + offerStart + "'");
offerStartPicker.evaluate("current.startDate").then(function (value) {
expect(value).toBe(offerStart);
});
})
but the ng-form that the element is on is invalid...
My solution uses css selection and arrow keys to select a date relative to today:
Shipment Start Date: <em id="offerStartPrint">{{current.startDate | date:'shortDate' }}</em>
<div id="offerStart"
name="offerStart"
uib-datepicker
ng-model="current.startDate"
class=""
ng-change="setStartDate()"
datepicker-options="startDateOptions"
required></div>
</div>
function convertToPickerDate(date) {
var date = new Date(date);
var dd = date.getDate();
var mm = date.getMonth() + 1; //January is 0!
var yyyy = date.getFullYear();
var yy = yyyy.toString().slice(2);
return mm + '/' + dd + '/' + yy;
}
it('should enter expiration date in date picker using tabs and arrows :)', function () {
// select today element on uib-datepicker calendar
// div#offerStart elem has date model
var calToday = element(by.css('div#offerStart table td button.active'));
calToday.sendKeys(protractor.Key.ARROW_DOWN); // one week away
calToday.sendKeys(protractor.Key.ARROW_DOWN); // two weeks away
calToday.click(); // if you remove this click no date is entered
var fortnightAway = new Date(Date.now() + 12096e5);
fortnightAwayString = convertToPickerDate(fortnightAway);
expect(element(by.id('offerStartPrint')).getText()).toBe(fortnightAwayString);
})
Left and right arrows can be used to increment/decrement date by one day at a time.
up/down arrows can be used to inc/dec one week at a time.
One could probably figure out how to arrow through months and years as well.
var data_picker = element(by.model("invoice.fromdate"));
// select current date with date function
var current_date = new Date();
var day = today.getDate();
var month = today.getMonth()+1; //By default January count as 0
var year = today.getFullYear();
if(day<10) {
day='0'+day
}
if(month<10) {
month='0'+month
}
current_date = month+'/'+day+'/'+year;
data_picker.clear(); // Note if you are facing error message related to clear. Comment this line
data_picker.sendKeys(today);
Hope this will work