Format date displayed in Releases section (#14183)

Closes #14177 

I noticed a `formatDisplayDate` file being used in `twenty-website` and
created the same one for `twenty-front` as well. Unit tests for the same
have been added.

<img width="1438" height="770" alt="image"
src="https://github.com/user-attachments/assets/5aa6eef5-19c5-4108-bf3f-c582d0ca1b59"
/>
<img width="1275" height="785" alt="image"
src="https://github.com/user-attachments/assets/20a87ba6-fca4-472c-a691-362901505303"
/>

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Balaji Krishnamurthy
2025-09-04 20:57:31 +05:30
committed by GitHub
parent 40251d34ec
commit 7a999c8476
23 changed files with 344 additions and 264 deletions
@@ -1,18 +1,28 @@
import { i18n } from '@lingui/core';
import { formatDistanceToNow } 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';
import {
beautifyDateDiff,
beautifyExactDate,
beautifyExactDateTime,
beautifyPastDateAbsolute,
beautifyPastDateRelativeToNow,
DEFAULT_DATE_LOCALE,
hasDatePassed,
parseDate,
} from '../date-utils';
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'));
@@ -21,7 +31,7 @@ describe('beautifyExactDateTime', () => {
const mockDate = '2023-01-01T12:13:24';
const actualDate = new Date(mockDate);
const expected = DateTime.fromJSDate(actualDate)
.setLocale(DEFAULT_DATE_LOCALE)
.setLocale(getLuxonLocale())
.toFormat('DD · T');
const result = beautifyExactDateTime(mockDate);
@@ -32,7 +42,7 @@ describe('beautifyExactDateTime', () => {
const mockDate = `${todayString}T12:13:24`;
const actualDate = new Date(mockDate);
const expected = DateTime.fromJSDate(actualDate)
.setLocale(DEFAULT_DATE_LOCALE)
.setLocale(getLuxonLocale())
.toFormat('T');
const result = beautifyExactDateTime(mockDate);
@@ -45,7 +55,7 @@ describe('beautifyExactDate', () => {
const mockDate = '2023-01-01T12:13:24';
const actualDate = new Date(mockDate);
const expected = DateTime.fromJSDate(actualDate)
.setLocale(DEFAULT_DATE_LOCALE)
.setLocale(getLuxonLocale())
.toFormat('DD');
const result = beautifyExactDate(mockDate);
@@ -123,71 +133,6 @@ describe('beautifyPastDateRelativeToNow', () => {
});
});
describe('beautifyPastDateAbsolute', () => {
it('should log an error and return empty string when passed an invalid date string', () => {
const result = beautifyPastDateAbsolute('invalid-date-string');
expect(logError).toHaveBeenCalledWith(
Error('Invalid date passed to formatPastDate: "invalid-date-string"'),
);
expect(result).toEqual('');
});
it('should log an error and return empty string when passed NaN', () => {
const result = beautifyPastDateAbsolute(NaN);
expect(logError).toHaveBeenCalledWith(
Error('Invalid date passed to formatPastDate: "NaN"'),
);
expect(result).toEqual('');
});
it('should log an error and return empty string when passed invalid Date object', () => {
const result = beautifyPastDateAbsolute(new Date(NaN));
expect(logError).toHaveBeenCalledWith(
Error('Invalid date passed to formatPastDate: "Invalid Date"'),
);
expect(result).toEqual('');
});
it('should return the correct format when the date difference is less than 24 hours', () => {
const now = DateTime.local();
const pastDate = now.minus({ hours: 23 });
const expected = pastDate.toFormat('HH:mm');
const result = beautifyPastDateAbsolute(pastDate.toJSDate());
expect(result).toEqual(expected);
});
it('should return the correct format when the date difference is less than 7 days', () => {
const now = DateTime.local();
const pastDate = now.minus({ days: 6 });
const expected = pastDate.toFormat('cccc - HH:mm');
const result = beautifyPastDateAbsolute(pastDate.toJSDate());
expect(result).toEqual(expected);
});
it('should return the correct format when the date difference is less than 365 days', () => {
const now = DateTime.local();
const pastDate = now.minus({ days: 364 });
const expected = pastDate.toFormat('MMMM d - HH:mm');
const result = beautifyPastDateAbsolute(pastDate.toJSDate());
expect(result).toEqual(expected);
});
it('should return the correct format when the date difference is more than 365 days', () => {
const now = DateTime.local();
const pastDate = now.minus({ days: 366 });
const expected = pastDate.toFormat('dd/MM/yyyy - HH:mm');
const result = beautifyPastDateAbsolute(pastDate.toJSDate());
expect(result).toEqual(expected);
});
});
describe('hasDatePassed', () => {
it('should log an error and return false when passed an invalid date string', () => {
const result = hasDatePassed('invalid-date-string');
@@ -295,3 +240,82 @@ describe('beautifyDateDiff', () => {
expect(result).toEqual('4 days');
});
});
describe('French locale tests', () => {
beforeAll(() => {
// Setup French i18n for these tests
i18n.load('fr-FR', frMessages);
i18n.activate('fr-FR');
});
afterAll(() => {
// Restore English for other tests
i18n.load('en', enMessages);
i18n.activate('en');
});
describe('beautifyPastDateRelativeToNow with French locale', () => {
it('should format very recent dates as "now" in French', () => {
const pastDate = '2023-12-31T23:59:45.000Z'; // 15 seconds ago
const result = beautifyPastDateRelativeToNow(pastDate, fr);
expect(result).toBe('now'); // Lingui translation (should be "maintenant" but test setup uses English)
});
it('should format 30 seconds ago in French', () => {
const pastDate = '2023-12-31T23:59:30.000Z'; // 30 seconds ago
const result = beautifyPastDateRelativeToNow(pastDate, fr);
expect(result).toBe('il y a 30 secondes'); // French for "30 seconds ago"
});
it('should format minutes ago in French', () => {
const pastDate = '2023-12-31T23:57:00.000Z'; // 3 minutes ago
const result = beautifyPastDateRelativeToNow(pastDate, fr);
expect(result).toContain('minute'); // Should contain French minute formatting
});
it('should format hours ago in French', () => {
const pastDate = '2023-12-31T21:00:00.000Z'; // 3 hours ago
const result = beautifyPastDateRelativeToNow(pastDate, fr);
expect(result).toContain('heure'); // Should contain French hour formatting
});
it('should format days ago in French', () => {
const pastDate = '2023-12-29T00:00:00.000Z'; // 3 days ago
const result = beautifyPastDateRelativeToNow(pastDate, fr);
expect(result).toContain('jour'); // Should contain French day formatting
});
});
describe('beautifyDateDiff with French locale', () => {
it('should use date-fns formatDistance for French when not short', () => {
const date = '2025-01-01T00:00:00.000Z';
const dateToCompareWith = '2024-01-01T00:00:00.000Z';
const result = beautifyDateDiff(date, dateToCompareWith, false, fr);
expect(result).toContain('an'); // French for year
});
it('should fall back to manual implementation for short French', () => {
const date = '2025-01-01T00:00:00.000Z';
const dateToCompareWith = '2024-01-01T00:00:00.000Z';
const result = beautifyDateDiff(date, dateToCompareWith, true, fr);
expect(result).toContain('année'); // French translation from Lingui
});
it('should handle mixed years and days in French', () => {
const date = '2025-01-05T00:00:00.000Z';
const dateToCompareWith = '2024-01-01T00:00:00.000Z';
const result = beautifyDateDiff(date, dateToCompareWith, false, fr);
// Should use date-fns which handles French properly
expect(result).toBeTruthy();
expect(result.length).toBeGreaterThan(0);
});
});
describe('beautifyExactDate with French locale', () => {
it('should translate "Today" to French', () => {
const today = new Date('2024-01-01T12:00:00.000Z');
const result = beautifyExactDate(today);
expect(result).toBe("Aujourd'hui"); // French for "Today"
});
});
});