What It Is
GlideDateTime is ServiceNow's server-side JavaScript class for manipulating date and time values with timezone awareness. It exists because JavaScript's native Date object is fundamentally broken for enterprise applications—it can't reliably handle timezone conversions, has inconsistent behavior across browsers, and lacks the precision needed for business logic that spans global operations. ServiceNow built GlideDateTime to solve these problems at the platform level, ensuring consistent date handling regardless of where your code executes on their infrastructure.
Architecturally, GlideDateTime lives exclusively on the server side—you cannot instantiate it in client scripts, UI scripts, or catalog client scripts. It executes within the Rhino JavaScript engine that powers ServiceNow's server-side scripting environment, which means it has access to the platform's timezone database, user preferences, and system configuration. When you create a GlideDateTime object, you're not just getting a date—you're getting an object that understands ServiceNow's user context, including their configured timezone, date format preferences, and locale settings.
Under the hood, ServiceNow's GlideDateTime wraps Java's date/time libraries and maintains all dates internally in GMT. When you call setValue() or setDisplayValue(), the platform converts your input to GMT and stores it as a long integer representing milliseconds since epoch. This GMT storage ensures that date calculations remain consistent regardless of user timezones, server locations, or daylight saving time transitions. The timezone conversion only happens when you request a display value or format the date for output, using the current user's timezone preferences from their user record.
Without GlideDateTime, you cannot reliably calculate SLA deadlines across timezones, determine business hours for international teams, or perform date arithmetic that accounts for daylight saving time changes. You cannot safely compare dates from users in different timezones, schedule future events with timezone accuracy, or generate reports that show consistent date ranges regardless of who runs them. Most critically, you cannot trust that date-based business rules will fire at the correct time for global deployments, because native JavaScript date handling will fail you when users span multiple continents.
Developers use GlideDateTime daily in business rules, script includes, and workflow scripts for anything involving date logic. System administrators need it for data imports, scheduled jobs, and custom reporting where date precision matters. Solution architects rely on it for designing SLA frameworks, integration patterns that exchange dates with external systems, and multi-timezone deployment strategies. The complexity scales with your organization's global footprint—single-timezone deployments might get away with shortcuts, but enterprise implementations with users across continents will break without proper GlideDateTime usage.
GlideDateTime works alongside GlideDate for date-only fields, and GlideDuration for time span calculations. While GlideDate handles simple calendar dates without time components, GlideDateTime provides the full temporal precision needed for timestamps, SLA calculations, and business hour logic. It differs from GlideDuration in that it represents specific points in time rather than elapsed periods, though both classes frequently work together in SLA and scheduling calculations.
How It Works Under the Hood
When you instantiate a GlideDateTime object, ServiceNow's Rhino JavaScript engine creates a wrapper around Java's temporal classes, specifically leveraging the platform's custom timezone database and user context awareness. The object maintains its internal state as a GMT timestamp while preserving metadata about the user's timezone, locale, and date format preferences. This dual-state approach allows the same GlideDateTime instance to return different display values depending on which user context is active when formatting methods are called.
The timezone conversion engine pulls user preferences from the sys_user table's time_zone field, then applies the appropriate offset calculations using ServiceNow's internal timezone database. This database gets updated with ServiceNow releases to handle changing daylight saving time rules, new timezone definitions, and political timezone changes. When you call methods like getDisplayValue() or getLocalTime(), the platform performs real-time offset calculations that account for historical daylight saving time rules, ensuring that past dates display correctly even if timezone rules have changed since then.
The Execution Lifecycle
- Object instantiation occurs in the Rhino JavaScript engine, where ServiceNow initializes the wrapper with the current user context, pulling timezone and locale settings from the session
- Date value assignment converts input strings or numbers to GMT using the current user's timezone settings, storing the result as milliseconds since epoch in the object's internal state
- Arithmetic operations manipulate the GMT timestamp directly using Java's temporal math libraries, ensuring precision across timezone boundaries and daylight saving transitions
- Display value generation applies timezone conversion using the active user context, formatting the GMT timestamp according to the user's locale and date format preferences
- Database persistence stores only the GMT timestamp in datetime fields, discarding the user context and timezone metadata to ensure consistent storage regardless of who created the record
Enjoying this? Get one deep-dive per week.
Join 1,000+ ServiceNow pros — scripts, GlideRecord patterns, Flow Designer techniques, and career moves. Free.
The Core Pattern
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
// Client-side can only pass date strings to server
var dueDateValue = g_form.getValue('due_date');
if (!dueDateValue) return;
// Client must use GlideAjax to trigger server-side GlideDateTime logic
var ga = new GlideAjax('IncidentDateUtils');
ga.addParam('sysparm_name', 'calculateEscalationDate');
ga.addParam('sysparm_due_date', dueDateValue);
ga.addParam('sysparm_priority', g_form.getValue('priority'));
ga.getXMLAnswer(function(answer) {
// Server returns formatted display value
if (answer) {
g_form.setValue('u_escalation_date', answer);
g_form.showInfoMessage('Escalation date set to: ' + answer);
}
});
}var IncidentDateUtils = Class.create();
IncidentDateUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {
calculateEscalationDate: function() {
// Server-side has full GlideDateTime capabilities
var dueDateStr = this.getParameter('sysparm_due_date');
var priority = this.getParameter('sysparm_priority');
// Create GlideDateTime from client-provided date string
var dueDate = new GlideDateTime();
dueDate.setDisplayValue(dueDateStr);
// Perform timezone-aware date arithmetic
var escalationHours = this._getEscalationHours(priority);
dueDate.addHours(escalationHours);
// Return formatted display value for client consumption
return dueDate.getDisplayValue();
},
_getEscalationHours: function(priority) {
return priority == '1' ? 2 : priority == '2' ? 8 : 24;
},
type: 'IncidentDateUtils'
});Real-World Scenarios
SLA Deadline Calculation Across Timezones
A global service desk needs to calculate 24-hour resolution deadlines for P1 incidents, accounting for the reporter's timezone and excluding weekends. The SLA must display correctly for both the New York-based reporter and the Mumbai-based resolver.
(function executeRule(current, previous /*null when async*/) {
if (current.priority == '1' && current.isNewRecord()) {
// Start with incident creation time in GMT
var startTime = new GlideDateTime(current.sys_created_on.getValue());
// Calculate 24-hour deadline, accounting for weekends
var deadline = new GlideDateTime(startTime);
deadline.addHours(24);
// Skip weekend days by checking day of week
while (deadline.getDayOfWeek() == 1 || deadline.getDayOfWeek() == 7) {
deadline.addDays(1);
}
// Set the deadline field - stored in GMT, displayed per user timezone
current.u_sla_deadline = deadline;
// Log for troubleshooting timezone issues
gs.info('SLA deadline set: GMT=' + deadline.getValue() + ', Display=' + deadline.getDisplayValue());
}
})(current, previous);Watch for daylight saving time transitions that can cause SLA deadlines to shift unexpectedly, especially for incidents created near DST boundaries. Always test SLA calculations with users in different timezones to verify display values match business expectations. The getDayOfWeek() method returns 1 for Sunday, which catches developers expecting Monday as day 1.
Scheduled Report Generation with User Context
A scheduled job generates monthly incident reports for regional managers, where each report must show data for their local business month boundaries. European managers need reports from 1st to 31st CET, while US managers need the same calendar month in EST/EDT.
// Iterate through regional managers from sys_user table
var managerGr = new GlideRecord('sys_user');
managerGr.addQuery('u_role', 'regional_manager');
managerGr.query();
while (managerGr.next()) {
// Create date range using manager's timezone context
var userTimeZone = managerGr.time_zone.toString();
// First day of current month in manager's timezone
var monthStart = new GlideDateTime();
monthStart.setDisplayValue(gs.beginningOfMonth(new GlideDateTime()));
// Last day of current month in manager's timezone
var monthEnd = new GlideDateTime();
monthEnd.setDisplayValue(gs.endOfMonth(new GlideDateTime()));
// Generate report with timezone-aware date filters
var reportData = this._generateIncidentReport(managerGr.sys_id, monthStart, monthEnd);
// Email uses display values appropriate for manager's timezone
this._emailReport(managerGr.email.toString(), reportData, monthStart.getDisplayValue());
}Scheduled jobs run in system context without a user timezone, so you must explicitly set timezone context or use setDisplayValueUTC() to avoid inconsistent date boundaries.
Data Import with Mixed Date Formats
An integration imports incident data from multiple external systems, each using different date formats and timezones. The transform script must normalize all dates to GMT storage while preserving the original timezone context for audit purposes.
// Handle various external date formats in transform script
var externalDate = source.incident_created_date; // From import source
var sourceTimezone = source.source_system_timezone || 'US/Eastern';
if (!gs.nil(externalDate)) {
var incidentDate = new GlideDateTime();
// Parse different date formats based on source system
if (source.source_system == 'system_a') {
// System A uses ISO format with timezone offset
incidentDate.setValue(externalDate);
} else if (source.source_system == 'system_b') {
// System B uses local date/time strings without timezone info
incidentDate.setDisplayValue(externalDate);
// Must manually adjust for known source timezone
incidentDate.addHours(this._getTimezoneOffset(sourceTimezone));
}
// Store normalized GMT value
target.opened_at = incidentDate.getValue();
// Preserve original for audit trail
target.u_original_date = externalDate;
target.u_source_timezone = sourceTimezone;
}External systems often send date strings without timezone indicators, making them dangerous to parse directly. Always validate date formats in your transform scripts and log parsing failures for troubleshooting. Consider building a lookup table for known external system timezone mappings rather than hardcoding assumptions about data sources.
The Classic Mistake
Comparing GlideDateTime objects directly with == or === operators instead of using comparison methods.
// Business Rule on incident table
function executeRule(current, previous) {
var assignedTime = new GlideDateTime(current.assigned.toString());
var openedTime = new GlideDateTime(current.opened_at.toString());
// This comparison NEVER works as expected
if (assignedTime == openedTime) {
gs.addInfoMessage('Times are equal');
return;
}
// This also fails silently
if (assignedTime > openedTime) {
current.work_notes = 'Assignment after opening confirmed';
}
}This fails because JavaScript compares object references, not the actual date/time values inside the GlideDateTime objects. ServiceNow creates new object instances each time, so assignedTime == openedTime always returns false, even for identical timestamps. You won't see browser console errors — the comparison just silently returns the wrong boolean value. The greater-than operator fails the same way, making time-based business logic completely unreliable.
// Business Rule on incident table
function executeRule(current, previous) {
var assignedTime = new GlideDateTime(current.assigned.toString());
var openedTime = new GlideDateTime(current.opened_at.toString());
// Use compareTo() for equality checks
if (assignedTime.compareTo(openedTime) == 0) {
gs.addInfoMessage('Times are equal');
return;
}
// Use after() and before() methods for ordering
if (assignedTime.after(openedTime)) {
current.work_notes = 'Assignment after opening confirmed';
}
}Never use ==, ===, <, or > with GlideDateTime objects. Always use compareTo(), equals(), before(), after(), and onOrBefore() methods.
Performance Rules
- Never call
new GlideDateTime()inside loops processing over 100 records. Each instantiation takes 2-3ms, causing Business Rules to timeout after 30 seconds and making UI Actions unresponsive. - Avoid
setDisplayValue()with user-entered strings in client scripts. Invalid date formats trigger server roundtrips for validation, adding 200-500ms delay per field that users notice as UI lag. - Cache
GlideDateTimeobjects when performing multiple operations on the same timestamp. Creating separate instances forgetDate(),getTime(), and formatting multiplies memory allocation unnecessarily. - Don't use
getDisplayValue()in loops over 50 iterations. Each call queries user timezone preferences and applies formatting rules, causing Script Includes to exceed execution limits and fail. - Minimize
setTZ()calls in data imports or bulk operations. Timezone conversion recalculates internal millisecond values, and processing 1000+ records triggers Java heap warnings in system logs. - Use
toString()instead ofgetDisplayValue()when storing values in variables or passing to web services. Display formatting adds 10-15ms overhead per call that accumulates in integration scenarios. - Avoid creating
GlideDateTimeobjects in ACL scripts. Access Control evaluations run on every record query, and date operations can slow list loading from 2 seconds to 15+ seconds with large datasets. - Don't call
subtract()oradd()repeatedly in real-time notifications or real-time Business Rules. Date arithmetic in high-frequency execution contexts causes memory leaks that require instance restarts.
Side Effects & Platform Behavior
- Creating
GlideDateTimeobjects in Business Rules triggers additional database queries to thesys_user_preferencetable to retrieve user timezone settings, visible in Debug Business Rules logs as 'Timezone lookup for user'. - Setting datetime fields programmatically bypasses client-side field validation rules but still triggers server-side Data Policies, creating inconsistent validation behavior between manual and automated updates.
- Date arithmetic operations create temporary objects in Java heap memory that don't get garbage collected until Business Rule execution completes, causing memory spikes visible in instance statistics.
- Using
GlideDateTimein Workflow conditions stores the evaluation results inwf_contextrecords, but timezone conversions aren't cached, causing different results when workflows resume after server restarts. - Audit records in
sys_auditstore datetime values in system timezone (GMT), not user timezone, so programmatic changes appear with different timestamps than manually entered changes in audit trails. - Client-side
GlideDateTimemethods in onChange scripts cause form submission delays because timezone conversion requires server communication, and users see spinning indicators during field changes. - Notifications using
${gs.nowDateTime}or similar datetime variables render in the recipient's timezone from their user record, but email headers always show server timezone, creating apparent time inconsistencies. - Import sets processing datetime fields automatically create
GlideDateTimetransform map entries insys_transform_entry, but these use system timezone assumptions that break when source data has explicit timezone indicators. - REST API responses automatically serialize
GlideDateTimefields to ISO format with timezone offset, but the offset reflects the API user's timezone preference, not the original data timezone, causing confusion in multi-timezone integrations. - Scheduled jobs using
GlideDateTimecalculations write execution details tosyslogtable with 'Timezone conversion' entries that accumulate rapidly and can exceed log retention policies in high-frequency jobs.
Debugging When It Breaks
Most GlideDateTime failures manifest as silent logic errors rather than explicit exceptions. Users report datetime-based Business Rules that 'sometimes work and sometimes don't,' while developers see conditions that should evaluate true but mysteriously return false. Client-side symptoms include form fields showing unexpected values after onChange events, or date fields reverting to previous values after user input.
Check System Log > All for 'Invalid date format' entries when datetime operations fail unexpectedly. These appear when setDisplayValue() receives malformed strings, but ServiceNow often continues execution with null values instead of throwing exceptions. Enable Debug Business Rules to see timezone lookup queries and conversion operations. Script Debugger shows GlideDateTime object contents as 'GMT: [timestamp] User: [timestamp]' pairs — mismatched values indicate timezone conversion problems.
Performance issues show up as Business Rule timeout warnings in System Log, or client scripts that hang with browser 'waiting for response' indicators. Import operations log 'Date conversion overhead' messages when processing large datasets. Look for 'Java heap' warnings coinciding with bulk datetime operations — these indicate memory leaks from uncached object creation.
Diagnostic checklist:
- Log
toString()values of allGlideDateTimeobjects before comparisons - Verify user timezone settings in
sys_user.time_zonefield - Test with system administrator account (GMT timezone) to isolate timezone issues
- Check if comparison operations use
==instead ofcompareTo() - Enable 'Log timing' in System Properties > Glide to track datetime operation performance
Quick Reference
- Server-side
GlideDateTimehandles timezone conversion automatically; client-side version requires manualsetTZ()calls - Use
getDisplayValue()for user-facing output,toString()for database storage and API responses - Constructor
new GlideDateTime()defaults to current timestamp; pass ISO string or GlideRecord field for specific times - Methods
add()andsubtract()modify the existing object; useclone()first to preserve original values - Comparison methods return integers:
compareTo()returns -1/0/1,equals()returns boolean - Format with
getByFormat('yyyy-MM-dd')for custom patterns; avoid deprecatedgetDate()method - Duration calculations:
getNumericValue()returns milliseconds since epoch for arithmetic operations - Timezone handling:
setTZ('US/Pacific')accepts standard timezone names, not abbreviations like PST/EST - Database storage always uses GMT internally; user timezone only affects display rendering and input parsing
- Default constructor behavior: server-side creates system time, client-side creates user's local time in their timezone