Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
var BusinessTimeUtils = Class.create();
BusinessTimeUtils.prototype = {
initialize: function() {},

/**
* Add working hours to a start date, respecting schedule and holidays.
* @param {String} scheduleSysId - sys_id of the GlideSchedule
* @param {Number} hoursToAdd - working hours to add (can be fractional)
* @param {GlideDateTime|String} startGdt - start time; if string, must be ISO/Glide-friendly
* @param {String} [timeZone] - optional IANA TZ, else schedule/instance TZ
* @returns {Object} { ok:Boolean, due:GlideDateTime|null, minutesAdded:Number, message:String }
*/
addWorkingHours: function(scheduleSysId, hoursToAdd, startGdt, timeZone) {
var result = { ok: false, due: null, minutesAdded: 0, message: '' };
try {
this._assertSchedule(scheduleSysId);
var start = this._toGdt(startGdt);
var msToAdd = Math.round(Number(hoursToAdd) * 60 * 60 * 1000);
if (!isFinite(msToAdd) || msToAdd <= 0) {
result.message = 'hoursToAdd must be > 0';
return result;
}

var sched = new GlideSchedule(scheduleSysId, timeZone || '');
var due = sched.add(new GlideDateTime(start), msToAdd); // returns GlideDateTime

// How many working minutes were added according to the schedule
var mins = Math.round(sched.duration(start, due) / 60000);

result.ok = true;
result.due = due;
result.minutesAdded = mins;
return result;
} catch (e) {
result.message = String(e);
return result;
}
},

/**
* Calculate working minutes between two times using the schedule.
* @returns {Object} { ok:Boolean, minutes:Number, message:String }
*/
workingMinutesBetween: function(scheduleSysId, startGdt, endGdt, timeZone) {
var out = { ok: false, minutes: 0, message: '' };
try {
this._assertSchedule(scheduleSysId);
var start = this._toGdt(startGdt);
var end = this._toGdt(endGdt);
if (start.after(end)) {
out.message = 'start must be <= end';
return out;
}
var sched = new GlideSchedule(scheduleSysId, timeZone || '');
out.minutes = Math.round(sched.duration(start, end) / 60000);
out.ok = true;
return out;
} catch (e) {
out.message = String(e);
return out;
}
},

/**
* Find the next time that is inside the schedule window at or after fromGdt.
* @returns {Object} { ok:Boolean, nextOpen:GlideDateTime|null, message:String }
*/
nextOpen: function(scheduleSysId, fromGdt, timeZone) {
var out = { ok: false, nextOpen: null, message: '' };
try {
this._assertSchedule(scheduleSysId);
var from = this._toGdt(fromGdt);
var sched = new GlideSchedule(scheduleSysId, timeZone || '');

// If already inside schedule, return the same timestamp
if (sched.isInSchedule(from)) {
out.ok = true;
out.nextOpen = new GlideDateTime(from);
return out;
}

// Move forward minute by minute until we hit an in-schedule time, with a sane cap
var probe = new GlideDateTime(from);
var limitMinutes = 24 * 60 * 30; // cap search to 30 days
for (var i = 0; i < limitMinutes; i++) {
probe.addSecondsUTC(60);
if (sched.isInSchedule(probe)) {
out.ok = true;
out.nextOpen = new GlideDateTime(probe);
return out;
}
}
out.message = 'No open window found within 30 days';
return out;
} catch (e) {
out.message = String(e);
return out;
}
},

/**
* Check if a time is inside the schedule.
* @returns {Object} { ok:Boolean, inSchedule:Boolean, message:String }
*/
isInSchedule: function(scheduleSysId, whenGdt, timeZone) {
var out = { ok: false, inSchedule: false, message: '' };
try {
this._assertSchedule(scheduleSysId);
var when = this._toGdt(whenGdt);
var sched = new GlideSchedule(scheduleSysId, timeZone || '');
out.inSchedule = sched.isInSchedule(when);
out.ok = true;
return out;
} catch (e) {
out.message = String(e);
return out;
}
},

// ---------- helpers ----------

_toGdt: function(val) {
if (val instanceof GlideDateTime) return new GlideDateTime(val);
if (typeof val === 'string' && val) return new GlideDateTime(val);
if (!val) return new GlideDateTime(); // default now
throw 'Unsupported datetime value';
},

_assertSchedule: function(sysId) {
if (!sysId) throw 'scheduleSysId is required';
var gr = new GlideRecord('cmn_schedule');
if (!gr.get(sysId)) throw 'Schedule not found: ' + sysId;
},

type: 'BusinessTimeUtils'
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Business time utilities (add, diff, next open, in schedule)

## What this solves
Teams repeatedly reimplement the same business-time maths. This utility wraps `GlideSchedule` with four practical helpers so you can:
- Add N working hours to a start date
- Calculate working minutes between two dates
- Find the next open time inside a schedule
- Check if a specific time is inside the schedule window

All functions return simple objects that are easy to log, test, and consume in Flows or Rules.

## Where to use
- Script Include in global or scoped app
- Call from Business Rules, Flow Actions, or Background Scripts

## Functions
- `addWorkingHours(scheduleSysId, hoursToAdd, startGdt, tz)`
- `workingMinutesBetween(scheduleSysId, startGdt, endGdt, tz)`
- `nextOpen(scheduleSysId, fromGdt, tz)`
- `isInSchedule(scheduleSysId, whenGdt, tz)`

## Notes
- The schedule determines both business hours and holidays.
- `tz` is optional; if omitted, the schedule’s TZ or instance default applies.
- All inputs accept either `GlideDateTime` or ISO strings (UTC-safe).

## References
- GlideSchedule API
https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideSchedule/concept/c_GlideScheduleAPI.html
- GlideDateTime API
https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideDateTime/concept/c_GlideDateTimeAPI.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Background Script demo for BusinessTimeUtils
(function() {
var SCHEDULE_SYS_ID = 'PUT_YOUR_SCHEDULE_SYS_ID_HERE';
var TZ = 'Europe/London';

var util = new BusinessTimeUtils();

var start = new GlideDateTime(); // now
var addRes = util.addWorkingHours(SCHEDULE_SYS_ID, 16, start, TZ);
gs.info('Add 16h ok=' + addRes.ok + ', due=' + (addRes.due ? addRes.due.getDisplayValue() : addRes.message));

var end = new GlideDateTime(addRes.due || start);
var diffRes = util.workingMinutesBetween(SCHEDULE_SYS_ID, start, end, TZ);
gs.info('Working minutes between start and due: ' + diffRes.minutes);

var openRes = util.nextOpen(SCHEDULE_SYS_ID, new GlideDateTime(), TZ);
gs.info('Next open ok=' + openRes.ok + ', at=' + (openRes.nextOpen ? openRes.nextOpen.getDisplayValue() : openRes.message));

var inRes = util.isInSchedule(SCHEDULE_SYS_ID, new GlideDateTime(), TZ);
gs.info('Is now in schedule: ' + inRes.inSchedule);
})();
Loading