Remove Luxon from codebase (#14448)

Fixes #14444 

We shouldn't have 2 libraries to manage dates, one is enough
This commit is contained in:
Félix Malfait
2025-09-12 23:25:05 +02:00
committed by GitHub
parent 1e97ad48c0
commit a0cfff6000
21 changed files with 215 additions and 290 deletions
@@ -1,5 +1,4 @@
import styled from '@emotion/styled';
import { DateTime } from 'luxon';
import { useRecoilValue } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
@@ -60,17 +59,15 @@ export const AbsoluteDatePickerHeader = ({
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const userLocale = currentWorkspaceMember?.locale ?? SOURCE_LOCALE;
const endOfDayDateTimeInLocalTimezone = DateTime.now().set({
day: date.getDate(),
month: date.getMonth() + 1,
year: date.getFullYear(),
hour: 23,
minute: 59,
second: 59,
millisecond: 999,
});
const endOfDayInLocalTimezone = endOfDayDateTimeInLocalTimezone.toJSDate();
const endOfDayInLocalTimezone = new Date(
date.getFullYear(),
date.getMonth(),
date.getDate(),
23,
59,
59,
999,
);
return (
<>
@@ -1,6 +1,6 @@
import styled from '@emotion/styled';
import { DateTime } from 'luxon';
import { lazy, Suspense, useContext, type ComponentType } from 'react';
import { addMonths, setDate, setMonth, setYear, subMonths } from 'date-fns';
import { lazy, Suspense, type ComponentType } from 'react';
import type { ReactDatePickerProps as ReactDatePickerLibProps } from 'react-datepicker';
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
@@ -13,7 +13,6 @@ import { DateTimeInput } from '@/ui/input/components/internal/date/components/Da
import { RelativeDatePickerHeader } from '@/ui/input/components/internal/date/components/RelativeDatePickerHeader';
import { getHighlightedDates } from '@/ui/input/components/internal/date/utils/getHighlightedDates';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { UserContext } from '@/users/contexts/UserContext';
import { useTheme } from '@emotion/react';
import { t } from '@lingui/core/macro';
import 'react-datepicker/dist/react-datepicker.css';
@@ -352,7 +351,6 @@ export const DateTimePicker = ({
}: DateTimePickerProps) => {
const internalDate = date ?? new Date();
const { timeZone } = useContext(UserContext);
const theme = useTheme();
const { closeDropdown: closeDropdownMonthSelect } = useCloseDropdown();
@@ -380,90 +378,60 @@ export const DateTimePicker = ({
};
const handleAddMonth = () => {
const dateParsed = DateTime.fromJSDate(internalDate, { zone: timeZone })
.plus({ months: 1 })
.toJSDate();
const dateParsed = addMonths(internalDate, 1);
onChange?.(dateParsed);
};
const handleSubtractMonth = () => {
const dateParsed = DateTime.fromJSDate(internalDate, { zone: timeZone })
.minus({ months: 1 })
.toJSDate();
const dateParsed = subMonths(internalDate, 1);
onChange?.(dateParsed);
};
const handleChangeYear = (year: number) => {
const dateParsed = DateTime.fromJSDate(internalDate, { zone: timeZone })
.set({ year: year })
.toJSDate();
const dateParsed = setYear(internalDate, year);
onChange?.(dateParsed);
};
const handleDateChange = (date: Date) => {
const dateParsed = DateTime.fromJSDate(internalDate, {
zone: isDateTimeInput ? timeZone : 'local',
})
.set({
day: date.getDate(),
month: date.getMonth() + 1,
year: date.getFullYear(),
})
.toJSDate();
let dateParsed = setYear(internalDate, date.getFullYear());
dateParsed = setMonth(dateParsed, date.getMonth());
dateParsed = setDate(dateParsed, date.getDate());
onChange?.(dateParsed);
};
const handleDateSelect = (date: Date) => {
const dateParsed = DateTime.fromJSDate(internalDate, {
zone: isDateTimeInput ? timeZone : 'local',
})
.set({
day: date.getDate(),
month: date.getMonth() + 1,
year: date.getFullYear(),
})
.toJSDate();
let dateParsed = setYear(internalDate, date.getFullYear());
dateParsed = setMonth(dateParsed, date.getMonth());
dateParsed = setDate(dateParsed, date.getDate());
handleClose?.(dateParsed);
};
const dateWithoutTime = DateTime.fromJSDate(internalDate)
.toLocal()
.set({
day: internalDate.getUTCDate(),
month: internalDate.getUTCMonth() + 1,
year: internalDate.getUTCFullYear(),
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
})
.toJSDate();
const dateParsed = DateTime.fromJSDate(internalDate, {
zone: isDateTimeInput ? timeZone : 'local',
});
const dateWithoutTime = new Date(
internalDate.getUTCFullYear(),
internalDate.getUTCMonth(),
internalDate.getUTCDate(),
0,
0,
0,
0,
);
// We have to force a end of day on the computer local timezone with the given date
// Because JS Date API cannot hold a timezone other than the local one
// And if we don't do that workaround we will have problems when changing the date
// Because the shown date will have 1 day more or less than the real date
// Leading to bugs where we select 1st of January and it shows 31st of December for example
const endOfDayDateTimeInLocalTimezone = DateTime.now().set({
day: dateParsed.get('day'),
month: dateParsed.get('month'),
year: dateParsed.get('year'),
hour: 23,
minute: 59,
second: 59,
millisecond: 999,
});
const endOfDayInLocalTimezone = endOfDayDateTimeInLocalTimezone.toJSDate();
const endOfDayInLocalTimezone = new Date(
internalDate.getFullYear(),
internalDate.getMonth(),
internalDate.getDate(),
23,
59,
59,
999,
);
const dateToUse = isDateTimeInput ? endOfDayInLocalTimezone : dateWithoutTime;
@@ -1,5 +1,7 @@
import { DateFormat } from '@/localization/constants/DateFormat';
import { DateTime } from 'luxon';
import { format } from 'date-fns';
import { formatInTimeZone } from 'date-fns-tz';
import { isDefined } from 'twenty-shared/utils';
import { getDateFormatString } from '~/utils/date-utils';
type ParseDateToStringArgs = {
@@ -17,23 +19,14 @@ export const parseDateToString = ({
}: ParseDateToStringArgs) => {
const parsingFormat = getDateFormatString(dateFormat, isDateTimeInput);
const dateParsed = DateTime.fromJSDate(date, { zone: userTimezone });
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.setZone(userTimezone).toFormat(parsingFormat)
: dateWithoutTime.toFormat(parsingFormat);
return formattedDate;
if (isDateTimeInput && isDefined(userTimezone)) {
return formatInTimeZone(date, userTimezone, parsingFormat);
} else if (isDateTimeInput) {
return format(date, parsingFormat);
} else {
const dateWithoutTime = new Date(
Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()),
);
return format(dateWithoutTime, parsingFormat);
}
};
@@ -1,5 +1,7 @@
import { type DateFormat } from '@/localization/constants/DateFormat';
import { DateTime } from 'luxon';
import { isValid, parse } from 'date-fns';
import { zonedTimeToUtc } from 'date-fns-tz';
import { isDefined } from 'twenty-shared/utils';
import { getDateFormatString } from '~/utils/date-utils';
type ParseStringToDateArgs = {
@@ -16,18 +18,17 @@ export const parseStringToDate = ({
dateFormat,
}: ParseStringToDateArgs) => {
const parsingFormat = getDateFormatString(dateFormat, isDateTimeInput);
const referenceDate = new Date();
const parsedDate = isDateTimeInput
? DateTime.fromFormat(dateAsString, parsingFormat, { zone: userTimezone })
: DateTime.fromFormat(dateAsString, parsingFormat, { zone: 'utc' });
const parsedDate = parse(dateAsString, parsingFormat, referenceDate);
const isValid = parsedDate.isValid;
if (!isValid) {
if (!isValid(parsedDate)) {
return null;
}
const jsDate = parsedDate.toJSDate();
if (isDateTimeInput && isDefined(userTimezone)) {
return zonedTimeToUtc(parsedDate, userTimezone);
}
return jsDate;
return parsedDate;
};