Skip to content

Instantly share code, notes, and snippets.

@tabarra
Created January 4, 2025 20:54
Show Gist options
  • Save tabarra/92cd341350039078c065b3e0e5174ca1 to your computer and use it in GitHub Desktop.
Save tabarra/92cd341350039078c065b3e0e5174ca1 to your computer and use it in GitHub Desktop.
MinuteScheduler Class: Similar to setInterval, but aligned to the minute or a multiple of it, while also accounting for drift.
const roundToNextDivisible = (num: number, interval: number) => {
return Math.ceil(num / interval) * interval;
};
/**
* Similar to setInterval, but aligned to the minute or a multiple of it, while also accounting for drift.
*/
export default class MinuteScheduler {
public tsLast = Date.now();
constructor(
private readonly callback: () => void,
private readonly intervalMins = 1,
alignInitial = true,
) {
if (this.intervalMins < 1) {
throw new Error('intervalMins must be at least 1');
}
if (this.intervalMins > 59) {
throw new Error('intervalMins must be at most 59');
}
if (this.intervalMins % 1 !== 0) {
throw new Error('intervalMins must be an integer');
}
if (intervalMins !== 1 && alignInitial) {
this.alignInitial();
} else {
this.scheduleNext();
}
}
/**
* Awaits until the minutes are divisible by this.intervalMins before starting the loop.
*/
private async alignInitial() {
//If it's already aligned, start the loop
const now = new Date();
if (now.getMinutes() % this.intervalMins === 0) {
this.run();
return;
}
//Find the next minute divisible and wait for it
const nextRun = new Date();
const currMinute = nextRun.getMinutes();
const nextMinute = roundToNextDivisible(currMinute, this.intervalMins);
const nextRunTs = nextRun.setMinutes(nextMinute, 0, 0);
const msDelay = nextRunTs - Date.now();
//Schedule first run
setTimeout(() => this.run(), msDelay);
}
/**
* Schedules the next run.
*/
private scheduleNext() {
const now = new Date();
const nextTs = now.setMinutes(now.getMinutes() + this.intervalMins, 0, 0);
const delay = nextTs - Date.now();
setTimeout(() => this.run(), delay);
}
/**
* Runs the callback and schedules the next run.
*/
private run() {
this.tsLast = Date.now();
this.callback();
this.scheduleNext();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment