- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
05-18-2020 09:36 AM
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.
Solved! Go to Solution.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
05-19-2020 03:08 PM
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.
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.
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
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.
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:
Test | Entered | See After Submit | |
Same Timezone | 2020-05-19 00:00:00 PDT (-0700) | 2020-05-19 00:00:00 PDT (-0700) | |
Standard Time during Daylight Savings | 2020-05-19 00:00:00 PST (-0800) | 2020-05-19 01:00:00 PDT (-0700) | |
India to Pacific | 2020-05-19 00:00:00 IST (+0530) | 2020-05-18 11:30:00 PDT (-0700) | |
Central Europe to Pacific | 2020-05-19 00:00:00 CEST (+0200) | 2020-05-18 15:00:00 PDT (-0700) | |
China to Pacific | 2020-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 Pacific | 2020-05-19 00:00:00 WET (+0000) | 2020-05-18 17:00:00 PDT (-0700) | |
No Offset Negative to Pacific | 2020-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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
06-12-2020 10:25 AM
I've noticed issues when hitting validation which uses getDateFromFormat
getDateFromFormat has a bug here:
...
} else if (token == "z")
i_val += 3;
else {
...
"z" is the token for timezone. It makes the assumption all timezones are 3 characters which is false.
A workaround for this could be to remove the " z (ZZ)" portion of the format and value before being passed into getDateFromFormat. Preferably ServiceNow would provide a way to fix this error in the code.
It also doesn't support "ZZ".
I'm planning to reach out to ServiceNow to see if they have any recommendations.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
08-12-2021 01:42 PM
I had this same problem over a year, and Servicenow includes a timezone table. I used that to add the offset to the date/time. I'll see if I can post the code.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
12-13-2021 02:11 PM
We also have this problem too, and using "z" in the system time would have been a great solution for us but we found that the scheduler will break because it doesn't translate the time with the appended timezone. 😞