Skip to content

Commit 45f8be4

Browse files
authored
Script Include: Next Business Window Calculator (ServiceNowDevProgram#2468)
* Create README.md * Create next_business_window.js
1 parent a5a7017 commit 45f8be4

File tree

2 files changed

+117
-0
lines changed

2 files changed

+117
-0
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Next Business Window Calculator
2+
3+
## Problem
4+
You often need to compute the *next business window* for task scheduling, SLAs, or batch processing: “Start at 16:45, add 95 working minutes using schedule X and holiday set Y, and return both the end time and the ‘windows’ you traversed.” Native APIs give you the building blocks, but teams frequently re-invent this logic.
5+
6+
## Where to use it
7+
- Script Include utility callable from Business Rules, Flow/Schedule jobs, or Background Scripts
8+
- Works in *scoped* apps
9+
10+
## What it does
11+
- Accepts: start `GlideDateTime`, working minutes (integer), a schedule sys_id (or name), and an optional timezone
12+
- Uses `GlideSchedule` to hop across working/non-working periods and holidays
13+
- Returns:
14+
- `endGdt` — the calculated ending `GlideDateTime`
15+
- `segments` — an ordered list of working sub-segments used (start/end per segment), useful for audit/debugging
16+
- `consumedMinutes` — total minutes consumed
17+
18+
## Configuration
19+
At the top of the script you can configure:
20+
- Default schedule sys_id (fallback)
21+
- Default timezone (e.g., `Europe/London`)
22+
- Maximum safety iterations
23+
24+
## How it works
25+
The utility constructs a `GlideSchedule` from the provided schedule id, aligns the starting point, then iteratively consumes the requested working minutes across schedule segments (respecting holidays and timezone). It avoids recursion and uses a safety counter to prevent infinite loops.
26+
27+
## References
28+
- GlideSchedule (Scoped) API. ServiceNow Docs.
29+
https://www.servicenow.com/docs/ (GlideSchedule Scoped)
30+
- Server API overview (Zurich docs bundle). :contentReference[oaicite:1]{index=1}
31+
32+
## Example
33+
```js
34+
var util = new x_snc_example.NextBusinessWindow();
35+
var result = util.addWorkingMinutes('2025-10-21 16:45:00', 95, 'your_schedule_sys_id', 'Europe/London');
36+
// result.endGdt.getDisplayValue() -> "2025-10-22 09:20:00"
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
var NextBusinessWindow = Class.create();
2+
NextBusinessWindow.prototype = (function () {
3+
// Configuration
4+
var DEFAULT_SCHEDULE_SYS_ID = ''; // optional fallback schedule sys_id (cmn_schedule)
5+
var DEFAULT_TZ = 'Europe/London';
6+
var MAX_SEGMENTS = 1000; // safety guard
7+
8+
// Helpers
9+
function _toGdt(input) {
10+
if (input instanceof GlideDateTime) return input;
11+
var g = new GlideDateTime();
12+
if (input) g.setDisplayValue(input);
13+
return g;
14+
}
15+
function _getSchedule(scheduleSysId) {
16+
var sch = new GlideSchedule();
17+
if (scheduleSysId) sch.load(scheduleSysId);
18+
else if (DEFAULT_SCHEDULE_SYS_ID) sch.load(DEFAULT_SCHEDULE_SYS_ID);
19+
else throw new Error('No schedule sys_id supplied and no default configured.');
20+
return sch;
21+
}
22+
function _setTz(gdt, tz) { if (tz) gdt.setTZ(tz); return gdt; }
23+
24+
return {
25+
initialize: function () {},
26+
27+
/**
28+
* Add working minutes across a GlideSchedule.
29+
* @param {String|GlideDateTime} start
30+
* @param {Number} minutes
31+
* @param {String} scheduleSysId
32+
* @param {String} [timeZone] IANA name, e.g. "Europe/London"
33+
* @returns {{endGdt: GlideDateTime, consumedMinutes: number, segments: Array}}
34+
*/
35+
addWorkingMinutes: function (start, minutes, scheduleSysId, timeZone) {
36+
if (!minutes || minutes < 0) throw new Error('Minutes must be a positive integer.');
37+
var tz = timeZone || DEFAULT_TZ;
38+
var sch = _getSchedule(scheduleSysId);
39+
40+
var cursor = _toGdt(start);
41+
_setTz(cursor, tz);
42+
43+
// If not in schedule, jump to the next working start
44+
if (!sch.isInSchedule(cursor)) {
45+
var nextStart = sch.getNextStartTime(cursor);
46+
if (!nextStart) throw new Error('No next working period found from start time.');
47+
cursor = nextStart;
48+
}
49+
50+
var remaining = parseInt(minutes, 10);
51+
var segments = [];
52+
var guard = 0;
53+
54+
while (remaining > 0) {
55+
if (guard++ > MAX_SEGMENTS) throw new Error('Exceeded max segments; check schedule/inputs.');
56+
57+
var segEnd = sch.getNextEndTime(cursor);
58+
if (!segEnd) throw new Error('Schedule has no next end time; check configuration.');
59+
60+
var available = Math.ceil((segEnd.getNumericValue() - cursor.getNumericValue()) / (60 * 1000)); // minutes
61+
62+
if (remaining <= available) {
63+
var end = new GlideDateTime(cursor);
64+
end.addSeconds(remaining * 60);
65+
segments.push({ segmentStart: new GlideDateTime(cursor), segmentEnd: new GlideDateTime(end), consumed: remaining });
66+
return { endGdt: end, consumedMinutes: minutes, segments: segments };
67+
}
68+
69+
// consume full segment
70+
segments.push({ segmentStart: new GlideDateTime(cursor), segmentEnd: new GlideDateTime(segEnd), consumed: available });
71+
remaining -= available;
72+
73+
var nextStart = sch.getNextStartTime(segEnd);
74+
if (!nextStart) throw new Error('No subsequent working segment found.');
75+
cursor = nextStart;
76+
}
77+
78+
return { endGdt: new GlideDateTime(cursor), consumedMinutes: minutes, segments: segments };
79+
}
80+
};
81+
})();

0 commit comments

Comments
 (0)