// Asia/Shanghai 时区工具 (中国无夏令时, 固定 +08:00) // 桶语义: 小时档/日档桶的 \"边界时刻\" 都按上海时区对齐, 存储统一存 UTC (Date) const OFFSET_MS = 8 * 3600 * 1000 // 把时刻按上海时区拆成 \"假装 UTC 的本地部件\" function cnParts(ts) { const d = new Date(ts + OFFSET_MS) return { y: d.getUTCFullYear(), mo: d.getUTCMonth() + 1, da: d.getUTCDate(), h: d.getUTCHours(), min: d.getUTCMinutes(), sec: d.getUTCSeconds(), dow: d.getUTCDay(), // 0=Sun } } // 上海当日 00:00 (返回对应 UTC 时刻的 Date) export function dayStart(ts = Date.now()) { const p = cnParts(ts) return new Date(Date.UTC(p.y, p.mo - 1, p.da) - OFFSET_MS) } // 上海整点 (返回对应 UTC 时刻的 Date) export function hourStart(ts = Date.now()) { const p = cnParts(ts) return new Date(Date.UTC(p.y, p.mo - 1, p.da, p.h) - OFFSET_MS) } // 上海当周起始 (周一 00:00) export function weekStart(date) { const dow = cnParts(date.getTime()).dow const diff = dow === 0 ? 6 : dow - 1 // 距周一的天数 return addDays(dayStart(date.getTime()), -diff) } export function addDays(date, n) { return new Date(date.getTime() + n * 86400000) } // 上海当月 1 号 00:00 export function monthStart(ts = Date.now()) { const p = cnParts(ts) return new Date(Date.UTC(p.y, p.mo - 1, 1) - OFFSET_MS) } // 上海当年 1 月 1 日 00:00 export function yearStart(ts = Date.now()) { const p = cnParts(ts) return new Date(Date.UTC(p.y, 0, 1) - OFFSET_MS) } export function addHours(date, n) { return new Date(date.getTime() + n * 3600000) } // 上海本地 \"YYYY-MM-DD\" export function cnDateStr(date) { const p = cnParts(date.getTime()) return `${p.y}-${String(p.mo).padStart(2, '0')}-${String(p.da).padStart(2, '0')}` } // 上海本地 \"HH:mm\" export function cnTimeStr(date) { const p = cnParts(date.getTime()) return `${String(p.h).padStart(2, '0')}:${String(p.min).padStart(2, '0')}` } // 上海本地 \"YYYY-MM-DD HH:mm\" (用于日志) export function cnFullStr(date) { return `${cnDateStr(date)} ${cnTimeStr(date)}` } // 判断两个日期是否同一上海日 export function sameCnDay(a, b) { return dayStart(a.getTime()).getTime() === dayStart(b.getTime()).getTime() }