Skip to content

Commit cd3da8d

Browse files
committed
Merge remote-tracking branch 'origin/develop' into fb_amountsAndUnits
# Conflicts: # packages/components/package-lock.json # packages/components/package.json # packages/components/releaseNotes/components.md
2 parents 30a68bc + 7db11c0 commit cd3da8d

5 files changed

Lines changed: 58 additions & 26 deletions

File tree

packages/components/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@labkey/components",
3-
"version": "6.61.0-amountsAndUnits.3",
3+
"version": "6.61.2-amountsAndUnits.3",
44
"description": "Components, models, actions, and utility functions for LabKey applications and pages",
55
"sideEffects": false,
66
"files": [

packages/components/releaseNotes/components.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ Components, models, actions, and utility functions for LabKey applications and p
1616
- `SampleAmountEditModal` updated so all users go through `updateSampleStorageData`
1717
- Mark `RawAmount` and `RawUnits` as ignored system fields
1818

19+
### version 6.61.1
20+
*Released*: 12 September 2025
21+
- DatePickerInput: don't add milliseconds when entering current time (Issue 53577)
22+
1923
### version 6.61.0
2024
*Released*: 8 September 2025
2125
- QueryModel.getSelectedIds: optimize when filterIds are not passed

packages/components/src/internal/components/forms/input/DatePickerInput.tsx

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,8 @@ export class DatePickerInputImpl extends DisableableInput<DatePickerInputImplPro
158158
return getDateFromISO(value, queryColumn, allowRelativeInput, minDate);
159159
}
160160

161-
onChange = (date: Date, event?: any, raw?: boolean): void => {
162-
const { onChange, formsy, hideTime, inlineEdit, queryColumn } = this.props;
163-
164-
if (!event && !raw && date?.getMilliseconds() > 0) {
165-
date.setMilliseconds(0); // react-datepicker milliseconds are not 0 when selecting time
166-
}
161+
onChange = (date: Date, event?: any): void => {
162+
const { onChange, formsy, inlineEdit, queryColumn } = this.props;
167163

168164
this.setState({ selectedDate: date, invalid: false, invalidStart: false });
169165

@@ -188,7 +184,7 @@ export class DatePickerInputImpl extends DisableableInput<DatePickerInputImplPro
188184

189185
if (queryColumn.isTimeColumn) {
190186
// Issue 50010: Time picker enters the wrong time if a time field has a format set
191-
this.onChange(parseTime(value), undefined, true);
187+
this.onChange(parseTime(value), undefined);
192188
} else if (isRelativeDateFilterValue(value)) {
193189
this.setState({ relativeInputValue: value });
194190
this.props.onChange?.(value);
@@ -197,11 +193,6 @@ export class DatePickerInputImpl extends DisableableInput<DatePickerInputImplPro
197193
}
198194
};
199195

200-
getDateFormat(): string {
201-
const { queryColumn, hideTime } = this.props;
202-
return getPickerDateAndTimeFormat(queryColumn, hideTime).dateFormat;
203-
}
204-
205196
onIconClick = (): void => {
206197
this.input.current?.setFocus();
207198
};
@@ -211,6 +202,26 @@ export class DatePickerInputImpl extends DisableableInput<DatePickerInputImplPro
211202
this.input.current?.setFocus();
212203
};
213204

205+
/**
206+
* This method returns the current value OR the current date with seconds and milliseconds set to zero. It is
207+
* important to set milliseconds to zero because there is a bug in react-datepicker that causes it to set the
208+
* milliseconds to the current milliseconds when the user first selects a value, even if the UI shows 0
209+
* milliseconds. If there is a pre-existing value, or openToDate has zero ms, then the selections will always be
210+
* correct.
211+
*
212+
* See Issue 53577
213+
*/
214+
getOpenToDate = (): Date => {
215+
const { selectedDate } = this.state;
216+
217+
if (selectedDate) return selectedDate;
218+
219+
const now = new Date();
220+
now.setSeconds(0);
221+
now.setMilliseconds(0);
222+
return now;
223+
};
224+
214225
render(): ReactNode {
215226
const {
216227
addLabelAsterisk,
@@ -243,7 +254,6 @@ export class DatePickerInputImpl extends DisableableInput<DatePickerInputImplPro
243254
const isTimeOnly = queryColumn.isTimeColumn;
244255
const picker = (
245256
<DatePicker
246-
ref={this.input}
247257
autoComplete="off"
248258
autoFocus={autoFocus}
249259
className={inputClassName}
@@ -252,22 +262,24 @@ export class DatePickerInputImpl extends DisableableInput<DatePickerInputImplPro
252262
id={queryColumn.fieldKey}
253263
isClearable={isClearable}
254264
name={name ? name : queryColumn.fieldKey}
265+
onBlur={inlineEdit ? onBlur : undefined}
255266
onCalendarClose={onCalendarClose}
256267
onChange={this.onChange}
257268
onChangeRaw={allowRelativeInput || isTimeOnly ? this.onChangeRaw : undefined}
258269
onKeyDown={onKeyDown}
259270
onMonthChange={this.onChange}
271+
onSelect={inlineEdit ? this.onSelect : undefined}
272+
openToDate={this.getOpenToDate()}
260273
placeholderText={placeholderText ?? `Select ${queryColumn.caption.toLowerCase()}`}
274+
ref={this.input}
261275
selected={invalid ? null : selectedDate}
276+
shouldCloseOnSelect={inlineEdit ? false : undefined}
262277
showTimeSelect={!hideTime && (isDateTimeCol(queryColumn) || isTimeOnly) && !validValueInvalidStart}
263278
showTimeSelectOnly={!hideTime && isTimeOnly}
264-
timeIntervals={isTimeOnly ? 10 : 30}
265279
timeFormat={timeFormat}
280+
timeIntervals={isTimeOnly ? 10 : 30}
266281
value={allowRelativeInput && !isTimeOnly && isRelativeDateFilterValue(value) ? value : undefined}
267282
wrapperClassName={inputWrapperClassName}
268-
onSelect={inlineEdit ? this.onSelect : undefined}
269-
onBlur={inlineEdit ? onBlur : undefined}
270-
shouldCloseOnSelect={inlineEdit ? false : undefined}
271283
/>
272284
);
273285

@@ -293,6 +305,8 @@ export class DatePickerInputImpl extends DisableableInput<DatePickerInputImplPro
293305
</label>
294306
) : (
295307
<FieldLabel
308+
column={queryColumn}
309+
isDisabled={isDisabled}
296310
label={label}
297311
labelOverlayProps={{
298312
isFormsy: false,
@@ -303,8 +317,6 @@ export class DatePickerInputImpl extends DisableableInput<DatePickerInputImplPro
303317
}}
304318
showLabel={showLabel}
305319
showToggle={allowDisable}
306-
column={queryColumn}
307-
isDisabled={isDisabled}
308320
toggleProps={{
309321
onClick: this.toggleDisabled,
310322
}}

packages/components/src/internal/util/Date.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,11 @@ export function getPickerTimeFormatWithPrecision(
131131
return timeFormat;
132132
}
133133

134-
// convert rawDateTimeFormat to adjust precision
135-
// for example:
136-
// 'yyyy-MM-dd HH:mm' -> 'yyyy-MM-dd HH:mm:ss' if showSeconds is true and showMilliSeconds is false
137-
// 'yyyy-MM-dd hh:mm a' -> 'yyyy-MM-dd hh:mm:ss.SSS a' if showSeconds is true and showMilliSeconds is true
134+
/**
135+
* This method is takes a date format, and adds additional precision to it as necessary:
136+
* 'yyyy-MM-dd HH:mm' -> 'yyyy-MM-dd HH:mm:ss' if showSeconds is true and showMilliSeconds is false
137+
* 'yyyy-MM-dd hh:mm a' -> 'yyyy-MM-dd hh:mm:ss.SSS a' if showSeconds is true and showMilliSeconds is true
138+
*/
138139
export function getPickerFormatWithPrecision(
139140
rawDateTimeFormat: string,
140141
showMinute?: boolean,
@@ -151,6 +152,21 @@ export function getPickerFormatWithPrecision(
151152
).trim();
152153
}
153154

155+
/**
156+
* Given a QueryColumn and an initDate value this method will return a date and time format. By default, we will return
157+
* the date/time format from the QueryColumn, however, in some cases we may return a date/time format that has higher
158+
* precision (see getPickerFormatWithPrecision above).
159+
*
160+
* Changing the precision of a date or time format is necessary for a few reasons:
161+
* - LabKey uses date formats for display reasons only, we actually always store fully precise dates and times
162+
* - We want to allow users to enter very precise dates and times if needed, and if they do enter a very precise date
163+
* then we want to render what they entered, so they do not falsely believe that we are truncating their data
164+
* - If the user enters a date/time that matches the existing format, we want to display it in that format, in order
165+
* to reduce visual clutter (react-datepicker looks worse when using highly precise date formats)
166+
* - react-datepicker uses date formats as display and value formats, if you pass a strict date format it will only
167+
* give you values that match the format, it will truncate more precise values entered by users, which is a data
168+
* integrity problem (See Issue 52820).
169+
*/
154170
export function getPickerDateAndTimeFormat(
155171
column: QueryColumn,
156172
hideTime?: boolean,

0 commit comments

Comments
 (0)