From d0dc52f92584e6d364f9ee6d68af96689c224b98 Mon Sep 17 00:00:00 2001
From: ManishMadan2882
Date: Tue, 16 Jun 2026 01:44:54 +0200
Subject: [PATCH] (feat:dateTime) consistent d/m/y
---
frontend/src/admin/AdminUI.tsx | 15 +--
frontend/src/agents/AgentLogs.tsx | 3 +-
frontend/src/agents/SharedAgentCard.tsx | 14 +-
.../src/agents/schedules/RunDetailDrawer.tsx | 6 +-
frontend/src/agents/schedules/RunLog.tsx | 6 +-
.../agents/schedules/ScheduleFormModal.tsx | 9 +-
.../schedules/SchedulerToolCallCard.tsx | 6 +-
.../src/agents/schedules/SchedulesView.tsx | 8 +-
frontend/src/components/FilePicker.tsx | 4 +-
.../src/conversation/SharedConversation.tsx | 4 +-
frontend/src/settings/RemoteDeviceConfig.tsx | 8 +-
frontend/src/utils/dateTimeUtils.test.ts | 43 ++++++
frontend/src/utils/dateTimeUtils.ts | 125 ++++++++++--------
13 files changed, 135 insertions(+), 116 deletions(-)
create mode 100644 frontend/src/utils/dateTimeUtils.test.ts
diff --git a/frontend/src/admin/AdminUI.tsx b/frontend/src/admin/AdminUI.tsx
index c83055f5..e62ab82b 100644
--- a/frontend/src/admin/AdminUI.tsx
+++ b/frontend/src/admin/AdminUI.tsx
@@ -1,4 +1,5 @@
import Spinner from '../components/Spinner';
+import { formatDateOnly, formatDateTime } from '../utils/dateTimeUtils';
export function Loading() {
return (
@@ -68,21 +69,11 @@ export function Pill({
}
export function fmtDate(value?: string | null): string {
- if (!value) return '—';
- const date = new Date(value);
- return Number.isNaN(date.getTime()) ? String(value) : date.toLocaleString();
+ return value ? formatDateTime(value) : '—';
}
export function fmtDateShort(value?: string | null): string {
- if (!value) return '—';
- const date = new Date(value);
- return Number.isNaN(date.getTime())
- ? String(value)
- : date.toLocaleDateString(undefined, {
- month: 'short',
- day: 'numeric',
- year: 'numeric',
- });
+ return value ? formatDateOnly(value) : '—';
}
export function fmtRelative(value?: string | null): string {
diff --git a/frontend/src/agents/AgentLogs.tsx b/frontend/src/agents/AgentLogs.tsx
index ec9fc53e..59994701 100644
--- a/frontend/src/agents/AgentLogs.tsx
+++ b/frontend/src/agents/AgentLogs.tsx
@@ -8,6 +8,7 @@ import Spinner from '../components/Spinner';
import { selectToken } from '../preferences/preferenceSlice';
import Analytics from '../settings/Analytics';
import Logs from '../settings/Logs';
+import { formatDateTime } from '../utils/dateTimeUtils';
import AgentPageHeader from './AgentPageHeader';
import { Agent } from './types';
@@ -59,7 +60,7 @@ export default function AgentLogs() {
{agent.last_used_at
? t('agents.logs.lastUsedAt') +
' ' +
- new Date(agent.last_used_at).toLocaleString()
+ formatDateTime(agent.last_used_at)
: t('agents.logs.noUsageHistory')}
diff --git a/frontend/src/agents/SharedAgentCard.tsx b/frontend/src/agents/SharedAgentCard.tsx
index f7745fe0..d5fb8e42 100644
--- a/frontend/src/agents/SharedAgentCard.tsx
+++ b/frontend/src/agents/SharedAgentCard.tsx
@@ -4,6 +4,7 @@ import EditIcon from '../assets/edit.svg';
import ToolIcon from '../components/ToolIcon';
import { Avatar } from '../components/ui/avatar';
import { Button } from '../components/ui/button';
+import { formatDateTime } from '../utils/dateTimeUtils';
import { getToolDisplayName } from '../utils/toolUtils';
import { Agent } from './types';
@@ -61,18 +62,7 @@ export default function SharedAgentCard({
)}
{agent.shared_metadata?.shared_at && (
- Shared on{' '}
- {new Date(agent.shared_metadata.shared_at).toLocaleString(
- 'en-US',
- {
- month: 'long',
- day: 'numeric',
- year: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- hour12: true,
- },
- )}
+ Shared on {formatDateTime(agent.shared_metadata.shared_at)}
)}
diff --git a/frontend/src/agents/schedules/RunDetailDrawer.tsx b/frontend/src/agents/schedules/RunDetailDrawer.tsx
index 7cd85876..4c1359bd 100644
--- a/frontend/src/agents/schedules/RunDetailDrawer.tsx
+++ b/frontend/src/agents/schedules/RunDetailDrawer.tsx
@@ -1,6 +1,7 @@
import { useEffect } from 'react';
import { Button } from '../../components/ui/button';
+import { formatDateTime } from '../../utils/dateTimeUtils';
import type { ScheduleRun } from '../types/schedule';
import ScheduleStatusBadge from './StatusBadge';
@@ -10,10 +11,7 @@ export type RunDetailDrawerProps = {
};
const formatTimestamp = (value?: string | null): string => {
- if (!value) return '—';
- const d = new Date(value);
- if (Number.isNaN(d.getTime())) return value;
- return d.toLocaleString();
+ return value ? formatDateTime(value) : '—';
};
/** Side drawer with a single run's output / error (terminal-state only). */
diff --git a/frontend/src/agents/schedules/RunLog.tsx b/frontend/src/agents/schedules/RunLog.tsx
index 364237b0..83b0913c 100644
--- a/frontend/src/agents/schedules/RunLog.tsx
+++ b/frontend/src/agents/schedules/RunLog.tsx
@@ -4,6 +4,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { Button } from '../../components/ui/button';
import { selectToken } from '../../preferences/preferenceSlice';
import type { AppDispatch, RootState } from '../../store';
+import { formatDateTime } from '../../utils/dateTimeUtils';
import type { ScheduleRun } from '../types/schedule';
import ScheduleStatusBadge from './StatusBadge';
import { loadRunsForSchedule, selectRunsForSchedule } from './schedulesSlice';
@@ -14,10 +15,7 @@ export type RunLogProps = {
};
const formatTimestamp = (value?: string | null): string => {
- if (!value) return '—';
- const d = new Date(value);
- if (Number.isNaN(d.getTime())) return value;
- return d.toLocaleString();
+ return value ? formatDateTime(value) : '—';
};
/** Paginated run log for a schedule (SSE updates merge via schedulesSlice). */
diff --git a/frontend/src/agents/schedules/ScheduleFormModal.tsx b/frontend/src/agents/schedules/ScheduleFormModal.tsx
index 1b7c48c4..d94d224d 100644
--- a/frontend/src/agents/schedules/ScheduleFormModal.tsx
+++ b/frontend/src/agents/schedules/ScheduleFormModal.tsx
@@ -20,6 +20,7 @@ import { TimePicker } from '@/components/ui/time-picker';
import { cn } from '@/lib/utils';
import { Modal } from '../../components/ui/modal';
+import { formatDateOnly } from '../../utils/dateTimeUtils';
import type { Schedule, ScheduleCreatePayload } from '../types/schedule';
import {
browserTimezone,
@@ -91,13 +92,7 @@ const dateToDateString = (d: Date): string => {
};
const formatDateLabel = (value: string): string => {
- const d = dateStringToDate(value);
- if (!d) return '';
- return d.toLocaleDateString(undefined, {
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- });
+ return formatDateOnly(value);
};
/** Create/edit a Schedule via a modal dialog. */
diff --git a/frontend/src/agents/schedules/SchedulerToolCallCard.tsx b/frontend/src/agents/schedules/SchedulerToolCallCard.tsx
index 0597b1c6..bbfc616b 100644
--- a/frontend/src/agents/schedules/SchedulerToolCallCard.tsx
+++ b/frontend/src/agents/schedules/SchedulerToolCallCard.tsx
@@ -4,6 +4,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { Button } from '../../components/ui/button';
import { selectToken } from '../../preferences/preferenceSlice';
import type { AppDispatch } from '../../store';
+import { formatDateTime } from '../../utils/dateTimeUtils';
import { deleteSchedule, loadSchedulesForAgent } from './schedulesSlice';
export type SchedulerToolCallCardProps = {
@@ -18,10 +19,7 @@ export type SchedulerToolCallCardProps = {
};
const formatTimestamp = (value?: string | null): string => {
- if (!value) return '—';
- const d = new Date(value);
- if (Number.isNaN(d.getTime())) return value;
- return d.toLocaleString();
+ return value ? formatDateTime(value) : '—';
};
const parseResult = (result: unknown): Record | null => {
diff --git a/frontend/src/agents/schedules/SchedulesView.tsx b/frontend/src/agents/schedules/SchedulesView.tsx
index 56d1b6fb..bc755f22 100644
--- a/frontend/src/agents/schedules/SchedulesView.tsx
+++ b/frontend/src/agents/schedules/SchedulesView.tsx
@@ -10,6 +10,7 @@ import ConfirmationModal from '../../modals/ConfirmationModal';
import { ActiveState } from '../../models/misc';
import { selectToken } from '../../preferences/preferenceSlice';
import type { AppDispatch, RootState } from '../../store';
+import { formatDateTime } from '../../utils/dateTimeUtils';
import AgentPageHeader from '../AgentPageHeader';
import type { Agent } from '../types';
import type {
@@ -33,10 +34,7 @@ import {
} from './schedulesSlice';
const formatTimestamp = (value?: string | null): string => {
- if (!value) return '—';
- const d = new Date(value);
- if (Number.isNaN(d.getTime())) return value;
- return d.toLocaleString();
+ return value ? formatDateTime(value) : '—';
};
/** Standalone Schedules page for an agent: list, create, edit, pause, run, delete. */
@@ -170,7 +168,7 @@ export default function SchedulesView() {
{agent.last_used_at
? t('agents.logs.lastUsedAt') +
' ' +
- new Date(agent.last_used_at).toLocaleString()
+ formatDateTime(agent.last_used_at)
: t('agents.logs.noUsageHistory')}
diff --git a/frontend/src/components/FilePicker.tsx b/frontend/src/components/FilePicker.tsx
index ba0b4ffb..d8437d42 100644
--- a/frontend/src/components/FilePicker.tsx
+++ b/frontend/src/components/FilePicker.tsx
@@ -3,7 +3,7 @@ import React, { useState, useEffect, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import userService from '../api/services/userService';
import { formatBytes } from '../utils/stringUtils';
-import { formatDate } from '../utils/dateTimeUtils';
+import { formatDateTime } from '../utils/dateTimeUtils';
import {
getSessionToken,
setSessionToken,
@@ -599,7 +599,7 @@ export const FilePicker: React.FC = ({
- {formatDate(file.modifiedTime)}
+ {formatDateTime(file.modifiedTime)}
{file.size ? formatBytes(file.size) : '-'}
diff --git a/frontend/src/conversation/SharedConversation.tsx b/frontend/src/conversation/SharedConversation.tsx
index 133c51ea..3a732467 100644
--- a/frontend/src/conversation/SharedConversation.tsx
+++ b/frontend/src/conversation/SharedConversation.tsx
@@ -8,7 +8,7 @@ import MessageInput from '../components/MessageInput';
import { Button } from '../components/ui/button';
import { selectToken } from '../preferences/preferenceSlice';
import { AppDispatch } from '../store';
-import { formatDate } from '../utils/dateTimeUtils';
+import { formatDateTime } from '../utils/dateTimeUtils';
import ConversationMessages from './ConversationMessages';
import {
addQuery,
@@ -69,7 +69,7 @@ export const SharedConversation = () => {
setFetchedData({
queries: data.queries,
title: data.title,
- date: formatDate(data.timestamp),
+ date: formatDateTime(data.timestamp),
identifier,
}),
);
diff --git a/frontend/src/settings/RemoteDeviceConfig.tsx b/frontend/src/settings/RemoteDeviceConfig.tsx
index a037aa42..f8426410 100644
--- a/frontend/src/settings/RemoteDeviceConfig.tsx
+++ b/frontend/src/settings/RemoteDeviceConfig.tsx
@@ -30,6 +30,7 @@ import {
import ConfirmationModal from '../modals/ConfirmationModal';
import { ActiveState } from '../models/misc';
import { selectToken } from '../preferences/preferenceSlice';
+import { formatDateTime } from '../utils/dateTimeUtils';
import { UserToolType } from './types';
/** ms-since-last-seen threshold for the online pill. */
@@ -43,12 +44,7 @@ function isOnline(device: Device | null): boolean {
}
function formatTimestamp(value: string | null | undefined): string {
- if (!value) return '-';
- try {
- return new Date(value).toLocaleString();
- } catch {
- return value;
- }
+ return value ? formatDateTime(value) : '-';
}
/** Compact relative span (e.g. "12s", "5m", "3h", "2d") since `value`. */
diff --git a/frontend/src/utils/dateTimeUtils.test.ts b/frontend/src/utils/dateTimeUtils.test.ts
new file mode 100644
index 00000000..36184377
--- /dev/null
+++ b/frontend/src/utils/dateTimeUtils.test.ts
@@ -0,0 +1,43 @@
+import { describe, expect, it } from 'vitest';
+
+import { formatDate, formatDateOnly, formatDateTime } from './dateTimeUtils';
+
+describe('dateTimeUtils', () => {
+ it('formats date-only values as DD/MM/YYYY', () => {
+ expect(formatDate('2026-05-21')).toBe('21/05/2026');
+ expect(formatDateOnly('2026-05-21')).toBe('21/05/2026');
+ });
+
+ it('formats local datetime strings with a 24-hour time', () => {
+ expect(formatDate('2026-05-21 14:30:00')).toBe('21/05/2026, 14:30');
+ expect(formatDateTime('2026-05-21T14:30:00')).toBe('21/05/2026, 14:30');
+ });
+
+ it('does not invent a midnight time for date-only values', () => {
+ expect(formatDateTime('2026-05-21')).toBe('21/05/2026');
+ });
+
+ it('supports ISO timestamps with fractional seconds and offsets', () => {
+ const value = '2026-06-09T00:51:20.427827+00:00';
+ const expected = new Intl.DateTimeFormat('en-GB', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ hour12: false,
+ }).format(new Date(value));
+
+ expect(formatDate(value)).toBe(expected);
+ expect(formatDateTime(value)).toBe(expected);
+ });
+
+ it('parses natural-language dates into DD/MM/YYYY', () => {
+ expect(formatDate('May 21, 2026')).toBe('21/05/2026');
+ });
+
+ it('returns the original value when parsing fails', () => {
+ expect(formatDate('not a date')).toBe('not a date');
+ expect(formatDateTime('still not a date')).toBe('still not a date');
+ });
+});
diff --git a/frontend/src/utils/dateTimeUtils.ts b/frontend/src/utils/dateTimeUtils.ts
index ce7a2c39..1f287cab 100644
--- a/frontend/src/utils/dateTimeUtils.ts
+++ b/frontend/src/utils/dateTimeUtils.ts
@@ -1,59 +1,70 @@
-export function formatDate(dateString: string): string {
- if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/.test(dateString)) {
- // ISO 8601 format
- const dateTime = new Date(dateString);
- return dateTime.toLocaleDateString('en-US', {
- month: 'short',
- day: 'numeric',
- year: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- });
- } else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(dateString)) {
- const dateTime = new Date(dateString);
- return dateTime.toLocaleDateString('en-US', {
- month: 'short',
- day: 'numeric',
- year: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- });
- } else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(dateString)) {
- const dateTime = new Date(dateString);
- return dateTime.toLocaleDateString('en-US', {
- month: 'short',
- day: 'numeric',
- year: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- });
- } else if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
- // `new Date('YYYY-MM-DD')` parses as UTC midnight, so rendering it
- // in a timezone west of UTC shows the previous day. Construct from
- // parts so the date is interpreted in local time (matching the
- // space-separated hourly buckets, which already parse as local).
- const [year, month, day] = dateString.split('-').map(Number);
- const date = new Date(year, month - 1, day);
- return date.toLocaleDateString('en-US', {
- month: 'short',
- day: 'numeric',
- year: 'numeric',
- });
- } else if (
- /^[A-Za-z]{3}, \d{2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/.test(
- dateString,
- )
- ) {
- // Format: "Fri, 08 Jul 2025 06:00:00 GMT"
- const dateTime = new Date(dateString);
- return dateTime.toLocaleDateString('en-US', {
- month: 'short',
- day: 'numeric',
- year: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- });
- } else {
- return dateString;
+type FormatMode = 'auto' | 'date' | 'dateTime';
+
+const DATE_ONLY_REGEX = /^(\d{4})-(\d{2})-(\d{2})$/;
+const LOCAL_DATE_TIME_REGEX =
+ /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?$/;
+const HAS_TIME_REGEX = /\d{1,2}:\d{2}/;
+
+const DATE_FORMATTER = new Intl.DateTimeFormat('en-GB', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric',
+});
+
+const DATE_TIME_FORMATTER = new Intl.DateTimeFormat('en-GB', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ hour12: false,
+});
+
+function parseDateValue(value: string): Date | null {
+ const dateOnlyMatch = DATE_ONLY_REGEX.exec(value);
+ if (dateOnlyMatch) {
+ const [, year, month, day] = dateOnlyMatch;
+ return new Date(Number(year), Number(month) - 1, Number(day));
}
+
+ const localDateTimeMatch = LOCAL_DATE_TIME_REGEX.exec(value);
+ if (localDateTimeMatch) {
+ const [, year, month, day, hour, minute, second = '0'] = localDateTimeMatch;
+ return new Date(
+ Number(year),
+ Number(month) - 1,
+ Number(day),
+ Number(hour),
+ Number(minute),
+ Number(second),
+ );
+ }
+
+ const parsed = new Date(value);
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
+}
+
+function formatDateValue(value: string, mode: FormatMode): string {
+ const parsed = parseDateValue(value);
+ if (!parsed) return value;
+
+ const hasTime = HAS_TIME_REGEX.test(value);
+ const shouldIncludeTime =
+ mode === 'dateTime' ? hasTime : mode === 'auto' && hasTime;
+
+ return shouldIncludeTime
+ ? DATE_TIME_FORMATTER.format(parsed)
+ : DATE_FORMATTER.format(parsed);
+}
+
+export function formatDate(dateString: string): string {
+ return formatDateValue(dateString, 'auto');
+}
+
+export function formatDateOnly(dateString: string): string {
+ return formatDateValue(dateString, 'date');
+}
+
+export function formatDateTime(dateString: string): string {
+ return formatDateValue(dateString, 'dateTime');
}