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

  1. 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
  2. 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
  3. Arithmetic operations manipulate the GMT timestamp directly using Java's temporal math libraries, ensuring precision across timezone boundaries and daylight saving transitions
  4. 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
  5. 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
Free Newsletter

Enjoying this? Get one deep-dive per week.

Join 1,000+ ServiceNow pros — scripts, GlideRecord patterns, Flow Designer techniques, and career moves. Free.

No spam · Unsubscribe anytime

The Core Pattern

Client Script — Incident Form.js
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);
        }
    });
}
Script Include — IncidentDateUtils.js
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.

Business Rule — Incident SLA Calculator.js
(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.

Scheduled Script Execution — Monthly Reports.js
// 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.

Transform Script — External Incident Import.js
// 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.

Anti-pattern — Do Not Use This.js
// 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.

The Fix.js
// 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

  1. 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.
  2. 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.
  3. Cache GlideDateTime objects when performing multiple operations on the same timestamp. Creating separate instances for getDate(), getTime(), and formatting multiplies memory allocation unnecessarily.
  4. 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.
  5. 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.
  6. Use toString() instead of getDisplayValue() when storing values in variables or passing to web services. Display formatting adds 10-15ms overhead per call that accumulates in integration scenarios.
  7. Avoid creating GlideDateTime objects 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.
  8. Don't call subtract() or add() 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 GlideDateTime objects in Business Rules triggers additional database queries to the sys_user_preference table 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 GlideDateTime in Workflow conditions stores the evaluation results in wf_context records, but timezone conversions aren't cached, causing different results when workflows resume after server restarts.
  • Audit records in sys_audit store 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 GlideDateTime methods 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 GlideDateTime transform map entries in sys_transform_entry, but these use system timezone assumptions that break when source data has explicit timezone indicators.
  • REST API responses automatically serialize GlideDateTime fields 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 GlideDateTime calculations write execution details to syslog table 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 all GlideDateTime objects before comparisons
  • Verify user timezone settings in sys_user.time_zone field
  • Test with system administrator account (GMT timezone) to isolate timezone issues
  • Check if comparison operations use == instead of compareTo()
  • Enable 'Log timing' in System Properties > Glide to track datetime operation performance

Quick Reference

  • Server-side GlideDateTime handles timezone conversion automatically; client-side version requires manual setTZ() 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() and subtract() modify the existing object; use clone() 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 deprecated getDate() 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