How do I allow users to see/enter timezone into Date/Time Fields?

Mathieu8
Kilo Guru

Hi Community,

We have an international company. User's are often booking meetings, support, and requests with people across many timezones.

User's sometimes have their Laptop's timezone set to our corporate office, their tablet timezone set to their home location, and their phone timezone to where ever they are at this moment. This means on some occasions every device is telling them a different time in ServiceNow, and every device needs to enter a different time when filling out forms.

The ServiceNow UI and Portal are incredibly confusing for people to fill out without timezone being displayed.

Ideally, the inputs would display "01-01-2020 14:00:00 PST" or "01-01-2020 17:00:00 EST". I know time format is only documented to permit the letters h/H/m/s, however knowing it was based off Java's SimpleDateFormat I thought I would try adding a timezone.

I changed my personal time format to "HH:mm:ss z" and this worked great for me. I could see my timezone and even save values other people would give me in their timezone and things worked flawlessly for me.

However, somewhat expectedly, this didn't continue to work. While it worked for PDT/PST/EST/EDT/IST/JST/etc things failed when I had one of my European coworkers try with the timezone CEST which would throw an error of an invalid date.

Alternatively I attempted to try using the format "HH:mm:ss 'GMT'ZZ" for "01-01-2020 14:00:00 GMT-0700", but this format doesn't work at all.

I was guessing that the momentjs library did the validation, so I was testing formats that both looked good and were compatible with momentjs. I decided on the format "MM-DD-YYYY HH:mm:ss [GMT]ZZ z" for "01-01-2020 14:00:00 GMT-0700 PST" but while compatible with momentjs, it was not compatible with ServiceNow.

Now were getting into some wild design decisions where we're saving the value the user entered, having them select the timezone they want to save the date/times in, and then converting the value to be saved in another column, all because the Date/Time input seemingly cannot accept certain timezones as input.

What can I do to make the Date/Time input function for our users?

Thanks.

1 ACCEPTED SOLUTION

I've spent about 2 hours on this and this is what I currently have. I'm wondering how I could go about writing tests for this. From my initial testing things appear to be working except for some EXTREME edge cases that are unlikely to happen.

Implementation

1. Under UI Scripts I've added moment.js (https://momentjs.com/downloads/moment.min.js)

Set it as a global script.

find_real_file.png

2. Under UI Scripts I've added moment-timezone.js (https://momentjs.com/downloads/moment-timezone-with-data-1970-2030.min.js)

Set it as a global script.

find_real_file.png

3.Under System Properties I've changed Time format ('glide.sys.time_format') to "HH:mm:ss z (ZZ)".

https://<YOUR_INSTANCE>.service-now.com/nav_to.do?uri=%2Fsystem_properties_ui.do%3Fsysparm_category%3DSystem%26sysparm_title%3DSystem%2520Properties

find_real_file.png

 

4. Change the Validation Script

https://<YOUR_INSTANCE>.service-now.com/nav_to.do?uri=sys_script_validator.do?sys_id=829b0bb0533131008d7d154f811c08f7

function validate(value) {
    // empty fields are still valid dates 
    if (!value) 
        return true; 

    // We "should" have the global date format defined always defined. but there's always that edge case...
    if (typeof g_user_date_time_format !== 'undefined') {
		
		//Check if format is "{{DATE}} {{TIME}} {{TIMEZONE}} ({{OFFSET}})"
		var dateTimeParts = g_user_date_time_format.split(' ');
		
		if (dateTimeParts.length === 4 && dateTimeParts[2] === 'z' && dateTimeParts[3] === "(ZZ)") {
			var valueDateTimeParts = value.split(' ');
			
			//There must be 4 parts to check.
			if (valueDateTimeParts.length === 4) {
				
				//Check the first two parts exactly like they were before
				if (!isDate(valueDateTimeParts[0] + ' ' + valueDateTimeParts[1], dateTimeParts[0] + ' ' + dateTimeParts[1])) {
					return getMessage("Invalid Date");
				}
				
				//Check if Date gets a valid date with this value
				var dateTimeVal = new Date(valueDateTimeParts[0] + ' ' + valueDateTimeParts[1] + ' ' + valueDateTimeParts[3]).getTime();
				if (isNaN(dateTimeVal)) {
					return getMessage("Invalid Date");
				}
				
				//Check if the timezone and offset are valid
				if (!isValidTimezone(valueDateTimeParts[2], valueDateTimeParts[3])) {
					return getMessage("Invalid Date");
				}
				
				return true;
				
			} else {
				return getMessage("Invalid Date");
			}
			
		} else {
			return isDate(value, g_user_date_time_format) ? true : getMessage("Invalid Date"); 
		}
		
	}

    // if we don't have that defined, we can always try guessing 
    return parseDate(value) !== null ? true : getMessage("Invalid Date"); 
	
}

function isValidTimezone(abbr, offset){
	//Offset should be the format (+0000)
	if(offset.length !== 7) return false;
	
	//remove brackets get each character
	offset = offset.substr(1,5).split('');
	
	//If it's a '-' it should be a positive number, if it's a + it should be negative
	//The first 2 digits are the number of hours for the offset
	//The second 2 digits are the number of minutes for the offset
	var offsetVal = (offset[0] === '-' ? 1 : -1) * (((offset[1]+offset[2]) * 60) + parseInt(offset[3]+offset[4]));
	
	return moment.tz.names()
		//Convert to list of Timezone Objects
		.map(function(z){ return moment.tz.zone(z); }) 
		//Check if the timezone exists at all
		.filter(function(tz){
			//The abbreviation and offset must both exist and be the same index
			var abbrsInd = tz.abbrs.indexOf(abbr);
			return  abbrsInd !== -1 && abbrsInd === tz.offsets.indexOf(offsetVal); 
		}) 
		.length; //return the number of valid results
	
}

 

Problems Considered

Here were some of my considerations.

CST = Central Standard Time (-0600), China Standard Time (+0800), Cuba Standard Time (-0500).

Similar problems exist for ECT, IST, MST, PST, etc.

Clearly the timezone abbreviation alone will sadly not be sufficient for a worldwide solution.

This lead me to design requiring the abbreviation (eg. PST) and the offset (eg. -0800). This helps both in readability for the user and explicitness for the data.

This means if you give an abbreviation of PST and offset of -0700, you will get an error.

find_real_file.png

Since PST's offset is -0800 for Pacific Standard Time, or +0800 for Philippine Standard time. Either -0800 or +0800 would not result in an error since either are valid values.

 

What is Validated

If a user doesn't have " z (ZZ)" on their time format setting, things work as they did before.

If a user is using " z (ZZ)" the script will execute 3 checks.

1. Remove the timezone and offset and do the original check it would have done.

2. Add back in the offset and check if JavaScript's Date function can understand the input. This doesn't include the abbreviation since many abbreviations will cause JavaScript's Date function to fail.

3. Use moment library to ensure the abbreviation and offset are valid values.

 

What I've manually tested

I've tested a handful of timezones and things appear to enter correctly and translate correctly to my timezone. Both within the tool and through the support portal. 

I'm in the Pacific Timezone, so this means when I enter:

TestEnteredSee After Submit 
Same Timezone2020-05-19 00:00:00 PDT (-0700)2020-05-19 00:00:00 PDT (-0700) 
Standard Time during Daylight Savings2020-05-19 00:00:00 PST (-0800)2020-05-19 01:00:00 PDT (-0700) 
India to Pacific2020-05-19 00:00:00 IST (+0530)2020-05-18 11:30:00 PDT (-0700) 
Central Europe to Pacific2020-05-19 00:00:00 CEST (+0200)2020-05-18 15:00:00 PDT (-0700) 
China to Pacific2020-05-19 00:00:00 CST (+0800)2020-05-18 09:00:00 PDT (-0700) 
Easter Island to Pacfic (Easter Island's timezone does not have a name)2020-05-19 00:00:00 -05 (-0500)2020-05-19 00:00:00 PDT (-0700)Failed
Kiritimati to Pacific (Kiritimati's timezone does not have a name)2020-05-19 00:00:00 +14 (+1400) 2020-05-19 00:00:00 PDT (-0700)Failed
No Offset Positive to Pacific2020-05-19 00:00:00 WET (+0000) 2020-05-18 17:00:00 PDT (-0700) 
No Offset Negative to Pacific2020-05-19 00:00:00 WET (-0000) 2020-05-18 17:00:00 PDT (-0700) 
Chatham to Pacific (Chatham's timezone does not have a name)2020-05-19 00:00:00 +1345 (+1345) 2020-05-18 03:15:00 PDT (-0700) 
Enderbury to Pacfic (Enderbury's timezone does not have a name)2020-05-19 00:00:00 -12 (-1200) 2020-05-19 00:00:00 PDT (-0700)Failed

During my tests I realized a few things.

1. If a timezone has no name, this gets very messy. This situation is unlikely, but problematic. From what I can tell, JavaScript can handle this by ignoring the name and using only the offset, but Java's SimpleDateFormat isn't prepared for names like -05 or +14. It happens to understand +1345 because it thinks it's an offset, not a name. These timezones aren't even selectable on OSX, so I'm not sure if these issues can even happen. There is about 7.6 billion people on earth, and about 50,000 live in a timezone without a name.

2. Timezone's change frequently. Moment-timezone should be updated regularly either automatically or manually or referenced externally.

 

Concerns

1. I wish I could figure out a way to test both the UI for every timezone possible and the DB.

2. Changing Timezone Data

3. The possibility exists that a user doesn't have a timezone. This is both extremely rare and possibly the problem is already solved by their device's OS choosing a timezone. I suspect these users are used to dealing with these issues.

 

Overall I'm very happy with this. I plan to do more testing and see what other issues may exist. But I am optimistic. 

 

If anyone has concerns about my approach or test coverage. Please let me know your thoughts.

View solution in original post

12 REPLIES 12

DrewW
Mega Sage
Mega Sage

When you say it did not work are you saying it displayed properly but did not pass validation?  If its just validation you should check the date validation script to see if tweeking that will allow it to work.

 

Yes, it did display properly but did not pass validation.

Please correct me if I'm wrong, but I don't think I have access to make changes to how ServiceNow validates Date/Time fields.

I'm reasonably certain this is client side validation. I get the error "Invalid Date" with no Client Scripts on the form.

I had hoped they used MomentJS or JavaScript's Date functions for validation. But that doesn't appear to be correct.

new Date("05-17-2020 00:00:00 GMT+0200 (CEST)"); //Valid
moment("05-17-2020 00:00:00 GMT+0200 (CEST)"); //valid

find_real_file.png

If memory serves this is the one it uses for all date/time fields
https://<YOUR_INSTANCE>.service-now.com/nav_to.do?uri=%2Fsys_script_validator.do%3Fsys_id%3D829b0bb0533131008d7d154f811c08f7%26sysparm_record_target%3Dsys_script_validator%26sysparm_record_row%3D6%26sysparm_record_rows%3D22%26sysparm_record_list%3DORDERBYinternal_type

If that does not open look under the Validation Scripts (sys_script_validator).

Wow, thanks Drew. I think I can make this work. This is great.