Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions frontend/src/__tests__/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import {
truncateAddress,
isValidAmount,
timeUntil,
normalizeUnixTimestamp,
formatDate,
formatTime,
calculatePayout,
calculateOdds,
bpsToPercent,
Expand Down Expand Up @@ -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", () => {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/app/markets/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -319,7 +319,7 @@ export default function MarketDetailPage({
</span>
</div>
<span className="text-xs text-slate-600 shrink-0">
{new Date(evt.timestamp * 1000).toLocaleTimeString()}
{formatTime(evt.timestamp)}
</span>
</div>
))}
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/services/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ─────────────────

Expand Down Expand Up @@ -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":
Expand Down
36 changes: 33 additions & 3 deletions frontend/src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

/**
Expand Down