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.
* If working hours are missing or incomplete, returns true.
* @param {{workingHours?: {from?: string, to?: string}}} config
* @param {number} now - Epoch ms
* @returns {boolean}
* Determine whether the given timestamp is within the configured working hours, or return true when the window is not set.
* - If workingHours is missing or either 'from' or 'to' is empty/null, returns true.
* - Supports windows that cross midnight (e.g., from '23:00' to '06:00').
*
* 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) {
const { workingHours } = config;
@@ -122,7 +133,20 @@ function duringWorkingHoursOrNotSet(config, now) {
}
const toDate = timeStringToMs(workingHours.to, 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
* @param a start int
* @param b max int
* @returns {*}
* Return a random integer between min and max (inclusive).
* @param {number} min - Minimum integer value.
* @param {number} max - Maximum integer value.
* @returns {number} A random integer N where min <= N <= max.
*/
function randomBetween(a, b) {
return Math.floor(Math.random() * (b - a + 1)) + a;
function randomBetween(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// Call refreshConfig() from the application entrypoint during startup to populate config.

View File

@@ -8,6 +8,7 @@ const fakeWorkingHoursConfig = (from, to) => ({
from,
},
});
describe('utils', () => {
describe('#isOneOf()', () => {
it('should be false', () => {
@@ -33,5 +34,19 @@ describe('utils', () => {
it('should be true if only from is set', () => {
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
});
});
});