From 6cbe137938120645aec9b76fba0d5cec2466af33 Mon Sep 17 00:00:00 2001 From: tenaxresearchbaekhui Date: Sat, 11 Jul 2026 05:38:03 +0900 Subject: [PATCH 1/2] Fix locale-aware timestamp formatting --- frontend/src/__tests__/helpers.test.ts | 16 +++++++++++++++ frontend/src/app/markets/[id]/page.tsx | 4 ++-- frontend/src/utils/helpers.ts | 28 +++++++++++++++++++++++--- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/frontend/src/__tests__/helpers.test.ts b/frontend/src/__tests__/helpers.test.ts index c039046..444c20b 100644 --- a/frontend/src/__tests__/helpers.test.ts +++ b/frontend/src/__tests__/helpers.test.ts @@ -4,6 +4,8 @@ import { truncateAddress, isValidAmount, timeUntil, + formatDate, + formatTime, calculatePayout, calculateOdds, bpsToPercent, @@ -171,6 +173,20 @@ describe("timeUntil", () => { }); }); +describe("timestamp formatting", () => { + 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 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/utils/helpers.ts b/frontend/src/utils/helpers.ts index 4688130..dcfc455 100644 --- a/frontend/src/utils/helpers.ts +++ b/frontend/src/utils/helpers.ts @@ -77,14 +77,36 @@ 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 formatDate( + timestamp: number, + locale?: string | string[], + options: Intl.DateTimeFormatOptions = {} +): string { + return new Intl.DateTimeFormat(locale, { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", - }); + timeZoneName: "short", + ...options, + }).format(new Date(timestamp * 1000)); +} + +/** + * Format a Unix timestamp to a locale-aware time string. + */ +export function formatTime( + timestamp: number, + locale?: string | string[], + options: Intl.DateTimeFormatOptions = {} +): string { + return new Intl.DateTimeFormat(locale, { + hour: "2-digit", + minute: "2-digit", + timeZoneName: "short", + ...options, + }).format(new Date(timestamp * 1000)); } /** From 0af07d2a6c3f672efb4b6bf241bdb5fe3a416280 Mon Sep 17 00:00:00 2001 From: tenaxresearchbaekhui Date: Sun, 12 Jul 2026 05:37:06 +0900 Subject: [PATCH 2/2] Normalize event timestamps --- frontend/src/__tests__/helpers.test.ts | 11 +++++++++++ frontend/src/services/events.ts | 5 ++++- frontend/src/utils/helpers.ts | 12 ++++++++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/frontend/src/__tests__/helpers.test.ts b/frontend/src/__tests__/helpers.test.ts index 444c20b..3c173ca 100644 --- a/frontend/src/__tests__/helpers.test.ts +++ b/frontend/src/__tests__/helpers.test.ts @@ -4,6 +4,7 @@ import { truncateAddress, isValidAmount, timeUntil, + normalizeUnixTimestamp, formatDate, formatTime, calculatePayout, @@ -174,12 +175,22 @@ 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" 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 dcfc455..8680be6 100644 --- a/frontend/src/utils/helpers.ts +++ b/frontend/src/utils/helpers.ts @@ -77,11 +77,17 @@ export function timeUntil(timestamp: number): string { /** * Format a Unix timestamp to a locale-aware date string. */ +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", @@ -90,7 +96,7 @@ export function formatDate( minute: "2-digit", timeZoneName: "short", ...options, - }).format(new Date(timestamp * 1000)); + }).format(new Date(unixTimestamp * 1000)); } /** @@ -101,12 +107,14 @@ export function formatTime( 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(timestamp * 1000)); + }).format(new Date(unixTimestamp * 1000)); } /**