refactor: 统一玩家端时间格式化方法并移除冗余本地时间工具

This commit is contained in:
2026-06-09 15:06:48 +08:00
parent 2ddba8462e
commit 4707101da4
10 changed files with 28 additions and 48 deletions

View File

@@ -15,7 +15,7 @@ import type { HallDrawLiveSnapshot } from "@/features/hall/use-hall-draw-live";
import { formatSecondsClock } from "@/lib/format-gmt";
import { LOTTERY_SCHEDULE_TIMEZONE } from "@/lib/lottery-schedule-timezone";
import {
formatLotteryInstant,
formatPlayerInstant,
formatLotteryScheduleClock,
getBrowserTimeZoneLabel,
} from "@/lib/player-datetime";
@@ -34,7 +34,7 @@ function ScheduleAnchorTime({ payload }: { payload: DrawCurrentPayload }) {
: isOpen
? "draw.scheduledEnd"
: "draw.scheduledClose";
const formatted = source ? formatLotteryInstant(source) : null;
const formatted = source ? formatPlayerInstant(source) : null;
if (!formatted) {
return (
<>

View File

@@ -2,7 +2,7 @@
import type { TFunction } from "i18next";
import { formatLotteryInstant } from "@/lib/player-datetime";
import { formatPlayerInstant } from "@/lib/player-datetime";
type OrderMetaLineProps = {
orderNo: string | null | undefined;
@@ -21,7 +21,7 @@ export function OrderMetaLine({ orderNo, placedAt, t, className }: OrderMetaLine
<span>{t("orders.orderNoLabel")}</span>{" "}
<span className="font-mono font-semibold text-slate-600">{displayNo}</span>
<span aria-hidden> · </span>
<span>{formatLotteryInstant(placedAt ?? null)}</span>
<span>{formatPlayerInstant(placedAt ?? null)}</span>
</p>
);
}

View File

@@ -20,7 +20,7 @@ import { TwentyThreeResultsGrid } from "@/features/results/twenty-three-results-
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
import { orderGroupPath } from "@/features/orders/group-ticket-items";
import { StatusDot, ticketStatusDisplay } from "@/features/orders/ticket-item-status";
import { formatLotteryInstant } from "@/lib/player-datetime";
import { formatPlayerInstant } from "@/lib/player-datetime";
import { formatMinorAsCurrency } from "@/lib/money";
import { norm4d } from "@/lib/norm-4d";
import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency";
@@ -274,7 +274,7 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
<div className="flex items-baseline justify-between gap-3">
<span className="shrink-0 text-slate-500">{t("orders.placedAt")}</span>
<span className="text-right font-medium text-slate-800">
{formatLotteryInstant(data.placed_at ?? null)}
{formatPlayerInstant(data.placed_at ?? null)}
</span>
</div>
<div className="flex items-baseline justify-between gap-3">
@@ -412,7 +412,7 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
<p className="mt-0.5 font-mono text-[11px] text-slate-500">{row.code}</p>
</div>
<p className="shrink-0 font-mono text-[11px] text-slate-500">
{formatLotteryInstant(row.time)}
{formatPlayerInstant(row.time)}
</p>
</div>
))}
@@ -422,7 +422,7 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
{data.settled_at ? (
<p className="text-[11px] text-slate-500">
{t("orders.settledAt", { time: formatLotteryInstant(data.settled_at) })}
{t("orders.settledAt", { time: formatPlayerInstant(data.settled_at) })}
</p>
) : null}
</CardContent>

View File

@@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { PlayerPanel } from "@/components/layout/player-panel";
import { usePendingWalletReconcile } from "@/hooks/use-pending-wallet-reconcile";
import { formatLocalDateTime } from "@/lib/format-local-datetime";
import { formatPlayerInstant } from "@/lib/player-datetime";
import { formatMinorAsCurrency } from "@/lib/money";
import {
pendingReconcileDescriptionKey,
@@ -74,7 +74,7 @@ export function NotificationsScreen() {
{t(pendingReconcileTitleKey(item.type))}
</p>
<p className="mt-0.5 text-xs text-slate-500">
{formatLocalDateTime(item.created_at)}
{formatPlayerInstant(item.created_at)}
</p>
</div>
<div className="flex shrink-0 flex-col items-end gap-1">

View File

@@ -19,7 +19,7 @@ import { Input } from "@/components/ui/input";
import { PlayerPanel } from "@/components/layout/player-panel";
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
import { formatMinorAsCurrency } from "@/lib/money";
import { formatLotteryInstant } from "@/lib/player-datetime";
import { formatPlayerInstant } from "@/lib/player-datetime";
import { playLabel } from "@/lib/play-labels";
import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency";
import type { DrawResultListItem } from "@/types/api/draw-results";
@@ -276,7 +276,7 @@ function WinningResultDialog({
<div className="col-span-2">
<p className="text-xs">{t("results.drawTime", { time: "" }).replace(":", "").trim()}</p>
<p className="mt-1 font-semibold text-slate-900">
{formatLotteryInstant(data.draw.draw_time_iso ?? data.draw.draw_time ?? null)}
{formatPlayerInstant(data.draw.draw_time_iso ?? data.draw.draw_time ?? null)}
</p>
</div>
</div>

View File

@@ -20,7 +20,7 @@ import { JackpotResultsStrip } from "@/features/results/jackpot-results-strip";
import { TwentyThreeResultsGrid } from "@/features/results/twenty-three-results-grid";
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
import { getPlayerBearerTokenPayload } from "@/lib/lottery-auth";
import { formatLotteryInstant } from "@/lib/player-datetime";
import { formatPlayerInstant } from "@/lib/player-datetime";
import { formatMinorAsCurrency } from "@/lib/money";
import { norm4d } from "@/lib/norm-4d";
import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency";
@@ -182,7 +182,7 @@ export function DrawResultDetailScreen({ drawNo }: DrawResultDetailScreenProps)
</CardTitle>
<CardDescription className="mt-1 font-mono text-xs text-slate-500">
{t("results.drawTime", {
time: formatLotteryInstant(data.draw_time_iso ?? data.draw_time ?? null),
time: formatPlayerInstant(data.draw_time_iso ?? data.draw_time ?? null),
})}
</CardDescription>
</div>

View File

@@ -21,7 +21,7 @@ import { PlayerPanel } from "@/components/layout/player-panel";
import { JackpotResultsStrip } from "@/features/results/jackpot-results-strip";
import { TwentyThreeResultsGrid } from "@/features/results/twenty-three-results-grid";
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
import { formatLotteryInstant } from "@/lib/player-datetime";
import { formatPlayerInstant } from "@/lib/player-datetime";
import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency";
import type { DrawResultListItem } from "@/types/api/draw-results";
@@ -244,7 +244,7 @@ export function DrawResultsListScreen() {
</p>
<p className="mt-1 font-mono text-xs text-slate-500">
{t("results.drawTime", {
time: formatLotteryInstant(
time: formatPlayerInstant(
featured.draw_time_iso ?? featured.draw_time ?? null,
),
})}
@@ -283,7 +283,7 @@ export function DrawResultsListScreen() {
{row.draw_no}
</p>
<p className="mt-1 text-[11px] text-slate-500">
{formatLotteryInstant(row.draw_time_iso ?? row.draw_time ?? null)}
{formatPlayerInstant(row.draw_time_iso ?? row.draw_time ?? null)}
</p>
</div>
<span className="shrink-0 rounded-full bg-[#f2f6ff] px-2.5 py-1 text-xs font-bold text-[#0b56b7]">

View File

@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { formatLocalDateTime } from "@/lib/format-local-datetime";
import { formatPlayerInstant } from "@/lib/player-datetime";
import { formatMinorAsCurrency } from "@/lib/money";
import type { WalletLogItem, WalletLogsData } from "@/types/api/wallet-logs";
@@ -225,7 +225,7 @@ export function LogRow({
</p>
</div>
<p className="mt-1.5 text-xs font-medium text-slate-500">
{formatLocalDateTime(item.created_at)}
{formatPlayerInstant(item.created_at)}
</p>
{item.ref_id ? (
<p className="mt-1 truncate font-mono text-[11px] text-slate-400">

View File

@@ -1,26 +0,0 @@
function pad2(n: number): string {
return String(n).padStart(2, "0");
}
/**
* 将接口 ISO 8601 时间格式化为浏览器 **本地时区** 下的 `YYYY-MM-DD HH:mm:ss`。
*
* 避免对原始字符串仅 `slice(0,19)`(易把 UTC 刻度误当本地钟面)。
*/
export function formatLocalDateTime(iso: string | null | undefined): string {
if (iso == null || iso === "") {
return "—";
}
const ms = Date.parse(iso);
if (Number.isNaN(ms)) {
return "—";
}
const date = new Date(ms);
const y = date.getFullYear();
const m = pad2(date.getMonth() + 1);
const d = pad2(date.getDate());
const h = pad2(date.getHours());
const min = pad2(date.getMinutes());
const s = pad2(date.getSeconds());
return `${y}-${m}-${d} ${h}:${min}:${s}`;
}

View File

@@ -126,9 +126,12 @@ export function getBrowserTimeZoneLabel(date = new Date()): string {
}
/**
* 将接口 ISO 时间格式化为 **浏览器本地时区** 下的 `YYYY-MM-DD HH:mm:ss`。
* 将接口 ISO 8601 时间格式化为浏览器 **本地时区** 下的 `YYYY-MM-DD HH:mm:ss`。
*
* 玩家端订单、钱包、通知、开奖结果等“已发生事件时间”统一走这里。
* 避免对原始字符串仅 `slice(0,19)`(易把 UTC 刻度误当本地钟面)。
*/
export function formatLotteryInstant(iso: string | null | undefined): string {
export function formatPlayerInstant(iso: string | null | undefined): string {
if (iso == null || iso === "") {
return "—";
}
@@ -139,6 +142,9 @@ export function formatLotteryInstant(iso: string | null | undefined): string {
return formatParts(new Date(ms));
}
/** @deprecated 玩家端新代码请使用 {@link formatPlayerInstant}。 */
export const formatLotteryInstant = formatPlayerInstant;
/**
* 将服务端计划时刻(无时区的 `YYYY-MM-DD HH:mm:ss`,按 schedule_timezone 解释,默认 UTC
* 格式化为浏览器本地时区的 `YYYY-MM-DD HH:mm:ss`。
@@ -158,7 +164,7 @@ export function formatLotteryScheduleClock(
}
/**
* 按指定 IANA 时区格式化(管理端等场景);玩家端展示请用 {@link formatLotteryInstant}。
* 按指定 IANA 时区格式化(管理端等场景);玩家端展示请用 {@link formatPlayerInstant}。
*/
export function formatLotteryInstantInTimeZone(
iso: string | null | undefined,