Remove Luxon from codebase (#14448)
Fixes #14444 We shouldn't have 2 libraries to manage dates, one is enough
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { i18n } from '@lingui/core';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { addDays, format, formatDistanceToNow, subDays } from 'date-fns';
|
||||
import { fr } from 'date-fns/locale';
|
||||
import { DateTime } from 'luxon';
|
||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
import { messages as enMessages } from '~/locales/generated/en';
|
||||
import { messages as frMessages } from '~/locales/generated/fr-FR';
|
||||
@@ -19,10 +18,6 @@ import { logError } from '../logError';
|
||||
i18n.load(SOURCE_LOCALE, enMessages);
|
||||
i18n.activate(SOURCE_LOCALE);
|
||||
|
||||
const getLuxonLocale = () => {
|
||||
return SOURCE_LOCALE === 'en' ? 'en-US' : SOURCE_LOCALE;
|
||||
};
|
||||
|
||||
jest.mock('~/utils/logError');
|
||||
jest.useFakeTimers().setSystemTime(new Date('2024-01-01T00:00:00.000Z'));
|
||||
|
||||
@@ -30,20 +25,16 @@ describe('beautifyExactDateTime', () => {
|
||||
it('should return the date in the correct format with time', () => {
|
||||
const mockDate = '2023-01-01T12:13:24';
|
||||
const actualDate = new Date(mockDate);
|
||||
const expected = DateTime.fromJSDate(actualDate)
|
||||
.setLocale(getLuxonLocale())
|
||||
.toFormat('DD · T');
|
||||
const expected = format(actualDate, 'MMM d, yyyy · HH:mm');
|
||||
|
||||
const result = beautifyExactDateTime(mockDate);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
it('should return the time in the correct format for a datetime that is today', () => {
|
||||
const todayString = DateTime.local().toISODate();
|
||||
const todayString = '2024-01-01'; // Using the mocked date
|
||||
const mockDate = `${todayString}T12:13:24`;
|
||||
const actualDate = new Date(mockDate);
|
||||
const expected = DateTime.fromJSDate(actualDate)
|
||||
.setLocale(getLuxonLocale())
|
||||
.toFormat('T');
|
||||
const expected = format(actualDate, 'HH:mm');
|
||||
|
||||
const result = beautifyExactDateTime(mockDate);
|
||||
expect(result).toEqual(expected);
|
||||
@@ -54,15 +45,13 @@ describe('beautifyExactDate', () => {
|
||||
it('should return the past date in the correct format without time', () => {
|
||||
const mockDate = '2023-01-01T12:13:24';
|
||||
const actualDate = new Date(mockDate);
|
||||
const expected = DateTime.fromJSDate(actualDate)
|
||||
.setLocale(getLuxonLocale())
|
||||
.toFormat('DD');
|
||||
const expected = format(actualDate, 'MMM d, yyyy');
|
||||
|
||||
const result = beautifyExactDate(mockDate);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
it('should return "Today" if the date is today', () => {
|
||||
const todayString = DateTime.local().toISODate();
|
||||
const todayString = '2024-01-01'; // Using the mocked date
|
||||
const mockDate = `${todayString}T12:13:24`;
|
||||
const expected = 'Today';
|
||||
|
||||
@@ -162,25 +151,25 @@ describe('hasDatePassed', () => {
|
||||
});
|
||||
|
||||
it('should return true when passed past date', () => {
|
||||
const now = DateTime.local();
|
||||
const pastDate = now.minus({ day: 1 });
|
||||
const now = new Date();
|
||||
const pastDate = subDays(now, 1);
|
||||
|
||||
const result = hasDatePassed(pastDate.toJSDate());
|
||||
const result = hasDatePassed(pastDate);
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false when passed future date', () => {
|
||||
const now = DateTime.local();
|
||||
const futureDate = now.plus({ days: 1 });
|
||||
const now = new Date();
|
||||
const futureDate = addDays(now, 1);
|
||||
|
||||
const result = hasDatePassed(futureDate.toJSDate());
|
||||
const result = hasDatePassed(futureDate);
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return false when passed current date', () => {
|
||||
const now = DateTime.local();
|
||||
const now = new Date();
|
||||
|
||||
const result = hasDatePassed(now.toJSDate());
|
||||
const result = hasDatePassed(now);
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { isDate, isNumber, isString } from '@sniptt/guards';
|
||||
import {
|
||||
differenceInCalendarDays,
|
||||
differenceInDays,
|
||||
differenceInYears,
|
||||
format,
|
||||
formatDistance,
|
||||
formatDistanceToNow,
|
||||
isToday,
|
||||
isValid,
|
||||
parseISO,
|
||||
type Locale,
|
||||
} from 'date-fns';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { DateFormat } from '@/localization/constants/DateFormat';
|
||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { CustomError } from '@/error-handler/CustomError';
|
||||
@@ -16,14 +20,10 @@ import { i18n } from '@lingui/core';
|
||||
import { plural, t } from '@lingui/core/macro';
|
||||
import { logError } from './logError';
|
||||
|
||||
const getLuxonLocale = () => {
|
||||
return SOURCE_LOCALE === 'en' ? 'en-US' : SOURCE_LOCALE;
|
||||
};
|
||||
export const parseDate = (dateToParse: Date | string | number): Date => {
|
||||
if (dateToParse === 'now') return new Date();
|
||||
|
||||
export const parseDate = (dateToParse: Date | string | number) => {
|
||||
if (dateToParse === 'now') return DateTime.fromJSDate(new Date());
|
||||
|
||||
let formattedDate: DateTime | null = null;
|
||||
let formattedDate: Date | null = null;
|
||||
|
||||
if (!dateToParse) {
|
||||
throw new CustomError(
|
||||
@@ -31,11 +31,11 @@ export const parseDate = (dateToParse: Date | string | number) => {
|
||||
'INVALID_DATE_FORMAT',
|
||||
);
|
||||
} else if (isString(dateToParse)) {
|
||||
formattedDate = DateTime.fromISO(dateToParse);
|
||||
formattedDate = parseISO(dateToParse);
|
||||
} else if (isDate(dateToParse)) {
|
||||
formattedDate = DateTime.fromJSDate(dateToParse);
|
||||
formattedDate = dateToParse;
|
||||
} else if (isNumber(dateToParse)) {
|
||||
formattedDate = DateTime.fromMillis(dateToParse);
|
||||
formattedDate = new Date(dateToParse);
|
||||
}
|
||||
|
||||
if (!formattedDate) {
|
||||
@@ -45,26 +45,23 @@ export const parseDate = (dateToParse: Date | string | number) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (!formattedDate.isValid) {
|
||||
if (!isValid(formattedDate)) {
|
||||
throw new CustomError(
|
||||
`Invalid date passed to formatPastDate: "${dateToParse}"`,
|
||||
'INVALID_DATE_FORMAT',
|
||||
);
|
||||
}
|
||||
|
||||
return formattedDate.setLocale(getLuxonLocale());
|
||||
return formattedDate;
|
||||
};
|
||||
|
||||
const isSameDay = (a: DateTime, b: DateTime): boolean =>
|
||||
a.hasSame(b, 'day') && a.hasSame(b, 'month') && a.hasSame(b, 'year');
|
||||
|
||||
export const formatDate = (
|
||||
dateToFormat: Date | string | number,
|
||||
format: string,
|
||||
formatString: string,
|
||||
) => {
|
||||
try {
|
||||
const parsedDate = parseDate(dateToFormat);
|
||||
return parsedDate.toFormat(format);
|
||||
return format(parsedDate, formatString);
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
return '';
|
||||
@@ -74,17 +71,19 @@ export const formatDate = (
|
||||
export const beautifyExactDateTime = (
|
||||
dateToBeautify: Date | string | number,
|
||||
) => {
|
||||
const isToday = isSameDay(parseDate(dateToBeautify), DateTime.local());
|
||||
const dateFormat = isToday ? 'T' : 'DD · T';
|
||||
const parsedDate = parseDate(dateToBeautify);
|
||||
const isTodayDate = isToday(parsedDate);
|
||||
const dateFormat = isTodayDate ? 'HH:mm' : 'MMM d, yyyy · HH:mm';
|
||||
return formatDate(dateToBeautify, dateFormat);
|
||||
};
|
||||
|
||||
export const beautifyExactDate = (dateToBeautify: Date | string | number) => {
|
||||
const isToday = isSameDay(parseDate(dateToBeautify), DateTime.local());
|
||||
if (isToday) {
|
||||
const parsedDate = parseDate(dateToBeautify);
|
||||
const isTodayDate = isToday(parsedDate);
|
||||
if (isTodayDate) {
|
||||
return t`Today`;
|
||||
}
|
||||
return formatDate(dateToBeautify, 'DD');
|
||||
return formatDate(dateToBeautify, 'MMM d, yyyy');
|
||||
};
|
||||
|
||||
export const beautifyPastDateRelativeToNow = (
|
||||
@@ -95,7 +94,7 @@ export const beautifyPastDateRelativeToNow = (
|
||||
const parsedDate = parseDate(pastDate);
|
||||
const now = new Date();
|
||||
const diffInSeconds = Math.abs(
|
||||
(now.getTime() - parsedDate.toJSDate().getTime()) / 1000,
|
||||
(now.getTime() - parsedDate.getTime()) / 1000,
|
||||
);
|
||||
|
||||
// For very recent times (less than 30 seconds), show "now"
|
||||
@@ -103,7 +102,7 @@ export const beautifyPastDateRelativeToNow = (
|
||||
return t`now`;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(parsedDate.toJSDate(), {
|
||||
return formatDistanceToNow(parsedDate, {
|
||||
addSuffix: true,
|
||||
locale,
|
||||
includeSeconds: true,
|
||||
@@ -118,12 +117,7 @@ export const hasDatePassed = (date: Date | string | number) => {
|
||||
try {
|
||||
const parsedDate = parseDate(date);
|
||||
|
||||
return (
|
||||
differenceInCalendarDays(
|
||||
DateTime.local().toJSDate(),
|
||||
parsedDate.toJSDate(),
|
||||
) >= 1
|
||||
);
|
||||
return differenceInCalendarDays(new Date(), parsedDate) >= 1;
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
return false;
|
||||
@@ -144,13 +138,16 @@ export const beautifyDateDiff = (
|
||||
}
|
||||
|
||||
// Manual implementation for complex cases or when locale is not available
|
||||
const dateDiff = DateTime.fromISO(date).diff(
|
||||
dateToCompareWith ? DateTime.fromISO(dateToCompareWith) : DateTime.now(),
|
||||
['years', 'days'],
|
||||
);
|
||||
const fromDate = parseISO(date);
|
||||
const toDate = dateToCompareWith ? parseISO(dateToCompareWith) : new Date();
|
||||
|
||||
const years = Math.floor(dateDiff.years);
|
||||
const days = Math.floor(dateDiff.days);
|
||||
const years = differenceInYears(fromDate, toDate);
|
||||
// Calculate remaining days after accounting for full years
|
||||
const startDateForDayCalculation = new Date(toDate);
|
||||
startDateForDayCalculation.setFullYear(
|
||||
startDateForDayCalculation.getFullYear() + years,
|
||||
);
|
||||
const days = differenceInDays(fromDate, startDateForDayCalculation);
|
||||
|
||||
let result = '';
|
||||
|
||||
@@ -179,7 +176,7 @@ export const beautifyDateDiff = (
|
||||
};
|
||||
|
||||
export const formatToHumanReadableDate = (date: Date | string) => {
|
||||
const parsedJSDate = parseDate(date).toJSDate();
|
||||
const parsedJSDate = parseDate(date);
|
||||
|
||||
return i18n.date(parsedJSDate, { dateStyle: 'medium' });
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ export const formatToHumanReadableMonth = (
|
||||
date: Date | string,
|
||||
timeZone: string,
|
||||
) => {
|
||||
const parsedJSDate = parseDate(date).toJSDate();
|
||||
const parsedJSDate = parseDate(date);
|
||||
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
month: 'short',
|
||||
@@ -16,7 +16,7 @@ export const formatToHumanReadableDay = (
|
||||
date: Date | string,
|
||||
timeZone: string,
|
||||
) => {
|
||||
const parsedJSDate = parseDate(date).toJSDate();
|
||||
const parsedJSDate = parseDate(date);
|
||||
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
day: 'numeric',
|
||||
@@ -28,7 +28,7 @@ export const formatToHumanReadableTime = (
|
||||
date: Date | string,
|
||||
timeZone: string,
|
||||
) => {
|
||||
const parsedJSDate = parseDate(date).toJSDate();
|
||||
const parsedJSDate = parseDate(date);
|
||||
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
hour: 'numeric',
|
||||
|
||||
@@ -16,5 +16,5 @@ export const sortFieldMetadataItem = (
|
||||
if (!dateA) return 1;
|
||||
if (!dateB) return -1;
|
||||
|
||||
return dateB.diff(dateA).milliseconds > 0 ? -1 : 1;
|
||||
return dateB.getTime() - dateA.getTime() > 0 ? -1 : 1;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DateFormat } from '@/localization/constants/DateFormat';
|
||||
import { FieldDateDisplayFormat } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { enUS } from 'date-fns/locale';
|
||||
import { DateTime } from 'luxon';
|
||||
import { subDays } from 'date-fns';
|
||||
import { formatDateString } from '~/utils/string/formatDateString';
|
||||
|
||||
describe('formatDateString', () => {
|
||||
@@ -31,7 +31,7 @@ describe('formatDateString', () => {
|
||||
});
|
||||
|
||||
it('should format date as relative when displayFormat is set to RELATIVE', () => {
|
||||
const mockDate = DateTime.now().minus({ days: 2 }).toISO();
|
||||
const mockDate = subDays(new Date(), 2).toISOString();
|
||||
const mockRelativeDate = '2 days ago';
|
||||
|
||||
const result = formatDateString({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { DateFormat } from '@/localization/constants/DateFormat';
|
||||
import { TimeFormat } from '@/localization/constants/TimeFormat';
|
||||
import { FieldDateDisplayFormat } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { subDays } from 'date-fns';
|
||||
import { enUS } from 'date-fns/locale';
|
||||
import { DateTime } from 'luxon';
|
||||
import { formatDateTimeString } from '~/utils/string/formatDateTimeString';
|
||||
|
||||
describe('formatDateTimeString', () => {
|
||||
@@ -33,7 +33,7 @@ describe('formatDateTimeString', () => {
|
||||
});
|
||||
|
||||
it('should format date as relative when displayFormat is RELATIVE', () => {
|
||||
const mockDate = DateTime.now().minus({ days: 2 }).toISO();
|
||||
const mockDate = subDays(new Date(), 2).toISOString();
|
||||
const mockRelativeDate = '2 days ago';
|
||||
|
||||
const result = formatDateTimeString({
|
||||
|
||||
Reference in New Issue
Block a user