Working with Time Zones Using Moment.js and Moment Timezone - ben-vargas/servicenow-wiki GitHub Wiki
This article explains how to leverage the Moment.js and Moment Timezone libraries within ServiceNow to simplify time zone conversions and formatting, particularly when dealing with complex date/time manipulations and varied output requirements. These libraries are crucial for building robust and user-friendly applications that span multiple time zones.
The Challenge:
ServiceNow's built-in date and time handling can become complex when working with different time zones and various display formats. Manually handling conversions and formatting can be tedious, error-prone, and lead to inconsistencies. These tasks can be made significantly easier by leveraging the power of Moment.js and Moment Timezone.
The Solution:
Moment.js and Moment Timezone are powerful JavaScript libraries that greatly simplify date and time manipulation, formatting, and time zone conversions. This article will guide you through using these libraries in ServiceNow, providing best practices, examples, and important considerations.
Implementation Steps:
This solution involves the following:
- Include the Necessary Script Includes: Ensure the
moment.min.js
andmoment-timezone.js
script includes (with data) are installed in your ServiceNow instance. - Utilize the Moment API: Use the Moment.js API within your server-side or client-side scripts to perform time zone conversions and date formatting.
Code Snippets:
-
Including the Libraries:
gs.include('moment.min.js'); // For basic date manipulation and formatting. gs.include('moment-timezone-with-data.min.js'); // For timezone conversions.
- These lines should be placed at the beginning of your script to load the libraries.
-
Basic Time Zone Conversion:
var opened_at_utc = moment.tz(current.opened_at.toString(), "YYYY-MM-DD HH:mm:ss","UTC"); var opened_at_los_angeles = opened_at_utc.clone().tz("America/Los_Angeles"); var formatted_date_time = opened_at_los_angeles.format("YYYY-MM-DD HH:mm:ss z"); gs.info('Original Date/Time (UTC): ' + opened_at_utc.format("YYYY-MM-DD HH:mm:ss z")); gs.info('Converted Date/Time (Los Angeles): ' + formatted_date_time);
- Explanation:
moment.tz(..., "YYYY-MM-DD HH:mm:ss","UTC")
parses the date string from theopened_at
field of the current record, using the provided format, and sets the timezone toUTC
.opened_at_utc.clone()
creates a copy of the moment object to avoid modifications of the original object.tz("America/Los_Angeles")
converts the date to the Los Angeles time zone.format("YYYY-MM-DD HH:mm:ss z")
formats the date and time to the formatYYYY-MM-DD HH:mm:ss
along with the timezone abbreviation.- You should always specify an input format when parsing values that are passed as strings.
- Explanation:
-
Client Side Example
-
Often you may want to do date conversions client side. This can be done using GlideAjax and an API to do the conversion.
-
Script Include Code:
var HSMomentTimeZoneUtilAJAX = Class.create(); HSMomentTimeZoneUtilAJAX.prototype = Object.extendsObject(AbstractAjaxProcessor, { convertTime: function() { var TAG = '[SI: HSMomentTimeZoneUtilAJAX.convertTime] '; var log = new GSLog('hs.logging.verbosity', TAG.trim()); // Use Moment.js and Moment Timezone for the conversions. gs.include('moment.min.js'); gs.include('moment-timezone-with-data.min.js'); var returnObject = { sourceDateTime: this.getParameter('sysparm_source_date_time') + '', sourceTimeZone: this.getParameter('sysparm_source_timezone') + '', targetTimeZone: this.getParameter('sysparm_target_timezone') + '', targetFormat: this.getParameter('sysparm_target_format') + '', targetDateTime: '' }; log.logDebug(TAG + 'before moment.tz returnObject: ' + JSON.stringify(returnObject)); // Convert Source Time & TimeZone to Target TimeZone returnObject.targetDateTime = moment .tz(returnObject.sourceDateTime, "YYYY-MM-DD HH:mm:ss", returnObject.sourceTimeZone) .tz(returnObject.targetTimeZone) .format(returnObject.targetFormat); log.logDebug(TAG + 'after moment.tz returnObject: ' + JSON.stringify(returnObject)); var returnJSON = JSON.stringify(returnObject); return returnJSON; }, type: 'HSMomentTimeZoneUtilAJAX' });
- This Script Include is client callable and returns the source date/time, source time zone, target time zone, and target date/time as a JSON object.
-
Client Side Script Example
function onChange(control, oldValue, newValue, isLoading) { if (newValue == '' || !newValue) { g_form.clearValue('date_of_access_work_localized'); } //Get value of the field we're converting; this is in the browser's time zone which complicates, not UTC yet. var sourceDateTime = g_form.getValue('date_of_access_work'); //Client side, could get this server side in GlideAjax, but this works as well (time zone from user browser) var sourceTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; //console.log('pune_date_of_access_work: ' + sourceDateTime + ' ' + sourceTimeZone); var ga = new GlideAjax('HSMomentTimeZoneUtilAJAX'); ga.addParam('sysparm_name', 'convertTime'); ga.addParam('sysparm_source_date_time', sourceDateTime); ga.addParam('sysparm_source_timezone', sourceTimeZone); ga.addParam('sysparm_target_timezone', 'Europe/Dublin'); ga.addParam('sysparm_target_format', 'YYYY-MM-DD HH:mm:ss z'); /** ** var returnJSON = { ** sourceDateTime: [Str] Original (source) dateTime passed (sysparm_source_date_time), ** sourceTimeZone: [Str] Original (source) TimeZone String identified from browser (sysparm_source_timezone), ** targetTimeZone: [Str] Targeted time zone to convert to, (sysparm_target_timezone), ** targetFormat: [Str] Target date/time format, set 'YYYY-MM-DD HH:mm:ss z' above (sysparm_target_format), ** targetDateTime: [Str] Converted date/time in targetFormat" ** }; */ ga.getXMLAnswer(function(returnJSON) { if (returnJSON) { var returnObject = JSON.parse(returnJSON); //console.log('returnJSON [client side]: ' + returnJSON); g_form.setValue('date_of_access_work_localized', returnObject.targetDateTime); } }); }
- This Client Script uses
GlideAjax
to call a server-side script that usesmoment.js
andmoment-timezone.js
to convert a date/time between the user's local time zone and the target time zone ofEurope/Dublin
, storing the value in the fielddate_of_access_work_localized
. - You can use this code as a template to convert dates between any time zones.
- This method is useful for when data needs to be displayed in a specific users local time zone, or when working with integration to systems that require a particular time zone.
- This Client Script uses
-
-
Formatting Date and Time:
var gdt = moment.tz(current.opened_at.toString(), "YYYY-MM-DD HH:mm:ss","UTC"); gs.info("Date/Time (Default Format) : " + gdt.format()); gs.info("Date/Time (ISO Format) : " + gdt.format('YYYY-MM-DDTHH:mm:ssZ')); gs.info("Date/Time (Custom Format) : " + gdt.format("MMMM DD, YYYY h:mm A z")); gs.info("Date/Time (Custom Format 2) : " + gdt.format("ddd MMM D, YYYY h:mm A z")); gs.info("Date/Time (No Time) : " + gdt.format('YYYY-MM-DD'));
- Explanation:
-
The examples show the use of format to convert the date/time to various formats. The output would look like:
Date/Time (Default Format) : 2024-08-07T05:08:00-07:00 Date/Time (ISO Format) : 2024-08-07T12:08:00Z Date/Time (Custom Format) : August 07, 2024 12:08 PM GMT-07:00 Date/Time (Custom Format 2) : Wed Aug 7, 2024 12:08 PM GMT-07:00 Date/Time (No Time) : 2024-08-07
-
- Explanation:
-
Working with Time Durations:
var startTime = moment.tz("2024-01-01 10:00:00", "YYYY-MM-DD HH:mm:ss", "America/Los_Angeles"); var endTime = moment.tz("2024-01-01 12:30:00", "YYYY-MM-DD HH:mm:ss", "America/Los_Angeles"); var duration = moment.duration(endTime.diff(startTime)); var hours = duration.asHours(); var minutes = duration.asMinutes() % 60; gs.info("Time Difference: " + hours + " Hour(s) " + minutes + " Minute(s)");
- Explanation:
moment.duration(endTime.diff(startTime))
computes the duration (difference) between two moments.duration.asHours()
andduration.asMinutes()
provide the duration in hours and minutes.
- Use Case: This is useful for calculating the difference between two dates in a user friendly format.
- Explanation:
-
Business Rule Example:
- This example shows how to use moment.js within a Business rule, specifically on the change request table.
(function executeRule(current, previous /*null when async*/) {
// Use Moment.js and Moment Timezone for the conversions.
gs.include('moment.min.js');
gs.include('moment-timezone-with-data.min.js');
// If the 'Planned end date' is empty then time zone conversions be as well.
if (current.end_date.nil()) {
current.u_kickoff_location_planned_end = '';
current.u_implementer_planned_end = '';
return;
}
var date_timeUTC = moment.tz(current.end_date.toString(), "YYYY-MM-DD HH:mm:ss","UTC");
// Calculate 'Kickoff location planned end date' string.
if (!current.u_kickoff_location.time_zone.nil()) {
var kickoff_end = date_timeUTC.clone().tz(current.u_kickoff_location.time_zone.toString());
current.u_kickoff_location_planned_end = kickoff_end.format("YYYY-MM-DD HH:mm:ss z");
}
else {
current.u_kickoff_location_planned_end = "No 'Kickoff location' time zone.";
}
// Calculate 'Implementer planned starte date' string.
if (!current.assigned_to.u_support_location.time_zone.nil()) {
var implementer_end_date = date_timeUTC.clone().tz(current.assigned_to.u_support_location.time_zone.toString());
current.u_implementer_planned_end = implementer_end_date.format("YYYY-MM-DD HH:mm:ss z");
}
else {
current.u_implementer_planned_end = "No 'Implementer location' time zone.";
}
})(current, previous);
- This business rule is set to run on changes to the
end_date
,u_kickoff_location
, orassigned_to
fields. - It is also converting the
end_date
field into two separate fields which contain theend_date
as a string in a specific time zone. - This example can be replicated by using a client side script that uses a glideajax call to retrieve the values.
How to Implement
- Include Libraries: Make sure that both
moment.min.js
andmoment-timezone-with-data.min.js
are uploaded and configured as Script Includes in your ServiceNow instance. The moment timezone script include should include the time zone data. - Add to Script: Add the library includes to your business rules, server-side scripts, client scripts (via Script Includes/GlideAjax), and UI actions.
- Code Examples: Use the provided code examples as templates and adapt them to your specific needs. Remember to replace any placeholder values.
- Test: Test your code in a non-production environment to ensure it works as expected and handles different time zones correctly.
Best Practices
- Always Include Libraries: Make sure to always include both libraries if you need to handle time zone data.
- Specify Input Format: Always specify the format of the date string you are parsing to ensure the script correctly interprets the value, and does not use the browser to perform the parse.
- UTC as Default: If dealing with multiple time zones, It's a best practice to handle your dates in UTC and then convert them to local times, or specific time zones as required.
- Use
clone()
: Always clone your moment object before performing time zone conversions to avoid unintended side effects on the original variable. - Clear Formatting: Use
.format()
to output dates in user-friendly formats, with time zones included (e.g.,"YYYY-MM-DD HH:mm:ss z"
). - Consistent Time Zone Handling: If you are working with a user's time zone, consider storing the user's time zone in the users record to ensure consistency.
- Error Handling: Use a try/catch when working with dates from outside sources, if possible. This will help to prevent errors due to invalid date strings being passed to the scripts.
- Performance: While these libraries are efficient, be aware of the performance implications when processing a large number of date conversions. If needed, utilize server-side scripts to do the calculations rather than client-side where possible.
Use Cases
- Global Applications: When your application is used across different time zones, Moment Timezone will provide a standardized way to handle this complexity.
- Time Zone-Specific Logic: When different actions need to be taken based on the time zone of the user or a record, these libraries provide the ability to easily determine this.
- Integration with External Systems: When integrations are sending data to your instance, you can use the libraries to handle different time zones when creating, updating, or reading data.
- User Interface Enhancements: Presenting dates and times in a localized format enhances the user experience by displaying times and dates that match their time zone.
Conclusion
Moment.js and Moment Timezone provide the tools necessary to properly manage dates and time zones in ServiceNow. By using these libraries, you can simplify complex date-related logic, ensure data integrity, and create a more seamless user experience. Remember to always test all code thoroughly in a non-production environment before deploying to production.