diff --git a/README.md b/README.md index 6508707..c5f3301 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Schedule Later -schedule-later is a static class that provides methods for managing date-time based tasks, such as starting timeouts and intervals at a specific time of day. Under-the-hood, it uses `setTimeout` and `setInterval`. +schedule-later provides methods for managing date-time based tasks, such as starting timeouts and intervals at a specific time of day. Under-the-hood, it uses `setTimeout` and `setInterval`. ## Install @@ -17,7 +17,7 @@ yarn add schedule-later ## Import ```typescript -import { Scheduler, TimeInMS } from 'schedule-later' +import { startTimeout, startInterval, TimeInMS } from 'schedule-later' ``` ## Key Concepts @@ -55,10 +55,7 @@ The `Scheduler` class provides two main static methods: `startTimeout` and `star The `startTimeout` method starts a timeout that calls a given function after a specific delay. The delay is calculated based on the `TimeUntil` object passed to it. The method returns a `StopFunction` (see below). ```typescript -public static startTimeout( - timerFunc: Function, - start: TimeUntil -): StopFunction; +function startTimeout(timerFunc: Function, start: TimeUntil): StopFunction ``` ### startInterval @@ -66,11 +63,11 @@ public static startTimeout( The `startInterval` method starts an interval that calls a given function repeatedly with a fixed time delay between each call. Like `startTimeout`, the initial delay is calculated based on a `TimeUntil` object. The method returns a `StopFunction` (see below). ```typescript -public static startInterval( +function startInterval( intervalFunc: Function, intervalMS: number, start?: TimeUntil -): StopFunction; +): StopFunction ``` ## Stop Functions diff --git a/package.json b/package.json index 7eccad8..b48a71c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "schedule-later", - "version": "1.0.6", + "version": "1.1.0", "types": "dist/Scheduler.d.ts", "main": "dist/Scheduler.js", "author": "Nightness", @@ -18,6 +18,6 @@ "@types/jest": "^29.5.3", "@types/node": "^18.11.18", "jest": "^29.6.1", - "typescript": "^4.6.4" + "typescript": "^5.1.6" } } \ No newline at end of file diff --git a/src/Scheduler.test.ts b/src/Scheduler.test.ts index 3169529..7e90078 100644 --- a/src/Scheduler.test.ts +++ b/src/Scheduler.test.ts @@ -1,6 +1,6 @@ -import { Scheduler } from './Scheduler' +import { startTimeout, startInterval, TimeInMS } from './Scheduler' -describe('Scheduler', () => { +describe('setTimeout Tests', () => { beforeEach(() => { jest.useFakeTimers() }) @@ -9,10 +9,10 @@ describe('Scheduler', () => { jest.clearAllTimers() }) - test('startTimeout should correctly start and stop timeout', () => { + test('startTimeout, should start and stop properly', () => { const callback = jest.fn() - const stop = Scheduler.startTimeout(callback, { ms: 5000 }) + const stop = startTimeout(callback, { ms: 5000 }) // Advance timers by less than the delay and check if callback has not been called jest.advanceTimersByTime(2000) @@ -26,10 +26,30 @@ describe('Scheduler', () => { stop() }) - test('startInterval should correctly start and stop interval', () => { + test('startTimeout, should not call the function if stop function is called', () => { + const fn = jest.fn() + const stop = startTimeout(fn, { ms: TimeInMS.SECOND }) + + expect(fn).not.toBeCalled() + stop() + jest.advanceTimersByTime(TimeInMS.SECOND) + expect(fn).not.toBeCalled() + }) +}) + +describe('setInterval Tests', () => { + beforeEach(() => { + jest.useFakeTimers() + }) + + afterEach(() => { + jest.clearAllTimers() + }) + + test('startInterval, should correctly start and repeat interval', () => { const callback = jest.fn() - const stop = Scheduler.startInterval(callback, 2000, { ms: 5000 }) + const stop = startInterval(callback, 2000, { ms: 5000 }) // Advance timers by less than the initial delay and check if callback has not been called jest.advanceTimersByTime(2000) @@ -46,4 +66,14 @@ describe('Scheduler', () => { // Cleanup stop() }) + + test('startInterval, should not call the function if stop function is called', () => { + const fn = jest.fn() + const stop = startInterval(fn, 2000, { ms: TimeInMS.SECOND }) + + expect(fn).not.toBeCalled() + stop() + jest.advanceTimersByTime(TimeInMS.SECOND) + expect(fn).not.toBeCalled() + }) }) diff --git a/src/Scheduler.ts b/src/Scheduler.ts index 82176de..a1d0322 100644 --- a/src/Scheduler.ts +++ b/src/Scheduler.ts @@ -26,156 +26,154 @@ export type StopCancelFunction = (stopRunning: boolean) => void export type StopFunction = (stopTime?: TimeUntil) => StopCancelFunction | null -export class Scheduler { - private static timeUntil(start: TimeUntil) { - if (start.ms) { - return start.ms - } - - // get the current date - let now = new Date() - - // set the target time - const targetTime = start.timeOfDay - ? new Date( - now.getFullYear(), - now.getMonth(), - now.getDate(), - start.timeOfDay.hour, - start.timeOfDay.minute ?? 0, - start.timeOfDay.seconds ?? 0, - 0 - ) - : start.date ?? now - - // if the target time has already passed today, set it for tomorrow - if (start.timeOfDay && now > targetTime) { - targetTime.setDate(targetTime.getDate() + 1) - } - - // calculate the delay until the next target time - // @ts-ignore - TS doesn't know this is allowed - let delay = targetTime - now - - return delay +function timeUntil(start: TimeUntil) { + if (start.ms) { + return start.ms } - /** - * Start a timeout - * @param {Function} timerFunc - * @param {TimeUntil} start - * @return {StopFunction} - */ - public static startTimeout( - timerFunc: Function, - start: TimeUntil - ): StopFunction { - let delay = this.timeUntil(start) + // get the current date + let now = new Date() - // set a timeout to start the interval at the target time - let timeout: NodeJS.Timeout | null = null - timeout = setTimeout(function () { - // Clear the timeout variable - timeout = null - // call the function immediately - timerFunc() - }, delay) + // set the target time + const targetTime = start.timeOfDay + ? new Date( + now.getFullYear(), + now.getMonth(), + now.getDate(), + start.timeOfDay.hour, + start.timeOfDay.minute ?? 0, + start.timeOfDay.seconds ?? 0, + 0 + ) + : start.date ?? now - const stopNow = () => { - if (timeout) clearTimeout(timeout) - } - - // stop() function to stop the interval and timeout - // stop(stopInMS) will stop the interval and timeout in stopInMS milliseconds - // stop(stopHour, stopMinute) will stop the interval and timeout at the next stopHour:stopMinute - const stop = (stopTime?: TimeUntil): StopCancelFunction | null => { - if (stopTime === undefined) { - stopNow() - return null - } - - if ((stopTime as any) instanceof Date) { - // @ts-ignore - TS doesn't know this is allowed - const timeFromNow = stopTime - new Date() - const stopTimeout = setTimeout(stopNow, timeFromNow) - // stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true) - return (stopRunning: boolean = false) => { - clearTimeout(stopTimeout) - if (stopRunning) stopNow() - } - } - - const stopTimeout = setTimeout(stopNow, this.timeUntil(stopTime)) - // stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true) - return (stopRunning: boolean = false) => { - clearTimeout(stopTimeout) - if (stopRunning) stopNow() - } - } - - // return a cleanup function - return stop + // if the target time has already passed today, set it for tomorrow + if (start.timeOfDay && now > targetTime) { + targetTime.setDate(targetTime.getDate() + 1) } - /** - * Start an interval - * @param {Function} intervalFunc - * @param {number} intervalMS - * @param {TimeUntil} start - * @return {StopFunction} - */ - public static startInterval( - intervalFunc: Function, - intervalMS: number, - start?: TimeUntil - ): StopFunction { - let delay = start ? this.timeUntil(start) : 0 + // calculate the delay until the next target time + // @ts-ignore - TS doesn't know this is allowed + let delay = targetTime - now - // set a timeout to start the interval at the target time - let interval: number | null = null - let timeout: NodeJS.Timeout | null = setTimeout(function () { - // Clear the timeout variable - timeout = null - // start the interval - interval = setInterval(intervalFunc, intervalMS) - // call the function immediately - intervalFunc() - }, delay) - - const stopNow = () => { - if (timeout) clearTimeout(timeout) - if (interval) clearInterval(interval) - } - - // stop() function to stop the interval and timeout - // stop(stopInMS) will stop the interval and timeout in stopInMS milliseconds - // stop(stopHour, stopMinute) will stop the interval and timeout at the next stopHour:stopMinute - const stop = (stopTime?: TimeUntil): StopCancelFunction | null => { - if (stopTime === undefined) { - stopNow() - return null - } - - if ((stopTime as any) instanceof Date) { - // @ts-ignore - TS doesn't know this is allowed - const timeFromNow = stopTime - new Date() - const stopTimeout = setTimeout(stopNow, timeFromNow) - // stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true) - return (stopRunning: boolean = false) => { - clearTimeout(stopTimeout) - if (stopRunning) stopNow() - } - } - - const stopTimeout = setTimeout(stopNow, this.timeUntil(stopTime)) - // stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true) - return (stopRunning: boolean = false) => { - clearTimeout(stopTimeout) - if (stopRunning) stopNow() - } - } - - // return a cleanup function - return stop - } + return delay +} + +/** + * Start a timeout + * @param {Function} timerFunc + * @param {TimeUntil} start + * @return {StopFunction} + */ +export function startTimeout( + timerFunc: Function, + start: TimeUntil +): StopFunction { + let delay = timeUntil(start) + + // set a timeout to start the interval at the target time + let timeout: NodeJS.Timeout | null = null + timeout = setTimeout(function () { + // Clear the timeout variable + timeout = null + // call the function immediately + timerFunc() + }, delay) + + const stopNow = () => { + if (timeout) clearTimeout(timeout) + } + + // stop() function to stop the interval and timeout + // stop(stopInMS) will stop the interval and timeout in stopInMS milliseconds + // stop(stopHour, stopMinute) will stop the interval and timeout at the next stopHour:stopMinute + const stop = (stopTime?: TimeUntil): StopCancelFunction | null => { + if (stopTime === undefined) { + stopNow() + return null + } + + if ((stopTime as any) instanceof Date) { + // @ts-ignore - TS doesn't know this is allowed + const timeFromNow = stopTime - new Date() + const stopTimeout = setTimeout(stopNow, timeFromNow) + // stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true) + return (stopRunning: boolean = false) => { + clearTimeout(stopTimeout) + if (stopRunning) stopNow() + } + } + + const stopTimeout = setTimeout(stopNow, timeUntil(stopTime)) + // stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true) + return (stopRunning: boolean = false) => { + clearTimeout(stopTimeout) + if (stopRunning) stopNow() + } + } + + // return a cleanup function + return stop +} + +/** + * Start an interval + * @param {Function} intervalFunc + * @param {number} intervalMS + * @param {TimeUntil} start + * @return {StopFunction} + */ +export function startInterval( + intervalFunc: Function, + intervalMS: number, + start?: TimeUntil +): StopFunction { + let delay = start ? timeUntil(start) : 0 + + // set a timeout to start the interval at the target time + let interval: number | null = null + let timeout: NodeJS.Timeout | null = setTimeout(function () { + // Clear the timeout variable + timeout = null + // start the interval + interval = setInterval(intervalFunc, intervalMS) + // call the function immediately + intervalFunc() + }, delay) + + const stopNow = () => { + if (timeout) clearTimeout(timeout) + if (interval) clearInterval(interval) + } + + // stop() function to stop the interval and timeout + // stop(stopInMS) will stop the interval and timeout in stopInMS milliseconds + // stop(stopHour, stopMinute) will stop the interval and timeout at the next stopHour:stopMinute + const stop = (stopTime?: TimeUntil): StopCancelFunction | null => { + if (stopTime === undefined) { + stopNow() + return null + } + + if ((stopTime as any) instanceof Date) { + // @ts-ignore - TS doesn't know this is allowed + const timeFromNow = stopTime - new Date() + const stopTimeout = setTimeout(stopNow, timeFromNow) + // stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true) + return (stopRunning: boolean = false) => { + clearTimeout(stopTimeout) + if (stopRunning) stopNow() + } + } + + const stopTimeout = setTimeout(stopNow, timeUntil(stopTime)) + // stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true) + return (stopRunning: boolean = false) => { + clearTimeout(stopTimeout) + if (stopRunning) stopNow() + } + } + + // return a cleanup function + return stop }