Fix date picker wrong on certain timezones (#5972)

Timezone with a negative offset weren't working good with date pickers.

I split the logic for display and parsing between date only and
datetime.

Date time is sending and displaying using timezone, and date only is
sending and displaying by forcing the date to take its UTC day and month
and 00:00:00 time.

This way its consistent across all timezones.
This commit is contained in:
Lucas Bordeau
2024-06-20 17:13:30 +02:00
committed by GitHub
parent 8c6e96c41b
commit 9e08445bff
7 changed files with 80 additions and 22 deletions

View File

@ -121,6 +121,7 @@ export const FieldInput = ({
onEscape={onEscape} onEscape={onEscape}
onClickOutside={onClickOutside} onClickOutside={onClickOutside}
onClear={onSubmit} onClear={onSubmit}
onSubmit={onSubmit}
/> />
) : isFieldDate(fieldDefinition) ? ( ) : isFieldDate(fieldDefinition) ? (
<DateFieldInput <DateFieldInput
@ -128,6 +129,7 @@ export const FieldInput = ({
onEscape={onEscape} onEscape={onEscape}
onClickOutside={onClickOutside} onClickOutside={onClickOutside}
onClear={onSubmit} onClear={onSubmit}
onSubmit={onSubmit}
/> />
) : isFieldNumber(fieldDefinition) ? ( ) : isFieldNumber(fieldDefinition) ? (
<NumberFieldInput <NumberFieldInput

View File

@ -13,6 +13,7 @@ export type DateFieldInputProps = {
onEnter?: FieldInputEvent; onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent; onEscape?: FieldInputEvent;
onClear?: FieldInputEvent; onClear?: FieldInputEvent;
onSubmit?: FieldInputEvent;
}; };
export const DateFieldInput = ({ export const DateFieldInput = ({
@ -20,6 +21,7 @@ export const DateFieldInput = ({
onEscape, onEscape,
onClickOutside, onClickOutside,
onClear, onClear,
onSubmit,
}: DateFieldInputProps) => { }: DateFieldInputProps) => {
const { fieldValue, setDraftValue } = useDateField(); const { fieldValue, setDraftValue } = useDateField();
@ -39,6 +41,10 @@ export const DateFieldInput = ({
onEnter?.(() => persistDate(newDate)); onEnter?.(() => persistDate(newDate));
}; };
const handleSubmit = (newDate: Nullable<Date>) => {
onSubmit?.(() => persistDate(newDate));
};
const handleEscape = (newDate: Nullable<Date>) => { const handleEscape = (newDate: Nullable<Date>) => {
onEscape?.(() => persistDate(newDate)); onEscape?.(() => persistDate(newDate));
}; };
@ -69,6 +75,7 @@ export const DateFieldInput = ({
clearable clearable
onChange={handleChange} onChange={handleChange}
onClear={handleClear} onClear={handleClear}
onSubmit={handleSubmit}
/> />
); );
}; };

View File

@ -12,6 +12,7 @@ export type DateTimeFieldInputProps = {
onEnter?: FieldInputEvent; onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent; onEscape?: FieldInputEvent;
onClear?: FieldInputEvent; onClear?: FieldInputEvent;
onSubmit?: FieldInputEvent;
}; };
export const DateTimeFieldInput = ({ export const DateTimeFieldInput = ({
@ -19,6 +20,7 @@ export const DateTimeFieldInput = ({
onEscape, onEscape,
onClickOutside, onClickOutside,
onClear, onClear,
onSubmit,
}: DateTimeFieldInputProps) => { }: DateTimeFieldInputProps) => {
const { fieldValue, setDraftValue } = useDateTimeField(); const { fieldValue, setDraftValue } = useDateTimeField();
@ -57,6 +59,10 @@ export const DateTimeFieldInput = ({
onClear?.(() => persistDate(null)); onClear?.(() => persistDate(null));
}; };
const handleSubmit = (newDate: Nullable<Date>) => {
onSubmit?.(() => persistDate(newDate));
};
const dateValue = fieldValue ? new Date(fieldValue) : null; const dateValue = fieldValue ? new Date(fieldValue) : null;
return ( return (
@ -69,6 +75,7 @@ export const DateTimeFieldInput = ({
onChange={handleChange} onChange={handleChange}
isDateTimeInput isDateTimeInput
onClear={handleClear} onClear={handleClear}
onSubmit={handleSubmit}
/> />
); );
}; };

View File

@ -142,10 +142,9 @@ export const RecordTableCellContainer = ({
[styles.cellBaseContainerSoftFocus]: hasSoftFocus, [styles.cellBaseContainerSoftFocus]: hasSoftFocus,
})} })}
> >
{isInEditMode && ( {isInEditMode ? (
<RecordTableCellEditMode>{editModeContent}</RecordTableCellEditMode> <RecordTableCellEditMode>{editModeContent}</RecordTableCellEditMode>
)} ) : hasSoftFocus ? (
{hasSoftFocus ? (
<RecordTableCellSoftFocusMode <RecordTableCellSoftFocusMode
editModeContent={editModeContent} editModeContent={editModeContent}
nonEditModeContent={nonEditModeContent} nonEditModeContent={nonEditModeContent}

View File

@ -33,6 +33,7 @@ export type DateInputProps = {
onChange?: (newDate: Nullable<Date>) => void; onChange?: (newDate: Nullable<Date>) => void;
isDateTimeInput?: boolean; isDateTimeInput?: boolean;
onClear?: () => void; onClear?: () => void;
onSubmit?: (newDate: Nullable<Date>) => void;
}; };
export const DateInput = ({ export const DateInput = ({
@ -44,6 +45,7 @@ export const DateInput = ({
onChange, onChange,
isDateTimeInput, isDateTimeInput,
onClear, onClear,
onSubmit,
}: DateInputProps) => { }: DateInputProps) => {
const [internalValue, setInternalValue] = useState(value); const [internalValue, setInternalValue] = useState(value);
@ -59,6 +61,11 @@ export const DateInput = ({
onClear?.(); onClear?.();
}; };
const handleMouseSelect = (newDate: Date | null) => {
setInternalValue(newDate);
onSubmit?.(newDate);
};
const { closeDropdown } = useDropdown(MONTH_AND_YEAR_DROPDOWN_ID); const { closeDropdown } = useDropdown(MONTH_AND_YEAR_DROPDOWN_ID);
const { closeDropdown: closeDropdownMonthSelect } = useDropdown( const { closeDropdown: closeDropdownMonthSelect } = useDropdown(
MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID, MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID,
@ -86,9 +93,7 @@ export const DateInput = ({
<InternalDatePicker <InternalDatePicker
date={internalValue ?? new Date()} date={internalValue ?? new Date()}
onChange={handleChange} onChange={handleChange}
onMouseSelect={(newDate: Date | null) => { onMouseSelect={handleMouseSelect}
onEnter(newDate);
}}
clearable={clearable ? clearable : false} clearable={clearable ? clearable : false}
isDateTimeInput={isDateTimeInput} isDateTimeInput={isDateTimeInput}
onEnter={onEnter} onEnter={onEnter}

View File

@ -57,11 +57,25 @@ export const DateTimeInput = ({
(date: any) => { (date: any) => {
const dateParsed = DateTime.fromJSDate(date); const dateParsed = DateTime.fromJSDate(date);
const formattedDate = dateParsed.toFormat(parsingFormat); const dateWithoutTime = DateTime.fromJSDate(date)
.toLocal()
.set({
day: date.getUTCDate(),
month: date.getUTCMonth() + 1,
year: date.getUTCFullYear(),
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
});
const formattedDate = isDateTimeInput
? dateParsed.toFormat(parsingFormat)
: dateWithoutTime.toFormat(parsingFormat);
return formattedDate; return formattedDate;
}, },
[parsingFormat], [parsingFormat, isDateTimeInput],
); );
const parseStringToDate = (str: string) => { const parseStringToDate = (str: string) => {

View File

@ -383,19 +383,35 @@ export const InternalDatePicker = ({
onChange?.(newDate); onChange?.(newDate);
}; };
const dateWithoutTime = DateTime.fromJSDate(date)
.toLocal()
.set({
day: date.getUTCDate(),
month: date.getUTCMonth() + 1,
year: date.getUTCFullYear(),
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
})
.toJSDate();
const dateToUse = isDateTimeInput ? date : dateWithoutTime;
return ( return (
<StyledContainer onKeyDown={handleKeyDown}> <StyledContainer onKeyDown={handleKeyDown}>
<div className={clearable ? 'clearable ' : ''}> <div className={clearable ? 'clearable ' : ''}>
<ReactDatePicker <ReactDatePicker
open={true} open={true}
selected={internalDate} selected={dateToUse}
openToDate={internalDate} openToDate={dateToUse}
disabledKeyboardNavigation
onChange={(newDate) => { onChange={(newDate) => {
onChange?.(newDate); onChange?.(newDate);
}} }}
customInput={ customInput={
<DateTimeInput <DateTimeInput
date={internalDate} date={dateToUse}
isDateTimeInput={isDateTimeInput} isDateTimeInput={isDateTimeInput}
onChange={onChange} onChange={onChange}
/> />
@ -424,13 +440,13 @@ export const InternalDatePicker = ({
options={months} options={months}
disableBlur disableBlur
onChange={handleChangeMonth} onChange={handleChangeMonth}
value={date.getMonth()} value={date.getUTCMonth()}
fullWidth fullWidth
/> />
<Select <Select
dropdownId={MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID} dropdownId={MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID}
onChange={handleChangeYear} onChange={handleChangeYear}
value={date.getFullYear()} value={date.getUTCFullYear()}
options={years} options={years}
disableBlur disableBlur
fullWidth fullWidth
@ -450,16 +466,24 @@ export const InternalDatePicker = ({
</StyledCustomDatePickerHeader> </StyledCustomDatePickerHeader>
</> </>
)} )}
onSelect={(date: Date, event) => { onSelect={(date: Date) => {
const dateUTC = DateTime.fromJSDate(date, { const dateParsedWithoutTime = DateTime.fromObject(
zone: 'utc', {
}).toJSDate(); day: date.getDate(),
month: date.getMonth() + 1,
year: date.getFullYear(),
hour: 0,
minute: 0,
second: 0,
},
{ zone: 'utc' },
).toJSDate();
if (event?.type === 'click') { const dateForUpdate = isDateTimeInput
handleMouseSelect?.(dateUTC); ? date
} else { : dateParsedWithoutTime;
onChange?.(dateUTC);
} handleMouseSelect?.(dateForUpdate);
}} }}
/> />
</div> </div>