diff --git a/frontend/src/__tests__/helpers.test.ts b/frontend/src/__tests__/helpers.test.ts index c039046..3c173ca 100644 --- a/frontend/src/__tests__/helpers.test.ts +++ b/frontend/src/__tests__/helpers.test.ts @@ -4,6 +4,9 @@ import { truncateAddress, isValidAmount, timeUntil, + normalizeUnixTimestamp, + formatDate, + formatTime, calculatePayout, calculateOdds, bpsToPercent, @@ -171,6 +174,30 @@ describe("timeUntil", () => { }); }); +describe("timestamp formatting", () => { + it("normalizes millisecond timestamps to Unix seconds", () => { + expect(normalizeUnixTimestamp(1772064000000)).toBe(1772064000); + }); + + it("formats dates with locale and timezone consistently", () => { + expect(formatDate(1772064000, "en-US", { timeZone: "UTC" })).toBe( + "Feb 26, 2026, 12:00 AM UTC" + ); + }); + + it("formats millisecond timestamps without drifting into the future", () => { + expect(formatDate(1772064000000, "en-US", { timeZone: "UTC" })).toBe( + "Feb 26, 2026, 12:00 AM UTC" + ); + }); + + it("formats times with locale and timezone consistently", () => { + expect(formatTime(1772064000, "en-US", { timeZone: "UTC" })).toBe( + "12:00 AM UTC" + ); + }); +}); + // ── calculatePayout ─────────────────────────────────────────────────────────── describe("calculatePayout", () => { diff --git a/frontend/src/app/markets/[id]/page.tsx b/frontend/src/app/markets/[id]/page.tsx index 5ae3a63..4c155bb 100644 --- a/frontend/src/app/markets/[id]/page.tsx +++ b/frontend/src/app/markets/[id]/page.tsx @@ -7,7 +7,7 @@ import { useWallet } from "@/hooks/useWallet"; import { useToken } from "@/hooks/useToken"; import { pollMarketEvents } from "@/services/events"; import { getXlmBalance } from "@/services/soroban"; -import { displayXLM, formatXLM, calculatePayout, truncateAddress } from "@/utils/helpers"; +import { displayXLM, formatXLM, formatTime, calculatePayout, truncateAddress } from "@/utils/helpers"; import { WIN_POINTS, LOSE_POINTS, @@ -319,7 +319,7 @@ export default function MarketDetailPage({ - {new Date(evt.timestamp * 1000).toLocaleTimeString()} + {formatTime(evt.timestamp)} ))} diff --git a/frontend/src/services/events.ts b/frontend/src/services/events.ts index a30c32d..e027823 100644 --- a/frontend/src/services/events.ts +++ b/frontend/src/services/events.ts @@ -2,6 +2,7 @@ import { rpc, scValToNative, xdr } from "@stellar/stellar-sdk"; import { MARKET_CONTRACT_ID } from "@/config/network"; import { getSorobanServer } from "@/services/soroban"; import type { MarketEvent } from "@/types"; +import { normalizeUnixTimestamp } from "@/utils/helpers"; // ── Event type names emitted by the PredictionMarket contract ───────────────── @@ -32,7 +33,9 @@ function parseEventResponse( if (!isKnownEventType(eventName)) return null; const data = scValToNative(event.value); - const timestamp = new Date(event.ledgerClosedAt).getTime(); + const timestamp = normalizeUnixTimestamp( + new Date(event.ledgerClosedAt).getTime() + ); switch (eventName) { case "bet_placed": diff --git a/frontend/src/utils/helpers.ts b/frontend/src/utils/helpers.ts index 4688130..8680be6 100644 --- a/frontend/src/utils/helpers.ts +++ b/frontend/src/utils/helpers.ts @@ -77,14 +77,44 @@ export function timeUntil(timestamp: number): string { /** * Format a Unix timestamp to a locale-aware date string. */ -export function formatDate(timestamp: number): string { - return new Date(timestamp * 1000).toLocaleDateString("en-US", { +export function normalizeUnixTimestamp(timestamp: number): number { + return timestamp > 9_999_999_999 ? Math.floor(timestamp / 1000) : timestamp; +} + +export function formatDate( + timestamp: number, + locale?: string | string[], + options: Intl.DateTimeFormatOptions = {} +): string { + const unixTimestamp = normalizeUnixTimestamp(timestamp); + + return new Intl.DateTimeFormat(locale, { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", - }); + timeZoneName: "short", + ...options, + }).format(new Date(unixTimestamp * 1000)); +} + +/** + * Format a Unix timestamp to a locale-aware time string. + */ +export function formatTime( + timestamp: number, + locale?: string | string[], + options: Intl.DateTimeFormatOptions = {} +): string { + const unixTimestamp = normalizeUnixTimestamp(timestamp); + + return new Intl.DateTimeFormat(locale, { + hour: "2-digit", + minute: "2-digit", + timeZoneName: "short", + ...options, + }).format(new Date(unixTimestamp * 1000)); } /**