约 7 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证
toISOString 永远输出带 Z 的 UTC 时间。拿它 slice(0,10) 当「今天的日期」,在东八区每天 0 点到 8 点之间都会退一天——线上「凌晨订单日期错一天」多半是它。
两行代码就能在本地复现这个线上事故。
const d = new Date('2026-10-16T02:30:00'); // 本地凌晨 2:30
console.log(d.toISOString()); // '2026-10-15T18:30:00.000Z'
console.log(d.toISOString().slice(0, 10)); // '2026-10-15' ← 日期少了一天
// 展示用本地方法,存储才用 ISO
console.log(d.getDate()); // 16同一个日期,写法不同,各浏览器结果可能不一样,Safari 尤其挑剔。
function formatDate(input, fmt = 'YYYY-MM-DD HH:mm:ss') {
const d = input instanceof Date ? input : new Date(input);
if (Number.isNaN(d.getTime())) return ''; // 非法输入返回空串,而不是 'NaN-NaN-NaN'
const pad = (n) => String(n).padStart(2, '0');
const map = {
YYYY: d.getFullYear(),
MM: pad(d.getMonth() + 1), // 月份从 0 开始
DD: pad(d.getDate()),
HH: pad(d.getHours()),
mm: pad(d.getMinutes()),
ss: pad(d.getSeconds()),
};
return fmt.replace(/YYYY|MM|DD|HH|mm|ss/g, (t) => map[t]);
}
formatDate('2026-10-16T22:30:00', 'YYYY年MM月DD日 HH:mm'); // '2026年10月16日 22:30'
formatDate(Date.now(), 'MM/DD HH:mm'); // 当天日期时间(按本地时区)先分清「给机器看」还是「给人看」,再选方法。
| 需求 | 推荐写法 | 坑 |
|---|---|---|
| 传给接口 / 存库 | d.toISOString() | 输出 UTC,展示端要自己转本地 |
| 页面展示 | 手写 formatDate | getFullYear/getHours 系列才是本地时区 |
| 日期大小比较 | 比时间戳或 Date 对象 | 别比较 '2026-1-1' 这类非定长字符串 |
| 倒计时 | Date.now() 与目标值做差 | 目标时间解析失败会得到 NaN,先 isNaN 检查 |
| 「今天是几号」 | d.getDate() | 别从 ISO 字符串 slice,凌晨会错一天 |
秒级时间戳喂给 new Date 会得到 1970 年;毫秒级又乘 1000 会跑到五万年后。先确认单位再动手。
// 秒级时间戳(10 位)转 Date:要乘 1000 const sec = 1790000000; new Date(sec * 1000); // 正确:正常日期 new Date(sec); // 错误:1970 年附近 // Date.now() 与 getTime() 都是毫秒(13 位) const ms = Date.now(); Math.floor(ms / 1000); // 毫秒转秒 // 判断后端给的是哪种:数值 < 1e12 就当秒 const ts = 1790000000; const date = new Date(ts < 1e12 ? ts * 1000 : ts);
手写不是目的,按需求复杂度选工具。
toISOString 永远输出 UTC(结尾带 Z)。东八区用户展示时要用 getHours 等本地方法或手写 formatDate,别直接 slice ISO 字符串。
老 Safari 只认完整 ISO 带时间的格式,纯日期或空格分隔的字符串可能解析失败。统一换成 'YYYY/MM/DD' 或先转时间戳最稳。
看位数:10 位是秒,13 位是毫秒;也可以按数值判断,小于 1e12 当秒处理并乘 1000。Date.now() 返回的永远是毫秒。
只是展示层格式化,20 行手写 formatDate 足够;涉及时区转换、相对时间(3 天前)、多语言,直接上 dayjs,别重复造轮子。