Skip to content

Commit 0a64e91

Browse files
Merge branch 'main' into Validate_phone_format_123_234_5678_no_Regex
2 parents 5102dd4 + 829c10f commit 0a64e91

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)