I have a template sheet inside a workbook that I want to copy to a new workbook that is not yet created. I want this to run on the 29th of each month and do the following:
Take a Spreadsheet names "Template" and the sheet named “Template..2020.”
Create a new Spreadsheet called "December 2020" for example, and rename the first tab to 12.01.20, which I can then copy and rename for every day of the month.
I have tried a few copy functions with time triggers but they have required a blank workbook to be open already.
Example Script
This script
Function createSheetFromTemplate
Uses the date object to get information about today's date and extracts the year, month, day etc.
Gets the template from another sheet using SpreadsheetApp.openById
Creates a new Sheet based on information from today's date.
Copies the template sheet and renames it based on the date.
Function createTrigger
Using ClockTriggerBuilder, creates a trigger to run on the 29th of each month.
Disclaimer: At the moment this script just creates a new Spreadsheet with today's month and a new sheet named with today's date. I understand you may be looking to create a sheet for next month. This problem is probably best served by another question - perhaps look here.
function createSheetFromTemplate() {
// Getting details of today's date
let date = new Date()
let year = date.getFullYear()
let month = date.getMonth() + 1
let monthName = date.toLocaleString('default', { month: 'long' });
let day = date.getDate()
console.log(year, monthName, day)
// Get template based on year
let templateSpread = SpreadsheetApp.openById("1H7GBVxK4f0nmYR5xfZawy6nreDTCZaj76-dKE9eOtUE")
let templateSheet = templateSpread.getSheetByName("Template_" + year)
// Create new Spreadsheet with renamed template
let newSpread = SpreadsheetApp.create(monthName + " " + year)
let newSheet = templateSheet.copyTo(newSpread).setName(year.toString().substring(2,4) + "." + month + "." + day)
let sheetToDelete = newSpread.getSheetByName("Sheet1")
newSpread.deleteSheet(sheetToDelete)
}
// Create trigger to run on 29th of each month.
function createTrigger() {
ScriptApp.newTrigger('createSheetFromTemplate')
.timeBased()
.onMonthDay(29)
.create();
}
References
date object
SpreadsheetApp.openById
ClockTriggerBuilder
Related
As the Google Apps Script doesn't support yearly triggers, I am trying to run a monthly trigger that runs on a certain date of every month, and if the month is matching my criteria, run the actual function. Here is the code written:
function createYearlyTrigger() {
const sheet=SpreadsheetApp.getActive().getSheetByName("mobile") //<- insert Sheet name
const subjectLine = "Message sent to be Yearly | {{Property}}"; // <-- Insert email draft subject line
sheet.getRange("AH2:AH").setValue("")
ScriptApp.newTrigger("shouldTriggerRun")
.timeBased().onMonthDay(17).atHour(19).create();
}
function shouldTriggerRun() {
var date = new Date();
if (date.getMonth() === 3) {
incrementCell();
}
sendEmails(subjectLine, sheet);
}
Now my target is to run the trigger on 17th day of April. I am also using the following trigger:
But it is not working properly.
Please let me know where I am doing wrong.
The script places the dates correctly on my Google Calendar, then misplaces them all one day early, then places the last one correctly again.
input from google sheet, displayed in Google Calendar
1/4/23 -> 1/4/23 put in goog cal correctly
1/8/23 -> 1/7/23 - one day off
2/5/23 -> 2/4/23 - one day off
6/18/23 -> 6/17/23 - one day off
10/15/23 -> 10/15/23 - correct
function addEvents(){
var ss =
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lr = ss.getLastRow();
var cal =
CalendarApp.getCalendarById("#group.calendar.google.com");
var data = ss.getRange("A1:C" + lr).getValues();
for(var i = 0; i<data.length;i++){
cal.createAllDayEvent(data[i][0], new Date(data[i][1]),
{description:'PRO: ' + data[i][2]});
}
}
three columns in the google sheet
title, date, and description
Try GetDisplayValues()
var data = ss.getRange("A1:C" + lr).GetDisplayValues();
and potentially format the column as yyyy-MM-dd.
This will ensure the data the script is using is the same as you see on the sheet. Formatting the column will ensure new Date() converts the the data correctly.
If its still off add a couple hours to your Date object to ensure the time is firmly in the middle of the day.
let today = new Date();
today.setHours(today.getHours() + 4);
var date = Utilities.formatDate(new Date(), "GMT-8", "m/dd/yyyy")
if (formS.getRange("B7").getValue() != " " && formS.getRange("B7").getValue() != date)
{
SpreadsheetApp.getUi().alert("Please Enter A Valid Date");
return
}
Trying to make the condition above check if the cell is not empty and that it does not contain a date prior to Today's Date
function myfunk() {
const ss = SpreadsheetApp.getActive();
const formS = ss.getSheetByName('formS');
const dtv = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate()).valueOf();
if (!formS.getRange("B7").isBlank() && new Date(formS.getRange("B7").getValue()).valueOf() < dtv) {
SpreadsheetApp.getUi().alert("Please Enter A Valid Date");
return;
}
}
Checking Dates in Apps Script
In general you can use the Date object as you would in normal JavaScript code. There are just one main thing to bear in mind if your script needs to be sensitive to timezones.
The timezone is defined in the manifest:
This cannot be changed dynamically. So if you need to be sensitive to them, then you will need to manage the offsets in your code.
Your script
This line:
var date = Utilities.formatDate(new Date(), "GMT-8", "m/dd/yyyy")
Returns a string. Not a date object, so you can't compare it to another date object, such as what is returned from a sheet value if it is formatted as a date.
You could use Regex or split to get the year and month and compare it that way, but then you may run into issue when you use the script on the 1st of January. This is because by simply comparing the year, month and date of 31/12/2021 with 01/01/2022, then your conditional statements would be a bit tricky. Possible, but maybe a bit hard to read.
Initializing to midnight
What follows is one approach to take to carry out this comparison in a relatively simple way.
It seems convenient to get a date object initialized to 00:00:00 of today. Then you can quickly compare the date using Unix time.
var now = new Date()
now.setHours(0)
now.setMinutes(0)
now.setSeconds(0)
now.setMilliseconds(0)
You can also do this in a more concise way like this:
var now = new Date()
now.setHours(0,0,0,0);
Then you can use the getTime() method on the date objects to get Unic time in milliseconds and compare them.
var dateToCheck = formS.getRange("B7").getValue()
if (
!(dateToCheck instanceof Date) || // If value is not instance of a Date object
dateToCheck.getTime() <= now.getTime() // If date is before 00:00:00 today.
) {
SpreadsheetApp.getUi().alert("Please Enter A Valid Date");
return
}
}
Which seems like a concise way to do the comparison you are looking for.
References
Apps Script Dates
JS Date object
I am trying to automate certain parts of my workflow for scheduling clients with Google Calendar. I've successfully managed to capture new/edited/deleted events in Google Apps Script using a trigger which detects changes and Calendar.event.list to sync those changes with a spreadsheet.
I create a new row, or edit an existing one, in my spreadsheet of all the clients. What I desire to do is three days before the appointment with the client, automatically generate a custom email with all of their details, to send them as a reminder regarding the appointment.
My plan was every time a new row was created in the Spreadsheet (when a new Calendar event was created), was to make a new email trigger. That trigger would execute code to create an email, with all of the clients info.
function createEmailTrigger(event) {
var today = new Date();
today.setHours(0,0,0,0); // Don't care about time
// Create Email Trigger three days before
const sendDaysBefore = 3;
var daysBefore = new Date(event.start);
daysBefore.setDate(daysBefore.getDate() - sendDaysBefore);
var trigger = ScriptApp.newTrigger('sendEmail')
.timeBased()
.at(daysBefore)
.create();
associateEventWithTrigger(trigger.getUniqueId(), event);
return trigger.getUniqueId();
}
associateEventWithTrigger connects the trigger id with the Calendar event. sendEmail would then create a new email with all of the client's info, which came from the Calendar event. When the trigger is executed, it deletes the trigger since it won't be used again.
All of this was working fine, as I was testing one Calendar event at a time. However, once I decided to sync all of this year's Calendar events, the script very quickly threw this error:
Exception: This script has too many triggers. Triggers must be deleted from the script before more can be added.
Apparently you can only have 20 triggers per user/script. This is very inconvenient, as I was expecting to create hundreds of triggers.
Therefore, I need to rethink how to go about doing this. Any suggestions? I appreciate it.
Proposed workaround
This script is designed to be run on a time-driven trigger that runs daily.
function sendReminderEmails() {
let file = SpreadsheetApp.getActive();
let sheet = file.getSheetByName("Sheet1");
let range = sheet.getDataRange();
let values = range.getValues();
// removing headers
values.shift()
values.forEach(row => {
let name = row[0]
let email = row[1]
let date = row[2]
// Date object representing time now
let now = new Date();
// helper variables
let second = 1000;
let minute = second * 60;
let hour = minute * 60;
let day = hour * 24;
// gets time to appointment in milliseconds
let timeToApp = date.getTime() - now.getTime()
if (timeToApp > 2 * day && timeToApp < 3 * day) {
MailApp.sendEmail(
email,
"Remember Your Appointment",
"Hello " + name + ",\nYou have an appointment coming up soon."
)
}
})
}
This is based on a sample spreadsheet like this:
So you would need to adapt it to your particular format.
Script walkthrough
It is based on the date object.
If your dates are stored as formatted dates in your spreadsheet, when you getValues, Apps Script will automatically pass them as Date object. Alternatively you can cast them as Date objects in Apps Script if needed.
The script starts off by getting all the values in the target sheet.
It initialized a new Date object that represents the time now.
It then goes through each row of the target sheet and gets a value for how long until the appointment.
If the value is between 2 days and 3 days, then an email is sent.
I Think you could make an script to search events every day , events that are 3 days ahead , select then and send email. So it will be just one script that will be triggeres every day, using the date trigger mode.
I have a macro to run at the beginning of every day which inserts today's date, grabs the previous day's data inputs, pastes them below today's date, and then formats it all. The issue is that I need it to display yesterday's date, not today's, and I need it to remain that date over time.
I've searched all over for solutions to this issue, but I can't find anything that answers my issue. Maybe I'm just bad at searching. This is what the code in question currently is:
function MorningRoutine() {
var spreadsheet = SpreadsheetApp.getActive();
var date = new Date();
spreadsheet.getActiveRangeList().setValue(date);
I need the macro to input yesterday's date, but this currently displays today's.
This will give yesterday date:
function MorningRoutine() {
var spreadsheet = SpreadsheetApp.getActive();
var date = new Date();
date.setDate(d.getDate()-1);
spreadsheet.getActiveRangeList().setValue(date);
}