const MS_MINUTE = 60_000; const MS_HOUR = 60 * MS_MINUTE; const MS_DAY = 24 * MS_HOUR; function shanghaiParts(input: string | Date) { const d = input instanceof Date ? input : new Date(input); if (Number.isNaN(d.getTime())) return null; const sh = new Date(d.getTime() + 8 * 60 * 60 * 1000); const p = (n: number) => String(n).padStart(2, '0'); return { year: sh.getUTCFullYear(), month: p(sh.getUTCMonth() + 1), day: p(sh.getUTCDate()), hour: p(sh.getUTCHours()), minute: p(sh.getUTCMinutes()), }; } /** 核销走马灯时间:24 小时内相对时间,超过则显示具体时间 */ export function formatRedeemRelativeTime(input?: string | Date | null): string { if (input == null || input === '') return '刚刚'; const at = input instanceof Date ? input : new Date(input); if (Number.isNaN(at.getTime())) return '刚刚'; const diffMs = Date.now() - at.getTime(); if (diffMs < MS_MINUTE) return '刚刚'; if (diffMs < MS_HOUR) return `${Math.floor(diffMs / MS_MINUTE)}分钟前`; if (diffMs < MS_DAY) return `${Math.floor(diffMs / MS_HOUR)}小时前`; const parts = shanghaiParts(at); if (!parts) return '刚刚'; const nowParts = shanghaiParts(new Date()); const datePrefix = nowParts && parts.year === nowParts.year ? `${parts.month}-${parts.day}` : `${parts.year}-${parts.month}-${parts.day}`; return `${datePrefix} ${parts.hour}:${parts.minute}`; } /** 格式化为 Asia/Shanghai:2026-08-03 15:14:30(不依赖 Intl,兼容微信小程序) */ export function formatShanghaiDateTime(input?: string | Date | null): string { if (input == null || input === '') return '—'; if (typeof input === 'string') { const s = input.trim(); if (/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/.test(s)) { return s.slice(0, 19).replace('T', ' '); } } const d = input instanceof Date ? input : new Date(input); if (Number.isNaN(d.getTime())) return '—'; // 用 UTC 读数 + 固定东八区偏移,避免依赖 Intl / 设备时区 API 差异 const sh = new Date(d.getTime() + 8 * 60 * 60 * 1000); const p = (n: number) => String(n).padStart(2, '0'); return `${sh.getUTCFullYear()}-${p(sh.getUTCMonth() + 1)}-${p(sh.getUTCDate())} ${p(sh.getUTCHours())}:${p(sh.getUTCMinutes())}:${p(sh.getUTCSeconds())}`; }