fixing working hours if they go to next day

This commit is contained in:
Christian Kellner
2025-09-25 15:57:13 +02:00
parent 44242d4e6a
commit eabade9ba7
2 changed files with 51 additions and 12 deletions

View File

@@ -109,11 +109,22 @@ function timeStringToMs(timeString, now) {
} }
/** /**
* Check whether current time is within configured working hours, or no hours are set. * Determine whether the given timestamp is within the configured working hours, or return true when the window is not set.
* If working hours are missing or incomplete, returns true. * - If workingHours is missing or either 'from' or 'to' is empty/null, returns true.
* @param {{workingHours?: {from?: string, to?: string}}} config * - Supports windows that cross midnight (e.g., from '23:00' to '06:00').
* @param {number} now - Epoch ms *
* @returns {boolean} * Time parsing is based on the local timezone of the running process.
*
* @param {{workingHours?: {from?: string|null, to?: string|null}}} config - Configuration object containing working hours in 'HH:mm' format.
* @param {number} now - Epoch milliseconds to evaluate.
* @returns {boolean} True when execution is allowed at 'now'.
* @example
* // Same-day window
* duringWorkingHoursOrNotSet({ workingHours: { from: '08:00', to: '17:00' } }, someTime);
* @example
* // Window crossing midnight
* // For { from: '05:00', to: '00:30' } → 23:00 => true, 01:00 => false, 06:00 => true
* duringWorkingHoursOrNotSet({ workingHours: { from: '05:00', to: '00:30' } }, Date.now());
*/ */
function duringWorkingHoursOrNotSet(config, now) { function duringWorkingHoursOrNotSet(config, now) {
const { workingHours } = config; const { workingHours } = config;
@@ -122,7 +133,20 @@ function duringWorkingHoursOrNotSet(config, now) {
} }
const toDate = timeStringToMs(workingHours.to, now); const toDate = timeStringToMs(workingHours.to, now);
const fromDate = timeStringToMs(workingHours.from, now); const fromDate = timeStringToMs(workingHours.from, now);
return fromDate <= now && toDate >= now;
// If parsing fails (e.g., malformed time), be lenient and allow.
if (isNaN(toDate) || isNaN(fromDate)) {
return true;
}
if (toDate >= fromDate) {
// Same-day window (e.g., 08:00 - 17:00)
return now >= fromDate && now <= toDate;
}
// Window crosses midnight (e.g., 05:00 -> 00:30 next day)
// Accept if we are after 'from' today OR before 'to' today (which represents next day's cutoff).
return now >= fromDate || now <= toDate;
} }
/** /**
@@ -244,13 +268,13 @@ function sleep(ms) {
} }
/** /**
* returns a random into between start and end * Return a random integer between min and max (inclusive).
* @param a start int * @param {number} min - Minimum integer value.
* @param b max int * @param {number} max - Maximum integer value.
* @returns {*} * @returns {number} A random integer N where min <= N <= max.
*/ */
function randomBetween(a, b) { function randomBetween(min, max) {
return Math.floor(Math.random() * (b - a + 1)) + a; return Math.floor(Math.random() * (max - min + 1)) + min;
} }
// Call refreshConfig() from the application entrypoint during startup to populate config. // Call refreshConfig() from the application entrypoint during startup to populate config.

View File

@@ -8,6 +8,7 @@ const fakeWorkingHoursConfig = (from, to) => ({
from, from,
}, },
}); });
describe('utils', () => { describe('utils', () => {
describe('#isOneOf()', () => { describe('#isOneOf()', () => {
it('should be false', () => { it('should be false', () => {
@@ -33,5 +34,19 @@ describe('utils', () => {
it('should be true if only from is set', () => { it('should be true if only from is set', () => {
expect(duringWorkingHoursOrNotSet(fakeWorkingHoursConfig('12:00', null), 1622026740000)).to.be.true; expect(duringWorkingHoursOrNotSet(fakeWorkingHoursConfig('12:00', null), 1622026740000)).to.be.true;
}); });
it('should handle working hours that cross midnight (e.g., 05:00 → 00:30)', () => {
const cfg = fakeWorkingHoursConfig('05:00', '00:30');
const mkTs = (h, m = 0) => {
const d = new Date();
d.setHours(h);
d.setMinutes(m);
d.setSeconds(0);
d.setMilliseconds(0);
return d.getTime();
};
expect(duringWorkingHoursOrNotSet(cfg, mkTs(23, 0))).to.be.true; // 23:00 => within window
expect(duringWorkingHoursOrNotSet(cfg, mkTs(1, 0))).to.be.false; // 01:00 => outside window
expect(duringWorkingHoursOrNotSet(cfg, mkTs(6, 0))).to.be.true; // 06:00 => within window
});
}); });
}); });