Skip to content

Commit 5fd7979

Browse files
authored
Add BusinessTimeUtils for schedule management
Implement business time utilities for working hours calculations.
1 parent 7909c04 commit 5fd7979

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-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+
};

0 commit comments

Comments
 (0)