From 03667947aa84339a4c9207a2a6ed21087bd927e5 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Thu, 2 Apr 2026 17:47:18 +0200 Subject: [PATCH] feat(web): add formatDmTimestamp utility with smart date formatting --- packages/web/src/utils/dmFormatters.test.ts | 60 +++++++++++++++++++++ packages/web/src/utils/dmFormatters.ts | 30 +++++++++++ 2 files changed, 90 insertions(+) create mode 100644 packages/web/src/utils/dmFormatters.test.ts create mode 100644 packages/web/src/utils/dmFormatters.ts diff --git a/packages/web/src/utils/dmFormatters.test.ts b/packages/web/src/utils/dmFormatters.test.ts new file mode 100644 index 00000000..5e8e87b3 --- /dev/null +++ b/packages/web/src/utils/dmFormatters.test.ts @@ -0,0 +1,60 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { formatDmTimestamp } from './dmFormatters'; + +/** Build a local-time Date: new Date(year, month-1, day, hour, minute) as a timestamp. */ +function localTs(year: number, month: number, day: number, hour = 12, minute = 0): number { + return new Date(year, month - 1, day, hour, minute).getTime(); +} + +describe('formatDmTimestamp', () => { + afterEach(() => { + vi.useRealTimers(); + }); + + it('shows time for today', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(2026, 3, 2, 16, 0)); // Apr 2 2026, 4:00 PM local + + const twoHoursAgo = localTs(2026, 4, 2, 14, 0); // Apr 2 2026, 2:00 PM local + const result = formatDmTimestamp(twoHoursAgo); + // Should be a time string like "2:00 PM" — not "Yesterday" or a date + expect(result).not.toBe('Yesterday'); + expect(result).not.toMatch(/\d{4}/); // no year + expect(result).toMatch(/\d{1,2}/); // has a number (hour) + }); + + it('shows "Yesterday" for yesterday', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(2026, 3, 2, 16, 0)); // Apr 2 2026 + + const yesterday = localTs(2026, 4, 1, 12, 0); // Apr 1 2026 + expect(formatDmTimestamp(yesterday)).toBe('Yesterday'); + }); + + it('shows month and day for this year', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(2026, 3, 2, 16, 0)); // Apr 2 2026 + + const marchDate = localTs(2026, 3, 15, 12, 0); // Mar 15 2026 + const result = formatDmTimestamp(marchDate); + expect(result).toMatch(/Mar\s+15/); + }); + + it('shows month, day and year for previous years', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(2026, 3, 2, 16, 0)); // Apr 2 2026 + + const lastYear = localTs(2025, 12, 14, 12, 0); // Dec 14 2025 + const result = formatDmTimestamp(lastYear); + expect(result).toMatch(/Dec\s+14/); + expect(result).toMatch(/2025/); + }); + + it('handles midnight boundary correctly', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(2026, 3, 2, 0, 5)); // Apr 2 2026, 00:05 local + + const lastNight = localTs(2026, 4, 1, 23, 55); // Apr 1 2026, 23:55 local + expect(formatDmTimestamp(lastNight)).toBe('Yesterday'); + }); +}); diff --git a/packages/web/src/utils/dmFormatters.ts b/packages/web/src/utils/dmFormatters.ts new file mode 100644 index 00000000..0915b9a7 --- /dev/null +++ b/packages/web/src/utils/dmFormatters.ts @@ -0,0 +1,30 @@ +/** + * Smart timestamp for DM sidebar items. + * Today → time ("4:32 PM"), Yesterday → "Yesterday", + * This year → "Mar 31", Older → "Dec 14, 2025" + */ +export function formatDmTimestamp(createdAt: number): string { + const now = new Date(); + const date = new Date(createdAt); + + // Build "start of today" and "start of yesterday" in local time + const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const startOfYesterday = new Date(startOfToday.getTime() - 86400000); + + if (date >= startOfToday) { + // Today — show time + return date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' }); + } + + if (date >= startOfYesterday) { + return 'Yesterday'; + } + + if (date.getFullYear() === now.getFullYear()) { + // This year — "Mar 31" + return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }); + } + + // Previous year — "Dec 14, 2025" + return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }); +}