// 后端按北京时间存储(无时区标记),此处显式按 Asia/Shanghai 渲染, // 避免浏览器本地时区导致时间显示错乱。 export function formatBjTime(t?: string | null): string { if (!t) return '-'; const iso = /[Z+]/.test(t) ? t : t + '+08:00'; const d = new Date(iso); if (isNaN(d.getTime())) return t; return d.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false }); } // 仅日期(北京时间) export function formatBjDate(t?: string | null): string { if (!t) return '-'; const iso = /[Z+]/.test(t) ? t : t + '+08:00'; const d = new Date(iso); if (isNaN(d.getTime())) return t; return d.toLocaleDateString('zh-CN', { timeZone: 'Asia/Shanghai' }); } // 将任意 Date 换算为「北京墙钟」对应的 UTC 视图,便于按北京日历日比较 function toBjView(d: Date): Date { return new Date(d.getTime() + (d.getTimezoneOffset() + 480) * 60000); } // 聊天消息时间:今天 HH:MM / 昨天 HH:MM / MM-DD HH:MM / YYYY-MM-DD HH:MM(北京时间) export function formatChatTime(iso?: string | null): string { if (!iso) return ''; const s = /[Z+]/.test(iso) ? iso : iso + '+08:00'; const d = new Date(s); if (isNaN(d.getTime())) return ''; const bj = toBjView(d); const nowBj = toBjView(new Date()); const dayDiff = Math.floor(nowBj.getTime() / 86400000) - Math.floor(bj.getTime() / 86400000); const hhmm = d.toLocaleTimeString('zh-CN', { timeZone: 'Asia/Shanghai', hour: '2-digit', minute: '2-digit' }); if (dayDiff <= 0) return hhmm; if (dayDiff === 1) return '昨天 ' + hhmm; const md = d.toLocaleDateString('zh-CN', { timeZone: 'Asia/Shanghai', month: '2-digit', day: '2-digit' }); if (bj.getUTCFullYear() === nowBj.getUTCFullYear()) return md + ' ' + hhmm; return d.toLocaleDateString('zh-CN', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit' }) + ' ' + hhmm; }