Skip to content

Commit

Permalink
Fix date picker wrong on certain timezones (twentyhq#5972)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lucasbordeau authored Jun 20, 2024
1 parent 8c6e96c commit 9e08445
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,15 @@ export const FieldInput = ({
onEscape={onEscape}
onClickOutside={onClickOutside}
onClear={onSubmit}
onSubmit={onSubmit}
/>
) : isFieldDate(fieldDefinition) ? (
<DateFieldInput
onEnter={onEnter}
onEscape={onEscape}
onClickOutside={onClickOutside}
onClear={onSubmit}
onSubmit={onSubmit}
/>
) : isFieldNumber(fieldDefinition) ? (
<NumberFieldInput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ export type DateFieldInputProps = {
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;
onClear?: FieldInputEvent;
onSubmit?: FieldInputEvent;
};

export const DateFieldInput = ({
onEnter,
onEscape,
onClickOutside,
onClear,
onSubmit,
}: DateFieldInputProps) => {
const { fieldValue, setDraftValue } = useDateField();

Expand All @@ -39,6 +41,10 @@ export const DateFieldInput = ({
onEnter?.(() => persistDate(newDate));
};

const handleSubmit = (newDate: Nullable<Date>) => {
onSubmit?.(() => persistDate(newDate));
};

const handleEscape = (newDate: Nullable<Date>) => {
onEscape?.(() => persistDate(newDate));
};
Expand Down Expand Up @@ -69,6 +75,7 @@ export const DateFieldInput = ({
clearable
onChange={handleChange}
onClear={handleClear}
onSubmit={handleSubmit}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ export type DateTimeFieldInputProps = {
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;
onClear?: FieldInputEvent;
onSubmit?: FieldInputEvent;
};

export const DateTimeFieldInput = ({
onEnter,
onEscape,
onClickOutside,
onClear,
onSubmit,
}: DateTimeFieldInputProps) => {
const { fieldValue, setDraftValue } = useDateTimeField();

Expand Down Expand Up @@ -57,6 +59,10 @@ export const DateTimeFieldInput = ({
onClear?.(() => persistDate(null));
};

const handleSubmit = (newDate: Nullable<Date>) => {
onSubmit?.(() => persistDate(newDate));
};

const dateValue = fieldValue ? new Date(fieldValue) : null;

return (
Expand All @@ -69,6 +75,7 @@ export const DateTimeFieldInput = ({
onChange={handleChange}
isDateTimeInput
onClear={handleClear}
onSubmit={handleSubmit}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,9 @@ export const RecordTableCellContainer = ({
[styles.cellBaseContainerSoftFocus]: hasSoftFocus,
})}
>
{isInEditMode && (
{isInEditMode ? (
<RecordTableCellEditMode>{editModeContent}</RecordTableCellEditMode>
)}
{hasSoftFocus ? (
) : hasSoftFocus ? (
<RecordTableCellSoftFocusMode
editModeContent={editModeContent}
nonEditModeContent={nonEditModeContent}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type DateInputProps = {
onChange?: (newDate: Nullable<Date>) => void;
isDateTimeInput?: boolean;
onClear?: () => void;
onSubmit?: (newDate: Nullable<Date>) => void;
};

export const DateInput = ({
Expand All @@ -44,6 +45,7 @@ export const DateInput = ({
onChange,
isDateTimeInput,
onClear,
onSubmit,
}: DateInputProps) => {
const [internalValue, setInternalValue] = useState(value);

Expand All @@ -59,6 +61,11 @@ export const DateInput = ({
onClear?.();
};

const handleMouseSelect = (newDate: Date | null) => {
setInternalValue(newDate);
onSubmit?.(newDate);
};

const { closeDropdown } = useDropdown(MONTH_AND_YEAR_DROPDOWN_ID);
const { closeDropdown: closeDropdownMonthSelect } = useDropdown(
MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID,
Expand Down Expand Up @@ -86,9 +93,7 @@ export const DateInput = ({
<InternalDatePicker
date={internalValue ?? new Date()}
onChange={handleChange}
onMouseSelect={(newDate: Date | null) => {
onEnter(newDate);
}}
onMouseSelect={handleMouseSelect}
clearable={clearable ? clearable : false}
isDateTimeInput={isDateTimeInput}
onEnter={onEnter}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,25 @@ export const DateTimeInput = ({
(date: any) => {
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;
},
[parsingFormat],
[parsingFormat, isDateTimeInput],
);

const parseStringToDate = (str: string) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,19 +383,35 @@ export const InternalDatePicker = ({
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 (
<StyledContainer onKeyDown={handleKeyDown}>
<div className={clearable ? 'clearable ' : ''}>
<ReactDatePicker
open={true}
selected={internalDate}
openToDate={internalDate}
selected={dateToUse}
openToDate={dateToUse}
disabledKeyboardNavigation
onChange={(newDate) => {
onChange?.(newDate);
}}
customInput={
<DateTimeInput
date={internalDate}
date={dateToUse}
isDateTimeInput={isDateTimeInput}
onChange={onChange}
/>
Expand Down Expand Up @@ -424,13 +440,13 @@ export const InternalDatePicker = ({
options={months}
disableBlur
onChange={handleChangeMonth}
value={date.getMonth()}
value={date.getUTCMonth()}
fullWidth
/>
<Select
dropdownId={MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID}
onChange={handleChangeYear}
value={date.getFullYear()}
value={date.getUTCFullYear()}
options={years}
disableBlur
fullWidth
Expand All @@ -450,16 +466,24 @@ export const InternalDatePicker = ({
</StyledCustomDatePickerHeader>
</>
)}
onSelect={(date: Date, event) => {
const dateUTC = DateTime.fromJSDate(date, {
zone: 'utc',
}).toJSDate();

if (event?.type === 'click') {
handleMouseSelect?.(dateUTC);
} else {
onChange?.(dateUTC);
}
onSelect={(date: Date) => {
const dateParsedWithoutTime = DateTime.fromObject(
{
day: date.getDate(),
month: date.getMonth() + 1,
year: date.getFullYear(),
hour: 0,
minute: 0,
second: 0,
},
{ zone: 'utc' },
).toJSDate();

const dateForUpdate = isDateTimeInput
? date
: dateParsedWithoutTime;

handleMouseSelect?.(dateForUpdate);
}}
/>
</div>
Expand Down

0 comments on commit 9e08445

Please sign in to comment.