I've the following columns in a spreadsheet
email
name
link
date
time
file
subject
title
a.d#abc.com
Sam Z
abc
18.01.23
14:41:00
def
abc
Hello World
With following code deployed as add-on in google apps script console I want to autamtically send emails to certain adresses at certain date and time, I#ve activeted a trigger to run the app every minute, but somehow it does not work...any hint what could be wrong? I'm 100% sure it is connected with the right spreadsheet
function sendReminderEmails() {
var spreadsheet = SpreadsheetApp.openById("1h570Eb4IqAaAHciQzz0U9oY_uqGo9UDzpWtth78NkCY").getSheetByName("test");
var data = spreadsheet.getDataRange().getValues();
// console.log(data);
for (var i = 1; i < data.length; i++) {
var email = data[i][0];
var name = data[i][1];
var link = data[i][2];
var date = data[i][3];
var time = data[i][4];
var file = data[i][5];
var now = new Date();
if (now.toLocaleDateString() == date && now.toLocaleTimeString() == time) {
MailApp.sendEmail(email, "Reminder: " + file, "Hello " + name + ",\n\nThis is a reminder about the " + file + " at " + link + " on " + date + " at " + time + ".");
}
}
}
There are three problems with your code and the way you're approaching this problem:
The variables date and time that you got from the Sheet are Date objects, so you also have to call toLocaleDateString() and toLocaleTimeString() on them if you want to compare them with the now values.
Even if you correct the above, the value returned by toLocaleTimeString() is a time in the format HH:MM:SS XM so the trigger would have to run at the exact same second as the time in your Sheet, which is very unlikely. You would need to cut out the seconds to compare the exact minute.
Even if you correct the above and compare the exact minute, Apps Script's time-driven triggers are slightly randomized, so there's no guarantee either that the trigger will run every single minute, and there may be a gap of a few minutes between executions.
My suggestion is to instead try something like rounding down the times to the nearest 10-minute mark and set the trigger to run every minute to compare the times. You would also need an extra checkbox column to mark them as done and avoid sending multiple emails. It may look something like this example:
function sendReminderEmails() {
var spreadsheet = SpreadsheetApp.openById("1h570Eb4IqAaAHciQzz0U9oY_uqGo9UDzpWtth78NkCY").getSheetByName("test");
var data = spreadsheet.getDataRange().getValues();
for (var i = 1; i < data.length; i++) {
var email = data[i][0];
var name = data[i][1];
var link = data[i][2];
var date = data[i][3];
var time = data[i][4];
var file = data[i][5];
var checked = data[i][7];
var now = new Date();
var roundedtime = time.getHours() + ":" + Math.floor(time.getMinutes() / 10) * 10
var roundednow = now.getHours() + ":" + Math.floor(now.getMinutes() / 10) * 10
if (now.toLocaleDateString() == date.toLocaleDateString() && roundednow == roundedtime && !checked) {
MailApp.sendEmail(email, "Reminder: " + file, "Hello " + name + ",\n\nThis is a reminder about the " + file + " at " + link + " on " + date + " at " + time + ".");
spreadsheet.getRange(i + 1, 8).setValue(true)
}
}
}
The sheet would look like this:
In this case roundedtime and roundednow would cut off the seconds and round down the minutes so, something like 14:41:00 would become 14:40, so the email would be sent when the trigger fires at any time between 14:40 to 14:49, then the checkbox is selected with spreadsheet.getRange(i + 1, 8).setValue(true) so it won't send another email within the same timeframe. Most often the emails will be sent closer to the lower bound.
Another possibility could be to instead create the triggers programmatically by using the time in the Sheet. If you use a trigger to fire at a specific time it will run once and then expire, but there's a limit of 20 triggers / user / script, so you wouldn't be able to use this method with a lot of data. Either way, keep in mind that you'll need to sacrifice some accuracy if you want your current approach to work.
Related
So I am currently making an expense tracker, and there is a feature that I'd like to implement.
So basically:
In this spreadsheet, the first two dates are automatically generated by the JavaScript Date object, while the rest aren't because I wanted to give an example of what my feature would do.
So currently, I have a custom function in Apps Script, one that returns how much a person has spent on a particular brand, like this (this is an HTML sidebar):
And returns this (although the numbers might be a bit off since I changed some of the data):
function perCentBrand(categ){
var sh = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var values = sh.getRange(2,1,sh.getLastRow()-1,sh.getLastColumn()).getValues();
var total = 0;
var sum = 0;
values.forEach(function(row){
total+=row[1];
if (row[6]==categ){sum+=row[1]}
})
var val = "You spent a total of " + sum + " on " + categ + " out of " + total + " ." + " Additionally, " + (sum/total)*100 + "%" + " of your income has been spent on " + categ;
var ui = SpreadsheetApp.getUi();
ui.alert(val)
}
The custom function is run in the HTML file like this:
<script>
function runFunc(){
var brand = document.getElementById("brand").value;
google.script.run.perCentBrand(brand);
}
</script>
and:
<form onsubmit="runFunc()">
Is there any way I can implement a similar feature, where for example, if I input the number 1 (corresponding to January), in an HTML input, it would return to me in UI alert the date, Amount Spent, and the Clothing brand?
I am new to JS and Apps Script but here is what I've tried:
function dateChecker(monthVal){
var sh = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var values = sh.getRange(2,1,sh.getLastRow()-1,sh.getLastColumn()).getValues();
let date = new Date();
let monthVal = date.getMonth();
values.forEach(function(row){
total+=row[1];
if (row[6] && row[1] && row[5] == monthVal){sum+=row[1]}
})
var val = "In the month of" + monthVal + "here is what you have spent" + row[6] + row[1] + row[5];
var ui = SpreadsheetApp.getUi();
ui.alert(val)
}
Probably, basically you can just add one more argument month in your function:
function perCentBrand(categ, month) {
...
And one more condition row[0].getMonth() + 1 == month:
if (row[6] == categ && row[0].getMonth() + 1 == month) { sum += row[1] }
It will give you the sum for a given month and brand. It supposes that year is a current year always.
And of course it's need to add the input area with ID="month" in your form as well and use it in the script about this way:
<script>
function runFunc(){
var brand = document.getElementById("brand").value;
var month = document.getElementById("month").value;
google.script.run.perCentBrand(brand, month);
}
</script>
I don't know how your HTLM-code looks like so it's up to you to add the input area to somewhere.
Date.getMonth() returns you a number of the month from a date object, where 0 is January, 1 is February, etc.
We are accessing an API that allows us to schedule date/time. We need to check scheduled things so we do NOT double book on the same hour. The API is returning a time as just HH in military. However, it is using UTC HH. So if we schedule something at 1PM it is coming back 18.
I am trying to use Moment.js to change the UTC 18 back to CST (local time). I am failing horribly.
In the example below I am getting 18 from the API and my function below tries to turn to local time (happens to be CST). But the function is just converting or leaving it to 18. Help?
function changeTakenHoursFromUTC (taken) {
if(taken) {
for (var i = 0, len = taken.length; i < len; i++) {
// construct a moment object with UTC-based input
var utchour = taken[i].send_hour + ":00:00";
console.log( "Hour before change " + utchour);
var h = moment.utc(utchour);
// convert using the TZDB identifier for US Central time
h.tz('America/Chicago');
h.local();
console.log( "Hour before change " + h._i );
var s = h.format("HH");
taken[i].send_hour = s._i;
console.log( "Taken hour back in taken is " + taken[i].send_hour );
count += 1;
if(i == len-1 && count > 0) {
//
return;
}
} // end for
} // end IF
} // end changeTakenHoursFromUTC
you are just missing a format string in your moment.utc(utchour, "HH")
I created this function to kind of help as an example:
function changeTakenHoursFromUTC (taken) {
// construct a moment object with UTC-based input
console.log( "Hour before change " + taken);
var h = moment.utc(taken, "HH");
console.log(h.format("HH"));
h.local();
console.log( "Hour after change " + h.format("HH"));
}
changeTakenHoursFromUTC(18);
I have two string for time, one is current time and the other one is twenty minutes later. Here is the output:
10/1/2017 1:20:58 PM
10/1/2017 1:40:58 PM
Here is the code:
var now = new Date();
console.log(now.toLocaleDateString() + " " + now.toLocaleTimeString());
var in20 = new Date(now.getTime() + (1000*60*20));
console.log(in20.toLocaleDateString() + " " + in20.toLocaleTimeString());
Is there any way to check if the variable for twenty minutes later is before or after the current time variable as I not sure how to make time comparison based on two time strings. If it is after current time variable, return a true, otherwise return a false.
Any ideas? Thanks!
Best way to compare the 2 time string is convert them to milliseconds and then compare. For Eg.
var now = new Date();
console.log(now.toLocaleDateString() + " " + now.toLocaleTimeString());
var in20 = new Date(now.getTime() + (1000*60*20));
console.log(in20.toLocaleDateString() + " " + in20.toLocaleTimeString());
// at any instant suppose now is cuurent time then you can compare like
if(now.getTime() > in20.getTime()) {
console.log('current is greater')
} else {
console.log('in20 is greater')
}
I have installed gem 'auto-session-timeout' to automatically log users off of inactive sessions. I want to give the user a 2 minute warning before the session times out
I added the following to my application.html.erb
<%= javascript_tag do %>
var d = new Date('<%= session[:auto_session_expires_at] %>');
setInterval(function(){
var d = new Date('<%= session[:auto_session_expires_at] %>');
var e = new Date();
var f = e.getTime();
var diff = d - f ;
alert(d + " | " + diff);
if (diff < 120000) {
// alert("Your session is about to timeout in less than 2 minutes " + diff);
}
}, 10000);
<% end %>
My goal is to use the if (diff < 120000) statement to show an alert 2 minutes from logout.
The code above gets the session variable set by auto-session-timeout and then subtracts it from the current date (from js's getTime()) and comes up with a diff.
It is ALMOST working properly. If interact with the app, the counter gets reset. However, it seems like the old counter keeps running.
In other words if I look at the results of the alert statement that is not remarked out, I can see the datetime that is in the session variable and the calculated diff. Below I am only showing the minutes and sessions that are shown for simplicity.
25:22 168134 -- first alert box - shows the contents of the session var and also shows the calculated diff or milliseconds remaining.
25:22 143249 -- second alert box -- everything is as expected.
25:22 120816 -- So, it is counting down correctly with the correct info from the session variable
Now, if I interact with the app, auto session timeout resets the session variable auto_session_expires_at.
26:13 158345 -- OK - exactly what I would expect . The Javascript is picking up the new session variable and it recalcualtes the diff.
25:22 86327 -- BUT now I'm seeing the previous session variable and the diff calculator is still running
The cycle above will then keep repeating between the 'good' and 'old' session variable and diff. I think there is some kind of caching going on but I can't pinpoint it.
this behavior is repeatable for me
=======================================================
HTML from when I first open the app
/<![CDATA[
var d = new Date('2016-05-06 15:58:19 -0400');
setInterval(function(){
var d = new Date('2016-05-06 15:58:19 -0400');
var e = new Date();
var f = e.getTime();
var diff = d - f ;
alert(d + " | " + diff);
if (diff < 120000) {
// alert("Your session is about to timeout in less than 2 minutes " + diff);
}
}, 10000);
//]]>
HTML from after I click on a nav item. You can see that the date grabbed from the session variable changes
/<![CDATA[
var d = new Date('2016-05-06 15:58:48 -0400');
setInterval(function(){
var d = new Date('2016-05-06 15:58:48 -0400');
var e = new Date();
var f = e.getTime();
var diff = d - f ;
alert(d + " | " + diff);
if (diff < 120000) {
// alert("Your session is about to timeout in less than 2 minutes " + diff);
}
}, 10000);
//]]>
</script>
#SanF was right about the clearInterval. Here is the revised code. It's a lot different from what I started out with.
<% if current_user %>
<%= javascript_tag do %>
// --------------------------------------------------------------------------------------------------------
// This script enhances the functionality of the auto_session_timeout gem
//
// this script will give the user a warning at a specified time before they will be automatically logged out
// from their session.
//
// The polling interval is set in var myVar = setInterval(myTimer, 10000); where 10000 = 10 seconds
// That should be set to about 1000 so that the login screen appears as soon as possible when
// the user comes to a screen from a timed out session.
//
// The time before logout for the warning is set in if (diff < 120000) 120000 = 120 seconds
//
// The timeout itself is contrlled by the auto_session_timeout gem and the setting in the application
// controller ' auto_session_timeout 4.minutes' . Make sure that the time set there is LONGER than the
// time set below for the warning
// ---------------------------------------------------------------------------------------------------------
clearTimer(); // clear the warning timer if it has been set previously
var diff = 0; // Initialize the difference calculation
var b = new Date();
var f = new Date();
var e = new Date();
var current_time = e.getTime(); // get the current time from the system
expires = new Date('<%= session[:auto_session_expires_at] %>'); // get the expiration time set by auto session expires.
var myVar = setInterval(myTimer, 5000); // this calls myTimer and runs it at intervals specified. 1000 = 1 second
// ------------------------------------ myTimer --------------------------------------------------
// This function is called with a setInterval so that it runs at a set interval depending on the number
// passed to var myVar = setInterval(myTimer, 1000); above. It gets the session variable
// containing the expires at date/time and also the current time. If the difference is less than the value set
// a warning is shown via an alert box and then the function is cleared.
// ------------------------------------------------------------------------------------------------
function myTimer() {
var diff = 0; // Initialize the difference between expires date/time and current date/time
var expires_raw = '<%= session[:auto_session_expires_at] %>' // Get the raw session variable from rails
// we need to parse the date above to be like var d = new Date('2016-05-10T13:50:12-04:00');
var exp_date = expires_raw.slice(0,10); // grab the date
var exp_time = expires_raw.slice(11,19); // grab the time H M S
var exp_tzh = expires_raw.slice(20,23); // grab the hour part of the timezone with the -
var exp_tzm = expires_raw.slice(23,25); // grab the minute part of the timezone which should be 00
var expires_parsed = exp_date + "T" + exp_time + exp_tzh + ":" + exp_tzm; // Parse it all together. Date plus a constant 'T' , time, time zone hour, colon, timezone min
var expires_parsed_time = new Date(expires_parsed); // take expires_parsed and create a date/time from it
var expires_parsed_time_iso = expires_parsed_time.toISOString(); // Convert expires_parsed_time to an ISO string - This step is KEY to making this
// work with IE 11 and firefox. They need the date from a strict ISO format
var expires_final = new Date(expires_parsed_time) // create a date from the expires_parsed_time
var expires_parsed_final = new Date(expires_parsed_time) // This is the final date that is fed to the diff variable
var e = new Date(); // set up a new date for the current time
current_time = e.getTime(); // get the current time from the system
diff = expires_parsed_final - current_time ; // calculate the difference between the expires and current times.
// The following commented alert can be uncommented as a debugging tool
// alert("\n raw date " + '<%= session[:auto_session_expires_at] %>' +
// "\n exp parsed " + expires_parsed +
// "\n diff " + diff +
// "\n current_time " + current_time +
// "\n exp raw " + expires_raw +
// "\n exp date " + exp_date +
// "\n exp time " + exp_time +
// "\n exp tzh " + exp_tzh +
// "\n exp tzm " + exp_tzm +
// "\n exp par time " + expires_parsed_time +
// "\n exp par tm iso " + expires_parsed_time_iso +
// "\n exp par final " + expires_parsed_final);
if (diff < 180000) {
alert("\n Your Internal complaints session is about to timeout \n in less than 3 minutes \n \n " +
"Please save your work \n" +
"Navigating to any page will reset the timeout timer");
clearInterval(myVar); // this clears the current warning timer since the message has already been shown to the user.
}
}
// ------------------------------------ clearTimer ------------------------------------------
// When called, this function will stop the setInterval for myTimer so that we don't have
// multiple timers running
// ------------------------------------------------------------------------------------------
function clearTimer() {
clearInterval(myVar);
}
function myTest() {
alert("test inside the test");
}
<% end %>
One other problem I ran into was that Firefox and IE use a strict interpretation of the ISO standards for dates. That required a few extra steps to use the ISO 8601 format for the date before trying to date arithmetic in IE/FF.
This still needs a little work but perhaps it could help someone trying to do a similar 'prior to timeout' warning.
So I am storing times as '01:30:00'. I have a start time and a date time dropdown. I want the dropdown to be set to the start time + 1hr. Is there a way to add the time via javascript or jquery?
Here's my current code:
$(".start_time").change(function(){
$(".end_time").val($(this).val());
});
Try this:
find the selected index of the start time
bump it up by 2 to find your end time index (given that you've got half hour increments)
use the mod operator % to wrap back to index 0 or 1 (for 00:00 and 00:30 respectively)
$(".start_time").change(function(){
var sel =$(this).attr('selectedIndex');
var endIdx = (sel + 2) % 48; // 47 is 23:30, so 48 should go back to index 0
$(".end_time").attr('selectedIndex', endIdx);
});
Try it out on JSBin.
There are two separate problems here: the first is parsing out the time from your .start_time input, and the second is incrementing it to be an hour later.
The first is really a string-manipulation exercise. Once you have parsed out the pieces of the string, e.g. via a regex, you could either turn them into a Date instance and use setHours, or you could just manipulate the components as numbers and them reassemble them into a string in the format you desire.
An example of this might be as follows:
var TIME_PARSING_REGEX = /([0-9]{2}):([0-9]{2}):([0-9]{2})/;
function padToTwoDigits(number) {
return (number < 10 ? "0" : "") + number;
}
$(".start_time").change(function () {
var stringTime = $(this).val();
var regexResults = TIME_PARSING_REGEX.exec(stringTime);
var hours = parseInt(regexResults[1], 10);
var newHours = (hours + 1) % 24;
var newHoursString = padToTwoDigits(newHours);
var minutesString = regexResults[2];
var secondsString = regexResults[3];
var newTimeString = newHoursString + ":" + minutesString + ":" + secondsString;
$(".end_time").val(newTimeString);
});
Basic example...
var date = new Date();
var h = date.getHours() + 1;
var m = date.getMinutes();
var s = date.getSeconds();
alert('One hour from now: ' + h + ':' + m + ':' + s);
Demo: http://jsfiddle.net/fBaDM/2/
After you parse you date/time string, you can use methods such as .setHours in your date object (more info at Mozilla Developer Center).
I highly recommend the DateJS library for working with date and time. I'm sure it'll be very handy for you.
protip: try to avoid replacing JavaScript with "jQuery markup"; it's all JS, after all. :)