From aee3476af11a06d4810deb72eef685b42ede4d34 Mon Sep 17 00:00:00 2001 From: ankurjuneja Date: Mon, 27 Jul 2026 13:40:07 -0700 Subject: [PATCH 1/2] Fix jquery-dateFormat parseTime dropping the time in colon-labelled timezones --- .../labkey/targetedms/view/instrumentCalendar.jsp | 5 +++-- .../tests/targetedms/InstrumentSchedulingTest.java | 3 +++ webapp/TargetedMS/js/scheduleUtils.js | 13 +++++++++++++ webapp/TargetedMS/js/scheduler.js | 7 +++++-- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/org/labkey/targetedms/view/instrumentCalendar.jsp b/src/org/labkey/targetedms/view/instrumentCalendar.jsp index 2e6c35d1a..6fe5b5e73 100644 --- a/src/org/labkey/targetedms/view/instrumentCalendar.jsp +++ b/src/org/labkey/targetedms/view/instrumentCalendar.jsp @@ -23,6 +23,7 @@ { dependencies.add("internal/jQuery"); dependencies.add("targetedms/yearCalendar"); + dependencies.add("TargetedMS/js/scheduleUtils.js"); } %> @@ -51,8 +52,8 @@ $('#delete-event').css('display', event.annotation ? '' : 'none'); $('#event-modal input[name="event-description"]').val(event.annotation ? event.annotation.description : ''); - $('#event-modal input[name="event-start-date"]').val(startDate.getFullYear() + '-' + (startDate.getMonth() + 1 < 10 ? '0' : '') + (startDate.getMonth() + 1) + '-' + (startDate.getDate() < 10 ? '0' : '') + startDate.getDate()); - $('#event-modal input[name="event-end-date"]').val(endDate.getFullYear() + '-' + (endDate.getMonth() + 1 < 10 ? '0' : '') + (endDate.getMonth() + 1) + '-' + (endDate.getDate() < 10 ? '0' : '') + endDate.getDate()); + $('#event-modal input[name="event-start-date"]').val(ScheduleUtils.toDateValue(startDate)); + $('#event-modal input[name="event-end-date"]').val(ScheduleUtils.toDateValue(endDate)); $('#annotation-save-error').text(''); $('#event-modal').modal(); } diff --git a/test/src/org/labkey/test/tests/targetedms/InstrumentSchedulingTest.java b/test/src/org/labkey/test/tests/targetedms/InstrumentSchedulingTest.java index 1ce61bf63..3684a046f 100644 --- a/test/src/org/labkey/test/tests/targetedms/InstrumentSchedulingTest.java +++ b/test/src/org/labkey/test/tests/targetedms/InstrumentSchedulingTest.java @@ -183,6 +183,9 @@ public void testSchedule() throws IOException, CommandException { String originalStart = getFormElement(START_DATE_TIME_FIELD.findElement(getDriver())); String originalEnd = getFormElement(END_DATE_TIME_FIELD.findElement(getDriver())); + // Pin the 8AM/5PM defaults so a regression that zeroes or shifts the time is caught even on agents whose timezone would otherwise hide it. + assertTrue("Start field should default to 8AM, was: " + originalStart, originalStart.endsWith("T08:00")); + assertTrue("End field should default to 5PM, was: " + originalEnd, originalEnd.endsWith("T17:00")); // Try scheduling over the first reservation and verify it is blocked setFormElement(START_DATE_TIME_FIELD.findElement(getDriver()), originalStart.replace("-03T", "-02T")); setFormElement(END_DATE_TIME_FIELD.findElement(getDriver()), originalEnd.replace("-03T", "-02T")); diff --git a/webapp/TargetedMS/js/scheduleUtils.js b/webapp/TargetedMS/js/scheduleUtils.js index 02588526f..26846879b 100644 --- a/webapp/TargetedMS/js/scheduleUtils.js +++ b/webapp/TargetedMS/js/scheduleUtils.js @@ -8,6 +8,19 @@ (function(window) { const utils = {}; + const pad = function(n) { return (n < 10 ? '0' : '') + n; }; + + // Format a Date as datetime-local's fixed 'yyyy-MM-ddTHH:mm' wire form from local fields; avoids DateFormat, which zeroes the time in timezones whose label contains a colon (e.g. Honolulu). + utils.toDateTimeLocalValue = function(date) { + return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()) + + 'T' + pad(date.getHours()) + ':' + pad(date.getMinutes()); + }; + + // Date-only 'yyyy-MM-dd' wire form, derived from the datetime variant. + utils.toDateValue = function(date) { + return utils.toDateTimeLocalValue(date).slice(0, 10); + }; + // Convert a CSS color string (named, rgb, hex) to standard 6-digit HEX color (#RRGGBB) utils.stringToColor = function(color) { if (!color) return '#888888'; diff --git a/webapp/TargetedMS/js/scheduler.js b/webapp/TargetedMS/js/scheduler.js index f289542e2..c9449f989 100644 --- a/webapp/TargetedMS/js/scheduler.js +++ b/webapp/TargetedMS/js/scheduler.js @@ -396,8 +396,8 @@ $(function() { endDate.setDate(endDate.getDate() - 1); } - let startDateFormatted = DateFormat.format.date(startDate, LABKEY.container.formats.dateTimeFormat); - let endDateFormatted = DateFormat.format.date(endDate, LABKEY.container.formats.dateTimeFormat); + let startDateFormatted = ScheduleUtils.toDateTimeLocalValue(startDate); + let endDateFormatted = ScheduleUtils.toDateTimeLocalValue(endDate); // remove the old event log rows removeEventLog(); @@ -581,6 +581,9 @@ $(function() { }); function fetchInstrumentCosts(instrumentId, startDate, endDate) { + // Normalize to Date: callers pass either Date objects (hover) or seconds-less datetime-local strings (post-save), which DateFormat can't parse as-is. + startDate = new Date(startDate); + endDate = new Date(endDate); LABKEY.Query.selectRows({ schemaName: 'targetedms', queryName: 'instrumentRate', From b92cfdccc10daf63e0ef675bdbbedd78b5360b6c Mon Sep 17 00:00:00 2001 From: ankurjuneja Date: Mon, 27 Jul 2026 14:14:39 -0700 Subject: [PATCH 2/2] claude cr findings and fixes --- .../targetedms/InstrumentSchedulingTest.java | 3 +++ webapp/TargetedMS/js/scheduleUtils.js | 13 ++++++------- webapp/TargetedMS/js/scheduler.js | 19 ++++++++++--------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/test/src/org/labkey/test/tests/targetedms/InstrumentSchedulingTest.java b/test/src/org/labkey/test/tests/targetedms/InstrumentSchedulingTest.java index 3684a046f..ea9905ba9 100644 --- a/test/src/org/labkey/test/tests/targetedms/InstrumentSchedulingTest.java +++ b/test/src/org/labkey/test/tests/targetedms/InstrumentSchedulingTest.java @@ -200,6 +200,9 @@ public void testSchedule() throws IOException, CommandException assertProjectEventCounts(2, 0); + // The event chip time range is rendered via DateFormat (the patched parseTime path); verify it shows the real 8AM-5PM range, not a zeroed 00:00 - 00:00 as happened in colon-labelled timezones. + assertEquals("Event chip should show the 8AM-5PM time range", "08:00 - 17:00", getText(Locator.css(".activeProjectEvent .event-date"))); + doAndWaitForPageToLoad(() -> selectOptionByText(PROJECT_DROP_DOWN, PROJECT_2)); scheduleInstrument(yearMonth + "-04", false); diff --git a/webapp/TargetedMS/js/scheduleUtils.js b/webapp/TargetedMS/js/scheduleUtils.js index 26846879b..7b4199c36 100644 --- a/webapp/TargetedMS/js/scheduleUtils.js +++ b/webapp/TargetedMS/js/scheduleUtils.js @@ -10,15 +10,14 @@ const pad = function(n) { return (n < 10 ? '0' : '') + n; }; - // Format a Date as datetime-local's fixed 'yyyy-MM-ddTHH:mm' wire form from local fields; avoids DateFormat, which zeroes the time in timezones whose label contains a colon (e.g. Honolulu). - utils.toDateTimeLocalValue = function(date) { - return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()) - + 'T' + pad(date.getHours()) + ':' + pad(date.getMinutes()); + // Date-only 'yyyy-MM-dd' wire form from local fields. + utils.toDateValue = function(date) { + return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()); }; - // Date-only 'yyyy-MM-dd' wire form, derived from the datetime variant. - utils.toDateValue = function(date) { - return utils.toDateTimeLocalValue(date).slice(0, 10); + // Format a Date as datetime-local's fixed 'yyyy-MM-ddTHH:mm' wire form from local fields; avoids DateFormat, which zeroes the time in timezones whose label contains a colon (e.g. Honolulu). + utils.toDateTimeLocalValue = function(date) { + return utils.toDateValue(date) + 'T' + pad(date.getHours()) + ':' + pad(date.getMinutes()); }; // Convert a CSS color string (named, rgb, hex) to standard 6-digit HEX color (#RRGGBB) diff --git a/webapp/TargetedMS/js/scheduler.js b/webapp/TargetedMS/js/scheduler.js index c9449f989..622221018 100644 --- a/webapp/TargetedMS/js/scheduler.js +++ b/webapp/TargetedMS/js/scheduler.js @@ -88,10 +88,7 @@ $(function() { let bgColor = e.event.extendedProps.project === project ? e.backgroundColor : 'gray'; const cl = e.event.extendedProps.project === project ? 'activeProjectEvent' : 'otherProjectEvent'; let textColor = ScheduleUtils.getContrastTextColor(ScheduleUtils.stringToColor(bgColor)); - let timeFormatString = LABKEY.container.formats.timeFormat; - // Strip seconds and milliseconds - timeFormatString = timeFormatString.replace(':ss', '').replace('.SSS', ''); - let dateStr = DateFormat.format.date(e.event.start, timeFormatString) + ' - ' + DateFormat.format.date(e.event.end, timeFormatString); + let dateStr = ScheduleUtils.formatTimeRange(e.event.start, e.event.end); let style = 'background-color: ' + LABKEY.Utils.encodeHtml(bgColor) + '; color: ' + LABKEY.Utils.encodeHtml(textColor) + ';' + 'width: 100%'; content += '
' + '
' + LABKEY.Utils.encodeHtml(dateStr) + '
' @@ -582,8 +579,12 @@ $(function() { function fetchInstrumentCosts(instrumentId, startDate, endDate) { // Normalize to Date: callers pass either Date objects (hover) or seconds-less datetime-local strings (post-save), which DateFormat can't parse as-is. - startDate = new Date(startDate); - endDate = new Date(endDate); + const start = new Date(startDate); + const end = new Date(endDate); + // Bail on degenerate input so the cost log never renders $NaN or garbage dates (mirrors calculateAndRenderCostPreview). + if (isNaN(start.getTime()) || isNaN(end.getTime()) || end <= start) { + return; + } LABKEY.Query.selectRows({ schemaName: 'targetedms', queryName: 'instrumentRate', @@ -598,7 +599,7 @@ $(function() { } let fee = data.rows[0].fee; let rateType = data.rows[0].rateType; - let cost = fee * (Math.abs(new Date(endDate) - new Date(startDate))) / 1000 / 60 / 60; + let cost = fee * (Math.abs(end - start)) / 1000 / 60 / 60; // query the rateType LABKEY.Query.selectRows({ @@ -611,8 +612,8 @@ $(function() { success: function (data) { let setupFee = data.rows[0].setupFee; - let startDateFormatted = DateFormat.format.date(startDate, LABKEY.container.formats.dateTimeFormat); - let endDateFormatted = DateFormat.format.date(endDate, LABKEY.container.formats.dateTimeFormat); + let startDateFormatted = DateFormat.format.date(start, LABKEY.container.formats.dateTimeFormat); + let endDateFormatted = DateFormat.format.date(end, LABKEY.container.formats.dateTimeFormat); let tableElt = document.getElementById('event-cost-table'); let rowElt = document.createElement('tr');