【时间利器】4、JavaScript时间处理全解:Date/moment/dayjs/Temporal

发布时间:2026/5/28 17:20:16

【时间利器】4、JavaScript时间处理全解:Date/moment/dayjs/Temporal JavaScript时间处理全解Date/moment/dayjs/Temporal【导语】前端开发中时间处理一直是个让人头疼的难题。new Date(2026-03-24)在不同浏览器里结果不同月份从0开始11月居然写成10想显示“2小时前”还要自己写算法……本文带你系统梳理 JavaScript 时间处理的演进史从原生Date的深坑到moment.js的辉煌与落幕再到轻量级day.js和函数式date-fns的崛起最后展望未来的标准Temporal API。读完本文你将彻底告别时间处理的噩梦写出健壮的前端时间代码。 一、引言JS时间处理的“噩梦”1.1 Date对象的设计缺陷JavaScript 的Date对象从诞生之初就充满了槽点易变性Date对象是可变的任何修改都会影响原对象容易引发副作用月份从0开始new Date(2026, 2, 24)表示的是3月24日而不是2月这是最反直觉的设计之一时区模糊new Date()返回的是本地时间但new Date(2026-03-24)在不同浏览器可能被解析为UTC或本地时间导致跨浏览器结果不一致解析行为不统一Date.parse()对非标准字符串的解析依赖于实现例如2026-03-24 14:30:00在 Chrome 和 Safari 中可能返回不同的结果缺少人性化方法没有内置“相对时间”如“2小时前”的输出需要自己计算1.2 真实案例前端时间显示错误某 SaaS 系统的后台管理页面后端返回订单时间2026-03-24T06:30:00ZUTC。前端直接new Date(orderTime)后调用toLocaleString()结果美国用户看到的是3/23/2026, 11:30:00 PM因为toLocaleString默认使用用户本地时区而new Date解析 ISO 字符串时会正确识别 UTC但展示时转成了本地。产品经理投诉订单明明是今天创建的为什么显示为昨天原来是因为时区转换后UTC 6:30 对应美国西部时间的前一天 22:30。根源前端没有明确控制时区展示逻辑依赖了浏览器的默认行为。1.3 前端时间处理的痛点跨浏览器兼容性不同浏览器对Date构造函数的字符串解析差异用户时区感知需要根据用户所在时区展示时间如中国用户看北京时间美国用户看美东时间国际化显示不同语言/地区的日期格式差异如03/24/2026vs24/03/2026性能与体积移动端或首屏渲染要求库体积尽可能小本文将从原生Date入手逐步升级到现代化方案帮助你根据场景选择最合适的时间处理方式。 二、原生Date对象认清坑用对场景2.1 Date对象的底层逻辑Date对象在 JavaScript 中基于 Unix 时间戳毫秒存储的是自 1970年1月1日 00:00:00 UTC 以来的毫秒数。时区信息完全由运行环境操作系统/浏览器提供Date对象本身不保存时区。2.2 基础操作(1) 时间创建// 当前时间本地constnownewDate();console.log(now);// Thu Mar 24 2026 14:30:00 GMT0800 (中国标准时间)// 从时间戳创建毫秒constfromTimestampnewDate(1742807400000);console.log(fromTimestamp);// 2026-03-24T06:30:00.000Z (UTC)// 从ISO字符串创建推荐constisonewDate(2026-03-24T06:30:00Z);console.log(iso.toISOString());// 2026-03-24T06:30:00.000Z// 从年月日创建注意月份从0开始constdtnewDate(2026,2,24,14,30,0);// 2026-03-24 14:30:00 本地时间console.log(dt);// Thu Mar 24 2026 14:30:00 GMT0800 (中国标准时间)(2) UTC方法 vs 本地方法Date对象提供了两套获取时间组件的方法本地方法基于系统时区和 UTC 方法基于 UTC0。constdatenewDate(2026-03-24T14:30:0008:00);// 北京时间 14:30console.log(date.getHours());// 14 (本地小时取决于系统时区)console.log(date.getUTCHours());// 6 (UTC小时)console.log(date.getMonth());// 2 (3月因为从0开始)console.log(date.getUTCMonth());// 2(3) 格式化Date原生提供几种格式化方法constdnewDate();d.toString();// Thu Mar 24 2026 14:30:00 GMT0800 (中国标准时间)d.toISOString();// 2026-03-24T06:30:00.000Zd.toUTCString();// Tue, 24 Mar 2026 06:30:00 GMTd.toLocaleString();// 2026/3/24 14:30:00 (取决于浏览器语言)d.toLocaleDateString();// 2026/3/242.3 避坑指南四个经典坑坑1new Date(2026-03-24)是UTC还是本地答案不同浏览器行为不一致根据 ES5 规范仅包含日期的 ISO 8601 字符串应解析为 UTC但部分旧浏览器或非标准实现会当作本地时间。// 在 Chrome 中解析为 UTCnewDate(2026-03-24).toISOString();// 2026-03-24T00:00:00.000Z// 在某些旧版 Safari 中可能解析为本地时间// 因此强烈建议使用带时间的完整 ISO 字符串如 2026-03-24T00:00:00Z解决方案始终使用完整格式2026-03-24T00:00:00Z表示 UTC 日期或使用new Date(Date.UTC(2026, 2, 24))构造 UTC 时间。坑2月份从0开始constmonthnewDate().getMonth();// 3月 2解决方案封装辅助函数或者使用第三方库。functiongetRealMonth(date){returndate.getMonth()1;}坑3Date对象是可变的constdate1newDate();constdate2date1;date2.setDate(10);// date1 也被修改了解决方案需要拷贝时使用new Date(date1)创建新对象。坑4解析非标准时间字符串的兼容性问题constdnewDate(2026-03-24 14:30:00);// 非 ISO 格式可能返回 Invalid Date解决方案永远不要用Date解析非标准格式。要么用正则拆解要么用第三方库。2.4 原生Date的适用场景简单的本地时间展示不涉及时区转换获取当前时间戳Date.now()轻量级场景且你已清楚上述坑并做了规避对于复杂的时区、格式化和相对时间建议使用第三方库。图1Date对象常见坑点图示——用思维导图展示月份从0、可变性、解析不一致、UTC/本地混淆四个坑点及其解决方案。 三、第三方库从moment.js到day.js3.1 moment.js经典但笨重moment.js曾经是前端时间处理的王者功能强大但体积较大约 200KB且不支持 Tree Shaking。目前官方已进入维护模式不再添加新功能推荐使用更现代的替代品。核心功能示例// 安装npm install momentimportmomentfrommoment;// 创建constnowmoment();// 当前时间constutcmoment.utc();// UTC时间constfromStrmoment(2026-03-24T14:30:0008:00);// 自动识别// 时区转换需要 moment-timezone 扩展importmomentTzfrommoment-timezone;constbeijingmoment.tz(Asia/Shanghai);constnewYorkbeijing.clone().tz(America/New_York);// 格式化now.format(YYYY-MM-DD HH:mm:ss);// 2026-03-24 14:30:00// 相对时间now.fromNow();// 2 hours ago// 运算now.add(1,day).subtract(2,hours);局限性体积大影响首屏加载可变性add/subtract会修改原对象容易出bug官方已停止功能开发仅维护安全漏洞3.2 day.js轻量替代2KBday.js的 API 与moment.js几乎一致但体积仅 2KB支持插件扩展是目前最受欢迎的轻量级时间库。安装与使用npminstalldayjsimportdayjsfromdayjs;importutcfromdayjs/plugin/utc;importtimezonefromdayjs/plugin/timezone;importrelativeTimefromdayjs/plugin/relativeTime;importdayjs/locale/zh-cn;// 扩展插件dayjs.extend(utc);dayjs.extend(timezone);dayjs.extend(relativeTime);dayjs.locale(zh-cn);// 设置中文// 基础操作constnowdayjs();// 当前时间本地constutcNowdayjs.utc();// UTC时间constparseddayjs(2026-03-24T14:30:0008:00);// 时区转换constbeijingdayjs.tz(2026-03-24 14:30:00,Asia/Shanghai);constnewYorkbeijing.tz(America/New_York);console.log(newYork.format());// 2026-03-24T02:30:00-04:00// 格式化console.log(now.format(YYYY-MM-DD HH:mm:ss));// 2026-03-24 14:30:00// 相对时间console.log(now.fromNow());// 2小时前// 运算不可变返回新对象consttomorrownow.add(1,day);day.js 时区插件实现 UTC ↔ 北京时间互转// UTC 转北京时间constutcTimedayjs.utc(2026-03-24T06:30:00Z);constbeijingTimeutcTime.tz(Asia/Shanghai);console.log(beijingTime.format());// 2026-03-24T14:30:0008:00// 北京时间转 UTCconstbeijingdayjs.tz(2026-03-24 14:30:00,Asia/Shanghai);constutcbeijing.utc();console.log(utc.format());// 2026-03-24T06:30:00Z3.3 date-fns函数式时间处理date-fns采用纯函数、按需导入的方式每个功能都是独立函数体积小适合现代打包工具。安装与使用npminstalldate-fnsimport{format,addDays,differenceInHours,formatDistance}fromdate-fns;import{zhCN}fromdate-fns/locale;constnownewDate();// 格式化console.log(format(now,yyyy-MM-dd HH:mm:ss));// 2026-03-24 14:30:00// 运算返回新对象consttomorrowaddDays(now,1);// 时间差constdiffdifferenceInHours(tomorrow,now);// 24// 相对时间console.log(formatDistance(now,addDays(now,2),{locale:zhCN}));// 2天date-fns 的优势完全按需打包后体积小不可变性纯函数易于测试支持时区通过date-fns-tz扩展包3.4 库选型建议库体积特点适用场景moment.js~200KB功能全面API 稳定老旧项目维护不推荐新项目day.js~2KB轻量API 兼容 moment插件化大多数新项目需时区/相对时间date-fns按需函数式不可变无副作用追求打包体积、使用现代开发模式原生 Date0无依赖极简场景且已充分了解坑点图2库选型对比图——表格形式展示 moment、dayjs、date-fns、原生在体积、API风格、功能完整性、推荐指数上的对比。 四、未来标准Temporal API4.1 Temporal API 的设计目标Temporal是 TC39 正在推进的提案旨在彻底解决Date对象的所有缺陷。它提供了不可变、语义清晰、支持时区、支持高精度时间的新 API。核心特性不可变性所有操作返回新对象明确的类型区分带时区的时间、无时区日期、无时区时间、时间段等支持时区内置 IANA 时区数据库支持高精度纳秒级精度直观的 API年份从1开始月份从1开始无需再记忆坑点4.2 核心类解析类说明示例Temporal.Instant绝对的纳秒级时间点类似 Unix 时间戳用于机器存储Temporal.ZonedDateTime带时区的日期时间推荐业务使用2026-03-24T14:30:0008:00[Asia/Shanghai]Temporal.PlainDate不带时区的日期生日、纪念日2026-03-24Temporal.PlainTime不带日期的时间14:30:00Temporal.PlainDateTime不带时区的日期时间2026-03-24T14:30:00Temporal.Duration时间段支持年/月/日/时/分/秒/毫秒/微秒/纳秒{ hours: 2, minutes: 30 }4.3 实战案例(1) 创建 UTC 时间和本地时间// 当前 UTC 时间InstantconstnowTemporal.Now.instant();console.log(now.toString());// 2026-03-24T06:30:00.123456789Z// 当前系统时区的 ZonedDateTimeconstnowInLocalTemporal.Now.zonedDateTimeISO();console.log(nowInLocal.toString());// 2026-03-24T14:30:00.12345678908:00[Asia/Shanghai]// 从 ISO 字符串创建 ZonedDateTimeconstbeijingTemporal.ZonedDateTime.from(2026-03-24T14:30:0008:00[Asia/Shanghai]);console.log(beijing.toString());// 2026-03-24T14:30:0008:00[Asia/Shanghai](2) 时区转换constbeijingTemporal.ZonedDateTime.from(2026-03-24T14:30:0008:00[Asia/Shanghai]);constnewYorkbeijing.withTimeZone(America/New_York);console.log(newYork.toString());// 2026-03-24T02:30:00-04:00[America/New_York](3) 时间运算constnowTemporal.Now.zonedDateTimeISO();// 加 2 天 3 小时constlaternow.add({days:2,hours:3});// 时间差constdiffnow.until(later);console.log(diff.total({unit:hours}));// 51(4) 无时区日期处理constdateTemporal.PlainDate.from(2026-03-24);constnextMonthdate.add({months:1});console.log(nextMonth.toString());// 2026-04-244.4 如何提前使用 Temporal目前 Temporal 提案处于 Stage 3尚未被所有浏览器原生支持。但可以通过 polyfill 提前体验npminstalljs-temporal/polyfillimport{Temporal}fromjs-temporal/polyfill;constnowTemporal.Now.zonedDateTimeISO();console.log(now.toString());生产环境建议待 Temporal 正式成为标准后再逐步迁移。目前仍推荐 day.js 或 date-fns。图3Temporal 核心类关系图——展示 Instant、ZonedDateTime、PlainDate 等类的关系及各自用途。 五、前端实战用户时区与国际化5.1 获取用户时区浏览器提供了Intl.DateTimeFormat().resolvedOptions().timeZone来获取用户的时区 IDconstuserTimeZoneIntl.DateTimeFormat().resolvedOptions().timeZone;console.log(userTimeZone);// Asia/Shanghai 或 America/New_York这个时区 ID 是 IANA 标准格式如Asia/Shanghai可直接用于 day.js 或 Temporal。5.2 后端返回 UTC前端转换本地假设后端 API 返回 ISO 8601 UTC 字符串{create_time_utc:2026-03-24T06:30:00Z}前端使用 day.js 转换importdayjsfromdayjs;importutcfromdayjs/plugin/utc;importtimezonefromdayjs/plugin/timezone;dayjs.extend(utc);dayjs.extend(timezone);constutcTimedayjs.utc(2026-03-24T06:30:00Z);constlocalTimeutcTime.tz(userTimeZone);console.log(localTime.format(YYYY-MM-DD HH:mm:ss));// 根据用户时区显示5.3 国际化显示多语言日期格式使用Intl.DateTimeFormat或 day.js 的 locale 插件// 原生 IntlconstdatenewDate(2026-03-24T14:30:00Z);constformatternewIntl.DateTimeFormat(zh-CN,{dateStyle:full,timeStyle:medium,timeZone:Asia/Shanghai});console.log(formatter.format(date));// 2026年3月24日星期二 22:30:00// day.js 国际化importdayjs/locale/zh-cn;importdayjs/locale/en;dayjs.locale(zh-cn);console.log(dayjs(date).format(LLLL));// 2026年3月24日星期二 22:305.4 跨端时间处理浏览器/Node.js/小程序浏览器使用上述库注意兼容性Node.js同样可以使用 day.js 或 date-fns但需要注意IntlAPI 可能需要 Node.js 编译时开启 ICU 支持微信小程序不支持Intl完整功能建议使用 day.js 并打包时区数据 六、工程最佳实践6.1 前端存储优先存 UTC 时间戳在本地存储localStorage、IndexedDB中不要存储格式化的字符串而应存储 UTC 时间戳毫秒。这样在读取时可以直接传给 day.js 或 Temporal 进行格式化避免时区混乱。// 存储consttimestampdayjs.utc().valueOf();localStorage.setItem(lastVisit,timestamp);// 读取conststoredparseInt(localStorage.getItem(lastVisit));constlocalTimedayjs(stored).tz(userTimeZone);6.2 接口传输统一用 ISO 8601 格式与后端交互时统一使用2026-03-24T06:30:00Z这样的 UTC 格式。不要使用本地格式字符串也不要使用时间戳除非特殊场景如大量数据传输。6.3 避免依赖客户端时区敏感场景对于订单创建时间、支付时间等敏感业务建议由后端直接返回展示格式如北京时间字符串避免前端时区转换带来的争议。前端只负责展示后端已确定的时间。6.4 性能优化减少频繁创建 Date/Temporal 对象在列表渲染或高频操作中避免在循环中创建大量时间对象。可以预先计算好需要的时间字符串或使用Intl.DateTimeFormat批量格式化。// 优化前items.forEach(item{item.displayTimedayjs(item.time).format(YYYY-MM-DD);});// 优化后使用 formatter 批量格式化constformatternewIntl.DateTimeFormat(zh-CN,{dateStyle:short});items.forEach(item{item.displayTimeformatter.format(newDate(item.time));}); 七、总结与预告7.1 JS时间处理核心建议场景推荐方案简单本地时间展示原生 Date注意坑需要时区转换、相对时间day.js 插件追求极致体积、函数式date-fns新项目期望未来升级day.js待 Temporal 稳定后迁移探索未来标准使用 js-temporal/polyfill 体验核心原则后端统一返回 UTC ISO 8601 字符串前端根据用户时区转换展示存储用时间戳或 UTC 字符串敏感时间由后端决定展示格式7.2 下一篇预告下一篇将进入后端领域《Go/C#/Rust时间处理多语言实战与统一规范》带你掌握主流后端语言的时间处理最佳实践并提炼跨语言通用规范敬请关注如果本文对你有帮助欢迎点赞、收藏、关注三连让更多人看到

相关新闻