fixed 24 hour locale system bug (#11719)
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
||||
} from '../utils/nextChatExtensions';
|
||||
import { formatAcpError } from '../acp/errors';
|
||||
import { toastError } from '../toasts';
|
||||
import { formatClockDisplay } from '../utils/timeUtils';
|
||||
|
||||
const i18n = defineMessages({
|
||||
goodMorning: { id: 'hub.goodMorning', defaultMessage: 'Good morning' },
|
||||
@@ -34,19 +35,14 @@ const i18n = defineMessages({
|
||||
goodEvening: { id: 'hub.goodEvening', defaultMessage: 'Good evening' },
|
||||
});
|
||||
|
||||
function useClock(): { time: string; meridiem: string; hour: number } {
|
||||
function useClock() {
|
||||
const [now, setNow] = useState(() => new Date());
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => setNow(new Date()), 30_000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const hour = now.getHours();
|
||||
const minutes = now.getMinutes();
|
||||
const meridiem = hour >= 12 ? 'PM' : 'AM';
|
||||
const displayHour = ((hour + 11) % 12) + 1;
|
||||
const time = `${displayHour}:${String(minutes).padStart(2, '0')}`;
|
||||
return { time, meridiem, hour };
|
||||
return formatClockDisplay(now);
|
||||
}
|
||||
|
||||
export default function Hub({
|
||||
@@ -151,7 +147,9 @@ export default function Hub({
|
||||
<span className="text-6xl font-light text-text-primary tracking-tight tabular-nums">
|
||||
{time}
|
||||
</span>
|
||||
<span className="text-2xl font-light text-text-secondary">{meridiem}</span>
|
||||
{meridiem ? (
|
||||
<span className="text-2xl font-light text-text-secondary">{meridiem}</span>
|
||||
) : null}
|
||||
</div>
|
||||
<p className="text-xl text-text-secondary mb-6">{greeting}</p>
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { formatClockDisplay, formatMessageTimestamp } from './timeUtils';
|
||||
|
||||
describe('timeUtils', () => {
|
||||
describe('formatClockDisplay', () => {
|
||||
it('formats 12-hour locale with AM/PM meridiem (en-US)', () => {
|
||||
// 8:49 PM
|
||||
const eveningDate = new Date(2026, 8, 1, 20, 49, 0);
|
||||
const eveningResult = formatClockDisplay(eveningDate, 'en-US');
|
||||
expect(eveningResult.time).toBe('8:49');
|
||||
expect(eveningResult.meridiem).toBe('PM');
|
||||
expect(eveningResult.hour).toBe(20);
|
||||
|
||||
// 8:49 AM
|
||||
const morningDate = new Date(2026, 8, 1, 8, 49, 0);
|
||||
const morningResult = formatClockDisplay(morningDate, 'en-US');
|
||||
expect(morningResult.time).toBe('8:49');
|
||||
expect(morningResult.meridiem).toBe('AM');
|
||||
expect(morningResult.hour).toBe(8);
|
||||
|
||||
// Midnight (12:00 AM)
|
||||
const midnight = new Date(2026, 8, 1, 0, 0, 0);
|
||||
const midnightResult = formatClockDisplay(midnight, 'en-US');
|
||||
expect(midnightResult.time).toBe('12:00');
|
||||
expect(midnightResult.meridiem).toBe('AM');
|
||||
expect(midnightResult.hour).toBe(0);
|
||||
|
||||
// Noon (12:00 PM)
|
||||
const noon = new Date(2026, 8, 1, 12, 0, 0);
|
||||
const noonResult = formatClockDisplay(noon, 'en-US');
|
||||
expect(noonResult.time).toBe('12:00');
|
||||
expect(noonResult.meridiem).toBe('PM');
|
||||
expect(noonResult.hour).toBe(12);
|
||||
});
|
||||
|
||||
it('formats 24-hour locale without meridiem (en-GB)', () => {
|
||||
const eveningDate = new Date(2026, 8, 1, 20, 49, 0);
|
||||
const result = formatClockDisplay(eveningDate, 'en-GB');
|
||||
expect(result.time).toBe('20:49');
|
||||
expect(result.meridiem).toBe('');
|
||||
expect(result.hour).toBe(20);
|
||||
|
||||
const midnight = new Date(2026, 8, 1, 0, 5, 0);
|
||||
const midnightResult = formatClockDisplay(midnight, 'en-GB');
|
||||
expect(midnightResult.time).toBe('0:05');
|
||||
expect(midnightResult.meridiem).toBe('');
|
||||
expect(midnightResult.hour).toBe(0);
|
||||
});
|
||||
|
||||
it('formats 24-hour European locales without meridiem (de-DE, sv-SE, fr-FR)', () => {
|
||||
const eveningDate = new Date(2026, 8, 1, 20, 49, 0);
|
||||
|
||||
const deResult = formatClockDisplay(eveningDate, 'de-DE');
|
||||
expect(deResult.time).toBe('20:49');
|
||||
expect(deResult.meridiem).toBe('');
|
||||
|
||||
const svResult = formatClockDisplay(eveningDate, 'sv-SE');
|
||||
expect(svResult.time).toBe('20:49');
|
||||
expect(svResult.meridiem).toBe('');
|
||||
|
||||
const frResult = formatClockDisplay(eveningDate, 'fr-FR');
|
||||
expect(frResult.time).toBe('20:49');
|
||||
expect(frResult.meridiem).toBe('');
|
||||
});
|
||||
|
||||
it('defaults to current date when no date is supplied', () => {
|
||||
const result = formatClockDisplay();
|
||||
expect(result).toHaveProperty('time');
|
||||
expect(result).toHaveProperty('meridiem');
|
||||
expect(result).toHaveProperty('hour');
|
||||
expect(typeof result.time).toBe('string');
|
||||
expect(typeof result.hour).toBe('number');
|
||||
});
|
||||
|
||||
it('handles unexpected/invalid locale gracefully using fallback', () => {
|
||||
const eveningDate = new Date(2026, 8, 1, 20, 49, 0);
|
||||
const result = formatClockDisplay(eveningDate, 'invalid-locale-!!!');
|
||||
expect(result.time).toBe('8:49');
|
||||
expect(result.meridiem).toBe('PM');
|
||||
expect(result.hour).toBe(20);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatMessageTimestamp', () => {
|
||||
it('formats timestamp from today with time only', () => {
|
||||
const now = new Date();
|
||||
const timestamp = Math.floor(now.getTime() / 1000);
|
||||
const result = formatMessageTimestamp(timestamp);
|
||||
expect(result).toBeTruthy();
|
||||
expect(typeof result).toBe('string');
|
||||
});
|
||||
|
||||
it('formats timestamp from previous date with date and time', () => {
|
||||
const pastDate = new Date(2025, 0, 1, 12, 0, 0);
|
||||
const timestamp = Math.floor(pastDate.getTime() / 1000);
|
||||
const result = formatMessageTimestamp(timestamp);
|
||||
expect(result).toContain('2025');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -28,3 +28,42 @@ export function formatMessageTimestamp(timestamp?: number): string {
|
||||
|
||||
return `${dateStr} ${timeStr}`;
|
||||
}
|
||||
|
||||
export interface ClockDisplay {
|
||||
time: string;
|
||||
meridiem: string;
|
||||
hour: number;
|
||||
}
|
||||
|
||||
export function formatClockDisplay(
|
||||
date: Date = new Date(),
|
||||
locale: string = currentLocale
|
||||
): ClockDisplay {
|
||||
const hour = date.getHours();
|
||||
|
||||
try {
|
||||
const formatter = new Intl.DateTimeFormat(locale, {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
});
|
||||
|
||||
const parts = formatter.formatToParts(date);
|
||||
const dayPeriodPart = parts.find((p) => p.type === 'dayPeriod');
|
||||
const meridiem = dayPeriodPart ? dayPeriodPart.value : '';
|
||||
|
||||
const time = parts
|
||||
.filter((p) => p.type !== 'dayPeriod')
|
||||
.map((p) => p.value)
|
||||
.join('')
|
||||
.trim();
|
||||
|
||||
return { time, meridiem, hour };
|
||||
} catch {
|
||||
const minutes = date.getMinutes();
|
||||
const meridiem = hour >= 12 ? 'PM' : 'AM';
|
||||
const displayHour = ((hour + 11) % 12) + 1;
|
||||
const time = `${displayHour}:${String(minutes).padStart(2, '0')}`;
|
||||
return { time, meridiem, hour };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user