I'm trying to create a schedule heat map so that we can adjust staffing times and days. In order to make this as easy as possible, I've come up with a tabular structure that allows the user to input the employee's name, then select their shift start time and their shift end time from a drop-down, and then use checkboxes to indicate which days they will work, as shown:
The end result would be a heat map that counts the number of instances that a value exists in the range between the start time and end time, broken down by hour and by day. My original thought was to use COUNTIFS thusly: =COUNTIFS(Calculations!D:D, ">=9:00:00", Sheet9!D:D, "=TRUE") Where Calculations!D:D is the column of the selected Start Time, where ">=9:00:00" checks to see if the start time is greater than or equal to 9AM, and where Sheet9!D:D, "=TRUE" checks to see if the checkbox for that day is checked. So this example would check to see if someone is working at 9AM on Monday.
However, this didn't pan out since we're checking for any value greater than 9AM, and most employees won't be working more than 10 hours, so I'm getting false positives.
My next thought was to use a named range that would start at the Start Time value and then, if necessary, loop back through to the End Time (for example, if an employee started at 10PM and their shift ended at 7AM). Since this range would be dynamic (not all employees will work strictly 8 hours per day), I would need to check to see if a value exists within the range, however, I'm not sure how to A: Loop through or B: check to see if a value is in the dynamic range. I assume this will require Google Apps Script to pull off, but I'm not well-versed in it, and I've been beating my head against a wall trying to figure this out. Any help would be appreciated!
Oh, and here's a screenshot of the desired output, with a couple of values filled in:
I don't know if this is exactly what you wanted but I wanted to practice using times is Google Scripts so I had a crack at solving your problem. However, there are a couple of little bits I didn't have time to write that I've commented in the script.
If I've understood your problem correctly, you want to take a series of shifts life this:
And convert it into a heatmap like this:
(I've not done all your formatting, but I'm guessing you want to see how many people are available for every hour of the day)
In my code, I called the first sheet above "Roster" and the second sheet "RosterOverview" - note the capitalisation and lack of spaces.
Approach
I wrote two scripts. One (rostering) to check which days a staff member worked and a second to see the hours they worked and update the values in RosterOverview (updateCalendar).
For each member of staff, I took their working hours and working days. I checked to see if they worked on a certain day and, if they did, sent their working hours to a script called updateCalendar. This second script then looked to see if they worked a night shift i.e. over midnight or not. The script then adds a 1 to each hour window a staff member is rostered for.
The scripts
function rostering() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var rosterSheet = ss.getSheetByName("Roster");
var overviewSheet = ss.getSheetByName("RosterOverview");
var range = rosterSheet.getRange(2,2,4,9); //You will need to work out how to accurately get this range for your data - 2 and 2 are the starting row and column respectively i.e. B2; 4 indicates the number of rows; 9 indicates the number of columns
var times = range.getValues();
var calendarRange = overviewSheet.getRange("B2:I25"); //On RosterOverview, this is the range of cells for Monday-Monday, midnight-2300. You need 8 columns of data because the extra one is for Sunday night overflowing into Monday morning.
//You will need to write a bit of code yourself to make sure all the values in calendarRange are set to 0. I have set this manually and pull them below when I was testing.
var calendarValues = calendarRange.getValues();
//This section loops through each staff member's hours and days.
//It looks to see if the check box is ticked for each day of the week in turn.
//If a checkbox is ticked, it calls a second script: updateCalendar
//It's probably possible to write a separate loop to go through the days of the week
for (var i = 0; i < range.getHeight(); i++){
//if Monday shift
if(times[i][2] == true){
calendarValues = updateCalendar(times[i][0], times[i][1], calendarValues, 0)
}
//if Tuesday shift
if(times[i][3] == true){
calendarValues = updateCalendar(times[i][0], times[i][1], calendarValues, 1)
}
//if Wednesday shift
if(times[i][4] == true){
calendarValues = updateCalendar(times[i][0], times[i][1], calendarValues, 2)
}
//if Thursday shift
if(times[i][5] == true){
calendarValues = updateCalendar(times[i][0], times[i][1], calendarValues, 3)
}
//if Friday shift
if(times[i][6] == true){
calendarValues = updateCalendar(times[i][0], times[i][1], calendarValues, 4)
}
//if Saturday shift
if(times[i][7] == true){
calendarValues = updateCalendar(times[i][0], times[i][1], calendarValues, 5)
}
//if Sunday shift
if(times[i][8] == true){
calendarValues = updateCalendar(times[i][0], times[i][1], calendarValues, 6)
}
}
//Once ithas looped through al the staff member's shifts, it updates the RosterOverview
//sheet with how many people are on each hour
calendarRange.setValues(calendarValues);
}
function updateCalendar(startTime, endTime, roster, day){ //Let day be a digit: 0 for Monday, 1 for Tuesday.... Roster is the calendarValues array from the roster function
if(startTime > endTime){ //night shift over midnight
for (var j = startTime.getHours(); j < 24; j++){
roster[j][day] = roster[j][day] + 1;
}
for (var j = 0; j < endTime.getHours(); j++){
roster[j][day + 1] = roster[j][day + 1] + 1;
}
} else {
for (var j = startTime.getHours(); j < endTime.getHours(); j++){
roster[j][day] = roster[j][day] + 1;
}
}
return roster;
}
What you need to do
It's probably best to add a custom menu so you can run it from the sheet: see this Fundamentals codelab to learn how: https://developers.google.com/codelabs/apps-script-fundamentals-3#0
You need to make sure your Google Sheet and the Google script are set to the same timezone. To set the timezone for the script, from the script editor you want to click on [Use legacy editor] in the top right hand corner then go to File > Project properties. Once set, return to the new editor.
You will need to write a little bit of code that selects the range of data for your staff members' shifts and a bit of code that resets the RosterOverview spreadsheet values to 0s before you run the script again.
Let me know if you have any problems.
I want to trigger a google script every 15 min in specific time slots, which are 9:30am - 4:30pm and 9:30pm - 5:30am, Monday to Friday only.
I want the script to send email when it is triggered. I write some codes (searching from google) and it successfully sends email to me, but I don’t know how to trigger the script in specific time slots I mentioned above. It is my first time try to write codes and I don’t know how to do it. I google for it, but my code doesn’t work. So, I come here looking for help. Thank you very much.
It is not feasible (or very convoluted) to set up an Apps Script trigger for specific time slots
Much easier is to set it up as everyMinutes(n) and then implement a conditional statement within the function run on trigger to decide either the email shall be sent or not
Sample (compare it carefully against the conditions you want to implement):
//run only once:
function createTrigger(){
ScriptApp.newTrigger("runOnTrigger")
.timeBased()
.everyMinutes(15)
.create();
}
function runOnTrigger(){
var date = new Date();
var weekDay = date.getDay();
var hours = date.getHours();
var minutes = date.getMinutes();
//stop execution if it's a weekday
if(weekDay == 0 || weekDay == 6){
return;
}
// stop execution if it's between 4:30pm and 9:30pm
if(((hours == 16 && minutes >= 30) || hours >= 17) && ((hours == 21 && minutes <= 30) || hours < 21)){
return;
}
// stop execution if it's between 9:30pm and 5:30 am
if(((hours == 5 && minutes >= 30) || hours > 6) && ((hours == 16 && minutes <= 30) || hours < 16)){
return;
}
// implement here your request (e.g. sending emails) that shall be run if you are in the correc time slot
}
I'm interested in having my functions be triggered every 30 days. The closest I can find within Google is the once a month option, but that'll vary away from 30 days due to February and the months with 31 days.
Does anyone have any advice on how I could get this to work?
Thanks!
Trigger this every day. It will run the first time then eventually run 30 days later etc.
// trigger this function every day
function myFunction() {
var lastRunDate = new Date(PropertiesService.getScriptProperties().getProperty("lastRunDate"));
var thirtyDaysAgo = new Date(new Date().setDate(new Date().getDate()-30));
if (lastRunDate > thirtyDaysAgo) {
return; //exit because it's not yet been 30 days since last run
} else {
PropertiesService.getScriptProperties().setProperty("lastRunDate", new Date());
}
// do you work here
}
I am trying to figure out how to check if it the current time is Thursday 9:00 AM. I don't care about date in any other way except that it is a Thursday and that it is past 9:00 AM.
I've never written a single google script before and the API here doesn't seem to offer something like what I am looking for: https://developers.google.com/apps-script/reference/contacts/date-field
Here is how far I've gotten:
FORM_OPEN_DATE = "2016-03-09 12:20";
FORM_CLOSE_DATE = "2016-03-09 12:30";
function Initialize() {
if ((FORM_OPEN_DATE !== "") &&
((new Date()).getTime() < parseDate_(FORM_OPEN_DATE).getTime())) {
closeForm();
ScriptApp.newTrigger("openForm")
.timeBased()
.at(parseDate_(FORM_OPEN_DATE))
.create();
}
if (FORM_CLOSE_DATE !== "") {
ScriptApp.newTrigger("closeForm")
.timeBased()
.at(parseDate_(FORM_CLOSE_DATE))
.create();
}
}
My script above works as intended, it will open and close the form between the time frames, however I am trying to figure out how to check based on the day of the week as oppose to exact date. Reason being is that I wish for the form to open itself every thursday at 9:00 AM and close at 8:30 PM.
Is this achievable?
The JavaScript method getDay() returns the day of the week as a number. Sunday is 0, Monday is 1, and so on. So, Thursday would be 4.
if (new Date().getDay() === 4) {//true block};
I'm trying to create a trigger that activates on weekdays only and at a specific time, but I don't know what I'm doing wrong. Here is my code.
function createTriggers() {
var days = [ScriptApp.WeekDay.MONDAY, ScriptApp.WeekDay.TUESDAY,
ScriptApp.WeekDay.WEDNESDAY, ScriptApp.WeekDay.THURSDAY,
ScriptApp.WeekDay.FRIDAY];
var d = new Date();
var time = d.toLocaleTimeString();
if (time == '3:05:00 PM EDT') {
for (var i = 0; i < days.length; i++) {
ScriptApp.newTrigger(Lunch1)
.timeBased().onWeekDay(days[i])
.everyMinutes().create();
}
}
}
I'm guessing that you mean you want to make it so a function is triggered (runs) every weekday at a certain time of the day. Here's what I would do.
First, in the Script Editor:
Go to Resources - Current Project's Triggers
Click Add a new trigger
Select the function that you want to be triggered
Change the next box to "Time-driven"
Change the next box to "Hour timer"
Then change the last box to "every hour"
Then at the very beginning of your function, add this code:
var d = new Date();
if (d.getDay() == 6 || d.getDay() == 0) return;
// more info here: http://www.w3schools.com/jsref/jsref_getday.asp
That will stop the rest of the script from running if it's a Saturday or Sunday.
Then, say you want the script to only run at one specific time each weekday, you could set the time-driven trigger to "Every minute" and then add another if statement after the one above:
if (d.getHours() != 15 && d.getMinutes() != 5) return;
That will stop the rest of the script from running if it's not exactly 15:05 (3:05 PM).
This is definitely not the most efficient way to do this, but it works.
Also, this is worth mentioning: I'm not sure if there's anything that would prevent you from triggering the script every minute. Google does impose quotas for what your scripts can do, but I didn't see anything about how many times a script can be triggered in a day. Here's the chart: https://developers.google.com/apps-script/guides/services/quotas
You should do something like this:
function createTriggers(func, hour) {
var weekdays = [ScriptApp.WeekDay.MONDAY, ScriptApp.WeekDay.TUESDAY,
ScriptApp.WeekDay.WEDNESDAY, ScriptApp.WeekDay.THURSDAY,
ScriptApp.WeekDay.FRIDAY];
for (var dayIndex in weekdays) {
var day = weekdays[dayIndex];
ScriptApp.newTrigger(func).timeBased()
.onWeekDay(day).atHour(hour).create();
}
}
This code creates a trigger for weekdays in a specific time calling the func function.