From 7909c04335722d0f3e0b00ef0857c188efb7e57b Mon Sep 17 00:00:00 2001 From: hanna-g-sn Date: Tue, 21 Oct 2025 11:49:47 +0100 Subject: [PATCH 1/3] 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. --- .../README.md | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Core ServiceNow APIs/GlideDateTime/Business time utilities (add, diff, next open, in schedule)/README.md diff --git a/Core ServiceNow APIs/GlideDateTime/Business time utilities (add, diff, next open, in schedule)/README.md b/Core ServiceNow APIs/GlideDateTime/Business time utilities (add, diff, next open, in schedule)/README.md new file mode 100644 index 0000000000..3642bf3d0f --- /dev/null +++ b/Core ServiceNow APIs/GlideDateTime/Business time utilities (add, diff, next open, in schedule)/README.md @@ -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 From 5fd79797bf1b2bd002d38899a15f3c9f9ce53149 Mon Sep 17 00:00:00 2001 From: hanna-g-sn Date: Tue, 21 Oct 2025 11:50:52 +0100 Subject: [PATCH 2/3] Add BusinessTimeUtils for schedule management Implement business time utilities for working hours calculations. --- .../BusinessTimeUtils.js | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 Core ServiceNow APIs/GlideDateTime/Business time utilities (add, diff, next open, in schedule)/BusinessTimeUtils.js diff --git a/Core ServiceNow APIs/GlideDateTime/Business time utilities (add, diff, next open, in schedule)/BusinessTimeUtils.js b/Core ServiceNow APIs/GlideDateTime/Business time utilities (add, diff, next open, in schedule)/BusinessTimeUtils.js new file mode 100644 index 0000000000..7f5eeb61d8 --- /dev/null +++ b/Core ServiceNow APIs/GlideDateTime/Business time utilities (add, diff, next open, in schedule)/BusinessTimeUtils.js @@ -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' +}; From 9709c147cd6b7eacde4e035968340b5d58b7d513 Mon Sep 17 00:00:00 2001 From: hanna-g-sn Date: Tue, 21 Oct 2025 11:51:12 +0100 Subject: [PATCH 3/3] 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. --- .../example_background_usage.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Core ServiceNow APIs/GlideDateTime/Business time utilities (add, diff, next open, in schedule)/example_background_usage.js diff --git a/Core ServiceNow APIs/GlideDateTime/Business time utilities (add, diff, next open, in schedule)/example_background_usage.js b/Core ServiceNow APIs/GlideDateTime/Business time utilities (add, diff, next open, in schedule)/example_background_usage.js new file mode 100644 index 0000000000..82b8075838 --- /dev/null +++ b/Core ServiceNow APIs/GlideDateTime/Business time utilities (add, diff, next open, in schedule)/example_background_usage.js @@ -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); +})();