diff --git a/core/src/client/internal/jqueryDateFormat.test.ts b/core/src/client/internal/jqueryDateFormat.test.ts new file mode 100644 index 00000000000..3d302c520a4 --- /dev/null +++ b/core/src/client/internal/jqueryDateFormat.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2026 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +// jquery-dateFormat.js publishes its API onto a jQuery global, so stub one before loading it +const jq: any = {}; +(globalThis as any).jQuery = jq; +require('../../../webapp/internal/jQuery/jquery-dateFormat.js'); + +// parseDate() only reaches for these five members of a Date, so a stub lets us pin toTimeString() +// output without depending on the machine's timezone. +const dateStub = (timeString: string) => ({ + getFullYear: () => 2026, + getMonth: () => 6, + getDate: () => 27, + getMilliseconds: () => 0, + toTimeString: () => timeString, +}); + +describe('DateFormat.format.date', () => { + test('timezone label containing a colon keeps the time', () => { + // Firefox renders Pacific/Honolulu as 'GMT-10:00'; that colon used to split the time into four + // parts, so parseTime() bailed and the time came back as 00:00. + const honolulu = dateStub('08:00:00 GMT-1000 (GMT-10:00)'); + expect(jq.format.date(honolulu, 'HH:mm')).toBe('08:00'); + expect(jq.format.date(honolulu, 'yyyy-MM-dd HH:mm:ss')).toBe('2026-07-27 08:00:00'); + }); + + test('timezone label without a colon still works', () => { + expect(jq.format.date(dateStub('08:00:00 GMT-0700 (Pacific Daylight Time)'), 'HH:mm')).toBe('08:00'); + expect(jq.format.date(dateStub('13:45:09 GMT+0000 (Coordinated Universal Time)'), 'HH:mm:ss')).toBe('13:45:09'); + }); +}); diff --git a/core/webapp/internal/jQuery/jquery-dateFormat.js b/core/webapp/internal/jQuery/jquery-dateFormat.js index 36c5e8e3572..2e88eb4ca13 100644 --- a/core/webapp/internal/jQuery/jquery-dateFormat.js +++ b/core/webapp/internal/jQuery/jquery-dateFormat.js @@ -1,3 +1,15 @@ +/* + * jquery-dateFormat 18-05-2015 + * + * LabKey patch - this file differs from the upstream release. Re-apply on upgrade, and keep it in sync with + * jquery-dateFormat.min.js. + * + * parseTime() split the whole time string on ':', so a timezone label containing a colon (Firefox renders + * Pacific/Honolulu as 'GMT-10:00') yielded four parts instead of three and the time fell back to 00:00. The + * label is now stripped before the split - see the comment at that line below. + * + * Covered by core/src/client/internal/jqueryDateFormat.test.ts. + */ var DateFormat = {}; (function($) { @@ -65,7 +77,8 @@ var DateFormat = {}; millis = delimited[delimited.length - 1]; } - timeArray = time.split(':'); + // Strip trailing timezone label before splitting on ':' so a colon in the label (e.g. Honolulu's GMT-10:00) doesn't produce >3 parts and lose the time. + timeArray = time.replace(/\s.+/, '').split(':'); if(timeArray.length === 3) { hour = timeArray[0]; diff --git a/core/webapp/internal/jQuery/jquery-dateFormat.min.js b/core/webapp/internal/jQuery/jquery-dateFormat.min.js index effc6e67a52..5eba5d8fff6 100644 --- a/core/webapp/internal/jQuery/jquery-dateFormat.min.js +++ b/core/webapp/internal/jQuery/jquery-dateFormat.min.js @@ -1,2 +1,12 @@ /*! jquery-dateFormat 18-05-2015 */ -var DateFormat={};!function(a){var b=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],c=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],d=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],e=["January","February","March","April","May","June","July","August","September","October","November","December"],f={Jan:"01",Feb:"02",Mar:"03",Apr:"04",May:"05",Jun:"06",Jul:"07",Aug:"08",Sep:"09",Oct:"10",Nov:"11",Dec:"12"},g=/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.?\d{0,3}[Z\-+]?(\d{2}:?\d{2})?/;a.format=function(){function a(a){return b[parseInt(a,10)]||a}function h(a){return c[parseInt(a,10)]||a}function i(a){var b=parseInt(a,10)-1;return d[b]||a}function j(a){var b=parseInt(a,10)-1;return e[b]||a}function k(a){return f[a]||a}function l(a){var b,c,d,e,f,g=a,h="";return-1!==g.indexOf(".")&&(e=g.split("."),g=e[0],h=e[e.length-1]),f=g.split(":"),3===f.length?(b=f[0],c=f[1],d=f[2].replace(/\s.+/,"").replace(/[a-z]/gi,""),g=g.replace(/\s.+/,"").replace(/[a-z]/gi,""),{time:g,hour:b,minute:c,second:d,millis:h}):{time:"",hour:"",minute:"",second:"",millis:""}}function m(a,b){for(var c=b-String(a).length,d=0;c>d;d++)a="0"+a;return a}return{parseDate:function(a){var b,c,d={date:null,year:null,month:null,dayOfMonth:null,dayOfWeek:null,time:null};if("number"==typeof a)return this.parseDate(new Date(a));if("function"==typeof a.getFullYear)d.year=String(a.getFullYear()),d.month=String(a.getMonth()+1),d.dayOfMonth=String(a.getDate()),d.time=l(a.toTimeString()+"."+a.getMilliseconds());else if(-1!=a.search(g))b=a.split(/[T\+-]/),d.year=b[0],d.month=b[1],d.dayOfMonth=b[2],d.time=l(b[3].split(".")[0]);else switch(b=a.split(" "),6===b.length&&isNaN(b[5])&&(b[b.length]="()"),b.length){case 6:d.year=b[5],d.month=k(b[1]),d.dayOfMonth=b[2],d.time=l(b[3]);break;case 2:c=b[0].split("-"),d.year=c[0],d.month=c[1],d.dayOfMonth=c[2],d.time=l(b[1]);break;case 7:case 9:case 10:d.year=b[3],d.month=k(b[1]),d.dayOfMonth=b[2],d.time=l(b[4]);break;case 1:c=b[0].split(""),d.year=c[0]+c[1]+c[2]+c[3],d.month=c[5]+c[6],d.dayOfMonth=c[8]+c[9],d.time=l(c[13]+c[14]+c[15]+c[16]+c[17]+c[18]+c[19]+c[20]);break;default:return null}return d.date=d.time?new Date(d.year,d.month-1,d.dayOfMonth,d.time.hour,d.time.minute,d.time.second,d.time.millis):new Date(d.year,d.month-1,d.dayOfMonth),d.dayOfWeek=String(d.date.getDay()),d},date:function(b,c){try{var d=this.parseDate(b);if(null===d)return b;for(var e,f=d.year,g=d.month,k=d.dayOfMonth,l=d.dayOfWeek,n=d.time,o="",p="",q="",r=!1,s=0;s=12?"PM":"AM",o="";break;case"p":p+=n.hour>=12?"p.m.":"a.m.",o="";break;case"E":p+=h(l),o="";break;case"'":o="",r=!0;break;default:p+=t,o=""}}return p+=q}catch(w){return console&&console.log&&console.log(w),b}},prettyDate:function(a){var b,c,d;return("string"==typeof a||"number"==typeof a)&&(b=new Date(a)),"object"==typeof a&&(b=new Date(a.toString())),c=((new Date).getTime()-b.getTime())/1e3,d=Math.floor(c/86400),isNaN(d)||0>d?void 0:60>c?"just now":120>c?"1 minute ago":3600>c?Math.floor(c/60)+" minutes ago":7200>c?"1 hour ago":86400>c?Math.floor(c/3600)+" hours ago":1===d?"Yesterday":7>d?d+" days ago":31>d?Math.ceil(d/7)+" weeks ago":d>=31?"more than 5 weeks ago":void 0},toBrowserTimeZone:function(a,b){return this.date(new Date(a),b||"MM/dd/yyyy HH:mm:ss")}}}()}(DateFormat),function(a){a.format=DateFormat.format}(jQuery); \ No newline at end of file +/* + * LabKey patch - this file differs from the upstream release. Re-apply on upgrade, and keep it in sync with + * jquery-dateFormat.js. + * + * parseTime() split the whole time string on ':', so a timezone label containing a colon (Firefox renders + * Pacific/Honolulu as 'GMT-10:00') yielded four parts instead of three and the time fell back to 00:00. The + * label is now stripped before the split: time.replace(/\s.+/, '').split(':'). + * + * Covered by core/src/client/internal/jqueryDateFormat.test.ts. + */ +var DateFormat={};!function(a){var b=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],c=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],d=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],e=["January","February","March","April","May","June","July","August","September","October","November","December"],f={Jan:"01",Feb:"02",Mar:"03",Apr:"04",May:"05",Jun:"06",Jul:"07",Aug:"08",Sep:"09",Oct:"10",Nov:"11",Dec:"12"},g=/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.?\d{0,3}[Z\-+]?(\d{2}:?\d{2})?/;a.format=function(){function a(a){return b[parseInt(a,10)]||a}function h(a){return c[parseInt(a,10)]||a}function i(a){var b=parseInt(a,10)-1;return d[b]||a}function j(a){var b=parseInt(a,10)-1;return e[b]||a}function k(a){return f[a]||a}function l(a){var b,c,d,e,f,g=a,h="";return-1!==g.indexOf(".")&&(e=g.split("."),g=e[0],h=e[e.length-1]),f=g.replace(/\s.+/,"").split(":"),3===f.length?(b=f[0],c=f[1],d=f[2].replace(/\s.+/,"").replace(/[a-z]/gi,""),g=g.replace(/\s.+/,"").replace(/[a-z]/gi,""),{time:g,hour:b,minute:c,second:d,millis:h}):{time:"",hour:"",minute:"",second:"",millis:""}}function m(a,b){for(var c=b-String(a).length,d=0;c>d;d++)a="0"+a;return a}return{parseDate:function(a){var b,c,d={date:null,year:null,month:null,dayOfMonth:null,dayOfWeek:null,time:null};if("number"==typeof a)return this.parseDate(new Date(a));if("function"==typeof a.getFullYear)d.year=String(a.getFullYear()),d.month=String(a.getMonth()+1),d.dayOfMonth=String(a.getDate()),d.time=l(a.toTimeString()+"."+a.getMilliseconds());else if(-1!=a.search(g))b=a.split(/[T\+-]/),d.year=b[0],d.month=b[1],d.dayOfMonth=b[2],d.time=l(b[3].split(".")[0]);else switch(b=a.split(" "),6===b.length&&isNaN(b[5])&&(b[b.length]="()"),b.length){case 6:d.year=b[5],d.month=k(b[1]),d.dayOfMonth=b[2],d.time=l(b[3]);break;case 2:c=b[0].split("-"),d.year=c[0],d.month=c[1],d.dayOfMonth=c[2],d.time=l(b[1]);break;case 7:case 9:case 10:d.year=b[3],d.month=k(b[1]),d.dayOfMonth=b[2],d.time=l(b[4]);break;case 1:c=b[0].split(""),d.year=c[0]+c[1]+c[2]+c[3],d.month=c[5]+c[6],d.dayOfMonth=c[8]+c[9],d.time=l(c[13]+c[14]+c[15]+c[16]+c[17]+c[18]+c[19]+c[20]);break;default:return null}return d.date=d.time?new Date(d.year,d.month-1,d.dayOfMonth,d.time.hour,d.time.minute,d.time.second,d.time.millis):new Date(d.year,d.month-1,d.dayOfMonth),d.dayOfWeek=String(d.date.getDay()),d},date:function(b,c){try{var d=this.parseDate(b);if(null===d)return b;for(var e,f=d.year,g=d.month,k=d.dayOfMonth,l=d.dayOfWeek,n=d.time,o="",p="",q="",r=!1,s=0;s=12?"PM":"AM",o="";break;case"p":p+=n.hour>=12?"p.m.":"a.m.",o="";break;case"E":p+=h(l),o="";break;case"'":o="",r=!0;break;default:p+=t,o=""}}return p+=q}catch(w){return console&&console.log&&console.log(w),b}},prettyDate:function(a){var b,c,d;return("string"==typeof a||"number"==typeof a)&&(b=new Date(a)),"object"==typeof a&&(b=new Date(a.toString())),c=((new Date).getTime()-b.getTime())/1e3,d=Math.floor(c/86400),isNaN(d)||0>d?void 0:60>c?"just now":120>c?"1 minute ago":3600>c?Math.floor(c/60)+" minutes ago":7200>c?"1 hour ago":86400>c?Math.floor(c/3600)+" hours ago":1===d?"Yesterday":7>d?d+" days ago":31>d?Math.ceil(d/7)+" weeks ago":d>=31?"more than 5 weeks ago":void 0},toBrowserTimeZone:function(a,b){return this.date(new Date(a),b||"MM/dd/yyyy HH:mm:ss")}}}()}(DateFormat),function(a){a.format=DateFormat.format}(jQuery); \ No newline at end of file