Skip to content

Commit 829c10f

Browse files
authored
Core ServiceNow APIs - Business Time Utilities - Pull 1 (#2372)
* Add README for business time utilities Added README.md for business time utilities that provide functions for adding working hours, calculating working minutes, finding next open time, and checking schedule inclusion. * Add BusinessTimeUtils for schedule management Implement business time utilities for working hours calculations. * Add example background script for BusinessTimeUtils This script demonstrates the usage of BusinessTimeUtils for adding working hours, calculating working minutes between two dates, finding the next open time, and checking if the current time is within the schedule.
1 parent 05b9e6a commit 829c10f

File tree

3 files changed

+188
-0
lines changed

3 files changed

+188
-0
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
var BusinessTimeUtils = Class.create();
2+
BusinessTimeUtils.prototype = {
3+
initialize: function() {},
4+
5+
/**
6+
* Add working hours to a start date, respecting schedule and holidays.
7+
* @param {String} scheduleSysId - sys_id of the GlideSchedule
8+
* @param {Number} hoursToAdd - working hours to add (can be fractional)
9+
* @param {GlideDateTime|String} startGdt - start time; if string, must be ISO/Glide-friendly
10+
* @param {String} [timeZone] - optional IANA TZ, else schedule/instance TZ
11+
* @returns {Object} { ok:Boolean, due:GlideDateTime|null, minutesAdded:Number, message:String }
12+
*/
13+
addWorkingHours: function(scheduleSysId, hoursToAdd, startGdt, timeZone) {
14+
var result = { ok: false, due: null, minutesAdded: 0, message: '' };
15+
try {
16+
this._assertSchedule(scheduleSysId);
17+
var start = this._toGdt(startGdt);
18+
var msToAdd = Math.round(Number(hoursToAdd) * 60 * 60 * 1000);
19+
if (!isFinite(msToAdd) || msToAdd <= 0) {
20+
result.message = 'hoursToAdd must be > 0';
21+
return result;
22+
}
23+
24+
var sched = new GlideSchedule(scheduleSysId, timeZone || '');
25+
var due = sched.add(new GlideDateTime(start), msToAdd); // returns GlideDateTime
26+
27+
// How many working minutes were added according to the schedule
28+
var mins = Math.round(sched.duration(start, due) / 60000);
29+
30+
result.ok = true;
31+
result.due = due;
32+
result.minutesAdded = mins;
33+
return result;
34+
} catch (e) {
35+
result.message = String(e);
36+
return result;
37+
}
38+
},
39+
40+
/**
41+
* Calculate working minutes between two times using the schedule.
42+
* @returns {Object} { ok:Boolean, minutes:Number, message:String }
43+
*/
44+
workingMinutesBetween: function(scheduleSysId, startGdt, endGdt, timeZone) {
45+
var out = { ok: false, minutes: 0, message: '' };
46+
try {
47+
this._assertSchedule(scheduleSysId);
48+
var start = this._toGdt(startGdt);
49+
var end = this._toGdt(endGdt);
50+
if (start.after(end)) {
51+
out.message = 'start must be <= end';
52+
return out;
53+
}
54+
var sched = new GlideSchedule(scheduleSysId, timeZone || '');
55+
out.minutes = Math.round(sched.duration(start, end) / 60000);
56+
out.ok = true;
57+
return out;
58+
} catch (e) {
59+
out.message = String(e);
60+
return out;
61+
}
62+
},
63+
64+
/**
65+
* Find the next time that is inside the schedule window at or after fromGdt.
66+
* @returns {Object} { ok:Boolean, nextOpen:GlideDateTime|null, message:String }
67+
*/
68+
nextOpen: function(scheduleSysId, fromGdt, timeZone) {
69+
var out = { ok: false, nextOpen: null, message: '' };
70+
try {
71+
this._assertSchedule(scheduleSysId);
72+
var from = this._toGdt(fromGdt);
73+
var sched = new GlideSchedule(scheduleSysId, timeZone || '');
74+
75+
// If already inside schedule, return the same timestamp
76+
if (sched.isInSchedule(from)) {
77+
out.ok = true;
78+
out.nextOpen = new GlideDateTime(from);
79+
return out;
80+
}
81+
82+
// Move forward minute by minute until we hit an in-schedule time, with a sane cap
83+
var probe = new GlideDateTime(from);
84+
var limitMinutes = 24 * 60 * 30; // cap search to 30 days
85+
for (var i = 0; i < limitMinutes; i++) {
86+
probe.addSecondsUTC(60);
87+
if (sched.isInSchedule(probe)) {
88+
out.ok = true;
89+
out.nextOpen = new GlideDateTime(probe);
90+
return out;
91+
}
92+
}
93+
out.message = 'No open window found within 30 days';
94+
return out;
95+
} catch (e) {
96+
out.message = String(e);
97+
return out;
98+
}
99+
},
100+
101+
/**
102+
* Check if a time is inside the schedule.
103+
* @returns {Object} { ok:Boolean, inSchedule:Boolean, message:String }
104+
*/
105+
isInSchedule: function(scheduleSysId, whenGdt, timeZone) {
106+
var out = { ok: false, inSchedule: false, message: '' };
107+
try {
108+
this._assertSchedule(scheduleSysId);
109+
var when = this._toGdt(whenGdt);
110+
var sched = new GlideSchedule(scheduleSysId, timeZone || '');
111+
out.inSchedule = sched.isInSchedule(when);
112+
out.ok = true;
113+
return out;
114+
} catch (e) {
115+
out.message = String(e);
116+
return out;
117+
}
118+
},
119+
120+
// ---------- helpers ----------
121+
122+
_toGdt: function(val) {
123+
if (val instanceof GlideDateTime) return new GlideDateTime(val);
124+
if (typeof val === 'string' && val) return new GlideDateTime(val);
125+
if (!val) return new GlideDateTime(); // default now
126+
throw 'Unsupported datetime value';
127+
},
128+
129+
_assertSchedule: function(sysId) {
130+
if (!sysId) throw 'scheduleSysId is required';
131+
var gr = new GlideRecord('cmn_schedule');
132+
if (!gr.get(sysId)) throw 'Schedule not found: ' + sysId;
133+
},
134+
135+
type: 'BusinessTimeUtils'
136+
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Business time utilities (add, diff, next open, in schedule)
2+
3+
## What this solves
4+
Teams repeatedly reimplement the same business-time maths. This utility wraps `GlideSchedule` with four practical helpers so you can:
5+
- Add N working hours to a start date
6+
- Calculate working minutes between two dates
7+
- Find the next open time inside a schedule
8+
- Check if a specific time is inside the schedule window
9+
10+
All functions return simple objects that are easy to log, test, and consume in Flows or Rules.
11+
12+
## Where to use
13+
- Script Include in global or scoped app
14+
- Call from Business Rules, Flow Actions, or Background Scripts
15+
16+
## Functions
17+
- `addWorkingHours(scheduleSysId, hoursToAdd, startGdt, tz)`
18+
- `workingMinutesBetween(scheduleSysId, startGdt, endGdt, tz)`
19+
- `nextOpen(scheduleSysId, fromGdt, tz)`
20+
- `isInSchedule(scheduleSysId, whenGdt, tz)`
21+
22+
## Notes
23+
- The schedule determines both business hours and holidays.
24+
- `tz` is optional; if omitted, the schedule’s TZ or instance default applies.
25+
- All inputs accept either `GlideDateTime` or ISO strings (UTC-safe).
26+
27+
## References
28+
- GlideSchedule API
29+
https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideSchedule/concept/c_GlideScheduleAPI.html
30+
- GlideDateTime API
31+
https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideDateTime/concept/c_GlideDateTimeAPI.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Background Script demo for BusinessTimeUtils
2+
(function() {
3+
var SCHEDULE_SYS_ID = 'PUT_YOUR_SCHEDULE_SYS_ID_HERE';
4+
var TZ = 'Europe/London';
5+
6+
var util = new BusinessTimeUtils();
7+
8+
var start = new GlideDateTime(); // now
9+
var addRes = util.addWorkingHours(SCHEDULE_SYS_ID, 16, start, TZ);
10+
gs.info('Add 16h ok=' + addRes.ok + ', due=' + (addRes.due ? addRes.due.getDisplayValue() : addRes.message));
11+
12+
var end = new GlideDateTime(addRes.due || start);
13+
var diffRes = util.workingMinutesBetween(SCHEDULE_SYS_ID, start, end, TZ);
14+
gs.info('Working minutes between start and due: ' + diffRes.minutes);
15+
16+
var openRes = util.nextOpen(SCHEDULE_SYS_ID, new GlideDateTime(), TZ);
17+
gs.info('Next open ok=' + openRes.ok + ', at=' + (openRes.nextOpen ? openRes.nextOpen.getDisplayValue() : openRes.message));
18+
19+
var inRes = util.isInSchedule(SCHEDULE_SYS_ID, new GlideDateTime(), TZ);
20+
gs.info('Is now in schedule: ' + inRes.inSchedule);
21+
})();

0 commit comments

Comments
 (0)