Executing Function with Minutes timer in Specific Times using Google Apps Script

Gists

This is a sample script for executing a function with the minutes timer in the specific times using Google Apps Script. For example, when this sample script is used, the following situation can be achieved.

  • Execute a function every 10 minutes only in 09:00 - 12:00 and 15:00 - 18:00 for the weekday.

When the above situation is shown as an image, it becomes as follows.

In the above sample situation, a function is run every 10 minutes in the green ranges of 09:00 - 12:00 and 15:00 - 18:00.

In the current specification, the maximum number of triggers is 20 for a script. Ref When the above script is used, 6 triggers are used. So the limitation is not affected.

Flow

The flow of this script is as follows. In this case, it will explain the flow using the above sample situation of “Execute a function every 10 minutes only in 09:00 - 12:00 and 15:00 - 18:00 for the weekday.”.

  1. Install a base trigger.

    • The sample function name is initTriggers.
    • This function is executed at 00:00 tomorrow. By this, the script is run from tomorrow.
    • When today supposes 2021-09-01, the function is executed at 2021-09-02T00:00:00.
  2. When initTriggers is run at 2021-09-02T00:00:00, 4 triggers for executing the functions at 2021-09-02T09:00:00, 2021-09-02T12:00:00, 2021-09-02T15:00:00 and 2021-09-02T18:00:00 are installed. And, the trigger for executing initTriggers is installed for 2021-09-03T00:00:00.

    • For example, when today is Saturday and Sunday, above 4 triggers are not installed. By this, on Saturday and Sunday, the script is not run.
  3. For example, when the trigger of 2021-09-02T09:00:00 is run, the trigger for executing every 10 minutes is installed and when the time is 2021-09-02T12:00:00, the trigger is deleted. About 2021-09-02T15:00:00 and 2021-09-02T18:00:00, the same situation is also run.

  4. When it’s 2021-09-03T00:00:00, initTriggers is run, and then, the above 2 and 3 flows are run.

By this, the function can be executed with the minutes timer in specific times.

Usage

1. Sample script

Please copy and paste the following script to the script editor of Google Apps Script. And, please set the variable of runTime and set your script in main(). And then, please run the function of runOnlyOneTime. By this, the trigger is run from 00:00 in tomorrow.

And, when the day is Saturday and Sunday under weekday: true, the script is run on the weekday. When weekday: false is used, the script is run only Saturday and Sunday.

// Please set the condition of the triggers.
const runTimes = {
  cycle: 10, // Unit is minute.
  functionName: "main",
  weekday: true, // When this property is existing, you can run functionName at weekday or weekend. When this property is not used, the function is run every day.
  times: [
    { start: "09:00", end: "12:00" },
    { start: "15:00", end: "18:00" },
  ],
};

// Please set your Web Apps URL.
const webAppsUrl = "https://script.google.com/macros/s/###/exec"; // or https://script.google.com/macros/s/###/dev of developer mode

// Please set your script you want to execute with the minutes timer in the specific times.
function main() {
  //
  // do something
  //
}

// When you run this function only one time, your main script will be run by the time-driven triggers.
// --- PLEASE RUN THIS FUNCTION ONLY ONE TIME! ---
function runOnlyOneTime() {
  fetchWebApps_(`${webAppsUrl}?func=${"setRunEveryDay"}`);
}

//
// --- Script for installing the triggers --- Begin
//

// When v8 runtime is used, when the trigger is set from the function executing by a trigger, the trigger is disabled. This is the recognized bug. But unfortunately, this has still not been resolved. (September 21, 2021)
// https://issuetracker.google.com/issues/150756612
// https://issuetracker.google.com/issues/142405165
const doGet = (e) => {
  const func = e.parameter.func;
  if (func == "setRunEveryDay") {
    setRunEveryDay_();
  } else if (func == "initTrigger") {
    initTriggers();
  } else if (func == "StartEndTriggers") {
    if (
      !runTimes.hasOwnProperty("weekday") ||
      (runTimes.hasOwnProperty("weekday") &&
        runTimes.weekday === true &&
        checkWeekday_()) ||
      (runTimes.hasOwnProperty("weekday") &&
        runTimes.weekday === false &&
        !checkWeekday_())
    ) {
      convertStrToDateObj_().times.forEach(({ start, end }) => {
        ScriptApp.newTrigger("startTrigger").timeBased().at(start).create();
        ScriptApp.newTrigger("endTrigger").timeBased().at(end).create();
      });
    }
  } else {
    throw new Error("No query parameter.");
  }
  return ContentService.createTextOutput();

  // DriveApp.createFile() // This is used for automatically detecting the scopes for requesting to Web Apps. Please don't remove this comment line.
};

const fetchWebApps_ = (url) =>
  UrlFetchApp.fetch(url, {
    headers: { authorization: "Bearer " + ScriptApp.getOAuthToken() },
  });

// Below script is used for installing triggers.
const initTriggers = (_) => {
  deleteTriggers_([
    runTimes.functionName,
    "startTrigger",
    "endTrigger",
    "initTriggers",
  ]);
  deleteTriggers_([
    runTimes.functionName,
    "startTrigger",
    "endTrigger",
    "initTriggers",
  ]);
  fetchWebApps_(`${webAppsUrl}?func=${"StartEndTriggers"}`);
  fetchWebApps_(`${webAppsUrl}?func=${"setRunEveryDay"}`);
};

const setRunEveryDay_ = (t = new Date()) => {
  t.setDate(t.getDate() + 1);
  t.setHours(0);
  t.setMinutes(0);
  t.setSeconds(0);
  ScriptApp.newTrigger("initTriggers").timeBased().at(t).create();
};

const startTrigger = (_) => {
  const obj = convertStrToDateObj_();
  ScriptApp.newTrigger(obj.functionName)
    .timeBased()
    .everyMinutes(obj.cycle)
    .create();
};

const endTrigger = (_) => deleteTriggers_([runTimes.functionName]);

const convertStrToDateObj_ = (now = new Date()) => {
  now = now.toISOString();
  runTimes.times = runTimes.times.map((obj) => {
    const [sh, sm] = obj.start.split(":");
    const [eh, em] = obj.end.split(":");
    const d1 = new Date(now);
    const d2 = new Date(now);
    d1.setHours(sh);
    d1.setMinutes(sm);
    d1.setSeconds(0);
    d2.setHours(eh);
    d2.setMinutes(em);
    d2.setSeconds(0);
    obj.start = new Date(d1.getTime() - runTimes.cycle * 60 * 1000);
    obj.end = new Date(d2.getTime() + runTimes.cycle * 60 * 1000);
    return obj;
  });
  return runTimes;
};

const deleteTriggers_ = (functions) =>
  ScriptApp.getScriptTriggers().forEach((t) => {
    if (functions.includes(t.getHandlerFunction())) ScriptApp.deleteTrigger(t);
  });

const checkWeekday_ = (d = new Date()) => ![0, 6].includes(d.getDay());

//
// --- Script for installing the triggers --- End
//

2. Deploy Web Apps

This sample script used Web Apps. Because when v8 runtime is used, when the trigger is set from the function executing by a trigger, the trigger is disabled. This is the recognized bug. But unfortunately, this has still not been resolved. (September 21, 2021) But, when the trigger is installed by Web Apps, no issue occurs. So, please deploy Web Apps.

The detail information can be seen at the official document and my report.

  1. On the script editor, at the top right of the script editor, please click “click Deploy” -> “New deployment”.
  2. Please click “Select type” -> “Web App”.
  3. Please input the information about the Web App in the fields under “Deployment configuration”.
  4. Please select “Me” for “Execute as”.
    • This is the important of this workaround.
  5. Please select “Only myself” for “Who has access”.
  6. Please click “Deploy” button.
  7. Copy the URL of Web App. It’s like https://script.google.com/macros/s/###/exec.

As a point for developing, in my environment, I have used this URL as the developer mode like https://script.google.com/macros/s/###/dev. By this, even when the script is modified, it is not required to update the version of Web Apps.

3. Run script

When you use this script, please run the function of runOnlyOneTime(). By this, the 1st trigger is installed at 00:00 of tomorrow. When it’s 00:01 of tomorrow, initTriggers() is run and triggers for executing main() are installed.

References

 Share!