JavaScript時間處理Date/moment/dayjs/Temporal方法全解
前端開發(fā)中,時間處理一直是個讓人頭疼的難題。new Date('2026-03-24') 在不同瀏覽器里結(jié)果不同;月份從0開始,11月居然寫成 10;想顯示“2小時前”還要自己寫算法……本文帶你系統(tǒng)梳理 JavaScript 時間處理的演進(jìn)史,從原生 Date 的深坑,到 moment.js 的輝煌與落幕,再到輕量級 day.js 和函數(shù)式 date-fns 的崛起,最后展望未來的標(biāo)準(zhǔn) Temporal API。讀完本文,你將徹底告別時間處理的噩夢,寫出健壯的前端時間代碼。

一、引言:JS時間處理的“噩夢”
1.1 Date對象的設(shè)計缺陷
JavaScript 的 Date 對象從誕生之初就充滿了槽點:
- 易變性:
Date對象是可變的,任何修改都會影響原對象,容易引發(fā)副作用 - 月份從0開始:
new Date(2026, 2, 24)表示的是 3月24日,而不是2月,這是最反直覺的設(shè)計之一 - 時區(qū)模糊:
new Date()返回的是本地時間,但new Date('2026-03-24')在不同瀏覽器可能被解析為UTC或本地時間,導(dǎo)致跨瀏覽器結(jié)果不一致 - 解析行為不統(tǒng)一:
Date.parse()對非標(biāo)準(zhǔn)字符串的解析依賴于實現(xiàn),例如"2026-03-24 14:30:00"在 Chrome 和 Safari 中可能返回不同的結(jié)果 - 缺少人性化方法:沒有內(nèi)置“相對時間”(如“2小時前”)的輸出,需要自己計算
1.2 真實案例:前端時間顯示錯誤
某 SaaS 系統(tǒng)的后臺管理頁面,后端返回訂單時間:"2026-03-24T06:30:00Z"(UTC)。前端直接 new Date(orderTime) 后調(diào)用 toLocaleString(),結(jié)果美國用戶看到的是 3/23/2026, 11:30:00 PM(因為 toLocaleString 默認(rèn)使用用戶本地時區(qū),而 new Date 解析 ISO 字符串時會正確識別 UTC,但展示時轉(zhuǎn)成了本地)。產(chǎn)品經(jīng)理投訴:訂單明明是今天創(chuàng)建的,為什么顯示為昨天?原來是因為時區(qū)轉(zhuǎn)換后,UTC 6:30 對應(yīng)美國西部時間的前一天 22:30。
根源:前端沒有明確控制時區(qū)展示邏輯,依賴了瀏覽器的默認(rèn)行為。
1.3 前端時間處理的痛點
- 跨瀏覽器兼容性:不同瀏覽器對
Date構(gòu)造函數(shù)的字符串解析差異 - 用戶時區(qū)感知:需要根據(jù)用戶所在時區(qū)展示時間(如中國用戶看北京時間,美國用戶看美東時間)
- 國際化顯示:不同語言/地區(qū)的日期格式差異(如
03/24/2026vs24/03/2026) - 性能與體積:移動端或首屏渲染要求庫體積盡可能小
本文將從原生 Date 入手,逐步升級到現(xiàn)代化方案,幫助你根據(jù)場景選擇最合適的時間處理方式。
二、原生Date對象:認(rèn)清坑,用對場景
2.1 Date對象的底層邏輯
Date 對象在 JavaScript 中基于 Unix 時間戳(毫秒),存儲的是自 1970年1月1日 00:00:00 UTC 以來的毫秒數(shù)。時區(qū)信息完全由運行環(huán)境(操作系統(tǒng)/瀏覽器)提供,Date 對象本身不保存時區(qū)。
2.2 基礎(chǔ)操作
(1) 時間創(chuàng)建
// 當(dāng)前時間(本地)
const now = new Date();
console.log(now); // Thu Mar 24 2026 14:30:00 GMT+0800 (中國標(biāo)準(zhǔn)時間)
// 從時間戳創(chuàng)建(毫秒)
const fromTimestamp = new Date(1742807400000);
console.log(fromTimestamp); // 2026-03-24T06:30:00.000Z (UTC)
// 從ISO字符串創(chuàng)建(推薦)
const iso = new Date('2026-03-24T06:30:00Z');
console.log(iso.toISOString()); // 2026-03-24T06:30:00.000Z
// 從年月日創(chuàng)建(注意月份從0開始)
const dt = new Date(2026, 2, 24, 14, 30, 0); // 2026-03-24 14:30:00 本地時間
console.log(dt); // Thu Mar 24 2026 14:30:00 GMT+0800 (中國標(biāo)準(zhǔn)時間)
(2) UTC方法 vs 本地方法
Date 對象提供了兩套獲取時間組件的方法:本地方法(基于系統(tǒng)時區(qū))和 UTC 方法(基于 UTC+0)。
const date = new Date('2026-03-24T14:30:00+08:00'); // 北京時間 14:30
console.log(date.getHours()); // 14 (本地小時,取決于系統(tǒng)時區(qū))
console.log(date.getUTCHours()); // 6 (UTC小時)
console.log(date.getMonth()); // 2 (3月,因為從0開始)
console.log(date.getUTCMonth()); // 2
(3) 格式化
Date 原生提供幾種格式化方法:
const d = new Date(); d.toString(); // "Thu Mar 24 2026 14:30:00 GMT+0800 (中國標(biāo)準(zhǔn)時間)" d.toISOString(); // "2026-03-24T06:30:00.000Z" d.toUTCString(); // "Tue, 24 Mar 2026 06:30:00 GMT" d.toLocaleString(); // "2026/3/24 14:30:00" (取決于瀏覽器語言) d.toLocaleDateString(); // "2026/3/24"
2.3 避坑指南(四個經(jīng)典坑)
坑1:new Date('2026-03-24')是UTC還是本地?
答案:不同瀏覽器行為不一致!根據(jù) ES5 規(guī)范,僅包含日期的 ISO 8601 字符串應(yīng)解析為 UTC,但部分舊瀏覽器或非標(biāo)準(zhǔn)實現(xiàn)會當(dāng)作本地時間。
// 在 Chrome 中:解析為 UTC
new Date('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)) 構(gòu)造 UTC 時間。
坑2:月份從0開始
const month = new Date().getMonth(); // 3月 => 2
解決方案:封裝輔助函數(shù),或者使用第三方庫。
function getRealMonth(date) {
return date.getMonth() + 1;
}
坑3:Date對象是可變的
const date1 = new Date(); const date2 = date1; date2.setDate(10); // date1 也被修改了!
解決方案:需要拷貝時,使用 new Date(date1) 創(chuàng)建新對象。
坑4:解析非標(biāo)準(zhǔn)時間字符串的兼容性問題
const d = new Date('2026-03-24 14:30:00'); // 非 ISO 格式,可能返回 Invalid Date
解決方案:永遠(yuǎn)不要用 Date 解析非標(biāo)準(zhǔn)格式。要么用正則拆解,要么用第三方庫。
2.4 原生Date的適用場景
- 簡單的本地時間展示(不涉及時區(qū)轉(zhuǎn)換)
- 獲取當(dāng)前時間戳(
Date.now()) - 輕量級場景,且你已清楚上述坑并做了規(guī)避
對于復(fù)雜的時區(qū)、格式化和相對時間,建議使用第三方庫。
圖1:Date對象常見坑點圖示——用思維導(dǎo)圖展示月份從0、可變性、解析不一致、UTC/本地混淆四個坑點及其解決方案。
三、第三方庫:從moment.js到day.js
3.1 moment.js:經(jīng)典但笨重
moment.js 曾經(jīng)是前端時間處理的王者,功能強大,但體積較大(約 200KB+),且不支持 Tree Shaking。目前官方已進(jìn)入維護(hù)模式,不再添加新功能,推薦使用更現(xiàn)代的替代品。
核心功能示例:
// 安裝:npm install moment
import moment from 'moment';
// 創(chuàng)建
const now = moment(); // 當(dāng)前時間
const utc = moment.utc(); // UTC時間
const fromStr = moment('2026-03-24T14:30:00+08:00'); // 自動識別
// 時區(qū)轉(zhuǎn)換(需要 moment-timezone 擴展)
import momentTz from 'moment-timezone';
const beijing = moment.tz('Asia/Shanghai');
const newYork = beijing.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 - 官方已停止功能開發(fā),僅維護(hù)安全漏洞
3.2 day.js:輕量替代(2KB)
day.js 的 API 與 moment.js 幾乎一致,但體積僅 2KB,支持插件擴展,是目前最受歡迎的輕量級時間庫。
安裝與使用:
npm install dayjs
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import relativeTime from 'dayjs/plugin/relativeTime';
import 'dayjs/locale/zh-cn';
// 擴展插件
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(relativeTime);
dayjs.locale('zh-cn'); // 設(shè)置中文
// 基礎(chǔ)操作
const now = dayjs(); // 當(dāng)前時間(本地)
const utcNow = dayjs.utc(); // UTC時間
const parsed = dayjs('2026-03-24T14:30:00+08:00');
// 時區(qū)轉(zhuǎn)換
const beijing = dayjs.tz('2026-03-24 14:30:00', 'Asia/Shanghai');
const newYork = beijing.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小時前"
// 運算(不可變,返回新對象)
const tomorrow = now.add(1, 'day');
day.js + 時區(qū)插件實現(xiàn) UTC ↔ 北京時間互轉(zhuǎn):
// UTC 轉(zhuǎn)北京時間
const utcTime = dayjs.utc('2026-03-24T06:30:00Z');
const beijingTime = utcTime.tz('Asia/Shanghai');
console.log(beijingTime.format()); // 2026-03-24T14:30:00+08:00
// 北京時間轉(zhuǎn) UTC
const beijing = dayjs.tz('2026-03-24 14:30:00', 'Asia/Shanghai');
const utc = beijing.utc();
console.log(utc.format()); // 2026-03-24T06:30:00Z
3.3 date-fns:函數(shù)式時間處理
date-fns 采用純函數(shù)、按需導(dǎo)入的方式,每個功能都是獨立函數(shù),體積小,適合現(xiàn)代打包工具。
安裝與使用:
npm install date-fns
import { format, addDays, differenceInHours, formatDistance } from 'date-fns';
import { zhCN } from 'date-fns/locale';
const now = new Date();
// 格式化
console.log(format(now, 'yyyy-MM-dd HH:mm:ss')); // 2026-03-24 14:30:00
// 運算(返回新對象)
const tomorrow = addDays(now, 1);
// 時間差
const diff = differenceInHours(tomorrow, now); // 24
// 相對時間
console.log(formatDistance(now, addDays(now, 2), { locale: zhCN })); // "2天"
date-fns 的優(yōu)勢:
- 完全按需,打包后體積小
- 不可變性,純函數(shù),易于測試
- 支持時區(qū)(通過
date-fns-tz擴展包)
3.4 庫選型建議
| 庫 | 體積 | 特點 | 適用場景 |
|---|---|---|---|
| moment.js | ~200KB | 功能全面,API 穩(wěn)定 | 老舊項目維護(hù),不推薦新項目 |
| day.js | ~2KB | 輕量,API 兼容 moment,插件化 | 大多數(shù)新項目,需時區(qū)/相對時間 |
| date-fns | 按需 | 函數(shù)式,不可變,無副作用 | 追求打包體積、使用現(xiàn)代開發(fā)模式 |
| 原生 Date | 0 | 無依賴 | 極簡場景,且已充分了解坑點 |
圖2:庫選型對比圖——表格形式展示 moment、dayjs、date-fns、原生在體積、API風(fēng)格、功能完整性、推薦指數(shù)上的對比。
四、未來標(biāo)準(zhǔn):Temporal API
4.1 Temporal API 的設(shè)計目標(biāo)
Temporal 是 TC39 正在推進(jìn)的提案,旨在徹底解決 Date 對象的所有缺陷。它提供了不可變、語義清晰、支持時區(qū)、支持高精度時間的新 API。
核心特性:
- 不可變性:所有操作返回新對象
- 明確的類型:區(qū)分帶時區(qū)的時間、無時區(qū)日期、無時區(qū)時間、時間段等
- 支持時區(qū):內(nèi)置 IANA 時區(qū)數(shù)據(jù)庫
- 支持高精度:納秒級精度
- 直觀的 API:年份從1開始,月份從1開始,無需再記憶坑點
4.2 核心類解析
| 類 | 說明 | 示例 |
|---|---|---|
| Temporal.Instant | 絕對的納秒級時間點(類似 Unix 時間戳) | 用于機器存儲 |
| Temporal.ZonedDateTime | 帶時區(qū)的日期時間(推薦業(yè)務(wù)使用) | 2026-03-24T14:30:00+08:00[Asia/Shanghai] |
| Temporal.PlainDate | 不帶時區(qū)的日期(生日、紀(jì)念日) | 2026-03-24 |
| Temporal.PlainTime | 不帶日期的時間 | 14:30:00 |
| Temporal.PlainDateTime | 不帶時區(qū)的日期+時間 | 2026-03-24T14:30:00 |
| Temporal.Duration | 時間段(支持年/月/日/時/分/秒/毫秒/微秒/納秒) | { hours: 2, minutes: 30 } |
4.3 實戰(zhàn)案例
(1) 創(chuàng)建 UTC 時間和本地時間
// 當(dāng)前 UTC 時間(Instant)
const now = Temporal.Now.instant();
console.log(now.toString()); // 2026-03-24T06:30:00.123456789Z
// 當(dāng)前系統(tǒng)時區(qū)的 ZonedDateTime
const nowInLocal = Temporal.Now.zonedDateTimeISO();
console.log(nowInLocal.toString()); // 2026-03-24T14:30:00.123456789+08:00[Asia/Shanghai]
// 從 ISO 字符串創(chuàng)建 ZonedDateTime
const beijing = Temporal.ZonedDateTime.from('2026-03-24T14:30:00+08:00[Asia/Shanghai]');
console.log(beijing.toString()); // 2026-03-24T14:30:00+08:00[Asia/Shanghai]
(2) 時區(qū)轉(zhuǎn)換
const beijing = Temporal.ZonedDateTime.from('2026-03-24T14:30:00+08:00[Asia/Shanghai]');
const newYork = beijing.withTimeZone('America/New_York');
console.log(newYork.toString()); // 2026-03-24T02:30:00-04:00[America/New_York]
(3) 時間運算
const now = Temporal.Now.zonedDateTimeISO();
// 加 2 天 3 小時
const later = now.add({ days: 2, hours: 3 });
// 時間差
const diff = now.until(later);
console.log(diff.total({ unit: 'hours' })); // 51
(4) 無時區(qū)日期處理
const date = Temporal.PlainDate.from('2026-03-24');
const nextMonth = date.add({ months: 1 });
console.log(nextMonth.toString()); // 2026-04-24
4.4 如何提前使用 Temporal
目前 Temporal 提案處于 Stage 3,尚未被所有瀏覽器原生支持。但可以通過 polyfill 提前體驗:
npm install @js-temporal/polyfill
import { Temporal } from '@js-temporal/polyfill';
const now = Temporal.Now.zonedDateTimeISO();
console.log(now.toString());
生產(chǎn)環(huán)境建議:待 Temporal 正式成為標(biāo)準(zhǔn)后,再逐步遷移。目前仍推薦 day.js 或 date-fns。
圖3:Temporal 核心類關(guān)系圖——展示 Instant、ZonedDateTime、PlainDate 等類的關(guān)系及各自用途。
五、前端實戰(zhàn):用戶時區(qū)與國際化
5.1 獲取用戶時區(qū)
瀏覽器提供了 Intl.DateTimeFormat().resolvedOptions().timeZone 來獲取用戶的時區(qū) ID:
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; console.log(userTimeZone); // "Asia/Shanghai" 或 "America/New_York"
這個時區(qū) ID 是 IANA 標(biāo)準(zhǔn)格式(如 Asia/Shanghai),可直接用于 day.js 或 Temporal。
5.2 后端返回 UTC,前端轉(zhuǎn)換本地
假設(shè)后端 API 返回 ISO 8601 UTC 字符串:
{
"create_time_utc": "2026-03-24T06:30:00Z"
}前端使用 day.js 轉(zhuǎn)換:
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
dayjs.extend(utc);
dayjs.extend(timezone);
const utcTime = dayjs.utc('2026-03-24T06:30:00Z');
const localTime = utcTime.tz(userTimeZone);
console.log(localTime.format('YYYY-MM-DD HH:mm:ss')); // 根據(jù)用戶時區(qū)顯示
5.3 國際化顯示(多語言日期格式)
使用 Intl.DateTimeFormat 或 day.js 的 locale 插件:
// 原生 Intl
const date = new Date('2026-03-24T14:30:00Z');
const formatter = new Intl.DateTimeFormat('zh-CN', {
dateStyle: 'full',
timeStyle: 'medium',
timeZone: 'Asia/Shanghai'
});
console.log(formatter.format(date)); // 2026年3月24日星期二 22:30:00
// day.js 國際化
import 'dayjs/locale/zh-cn';
import 'dayjs/locale/en';
dayjs.locale('zh-cn');
console.log(dayjs(date).format('LLLL')); // 2026年3月24日星期二 22:30
5.4 跨端時間處理(瀏覽器/Node.js/小程序)
- 瀏覽器:使用上述庫,注意兼容性
- Node.js:同樣可以使用 day.js 或 date-fns,但需要注意
IntlAPI 可能需要 Node.js 編譯時開啟 ICU 支持 - 微信小程序:不支持
Intl完整功能,建議使用 day.js 并打包時區(qū)數(shù)據(jù)
六、工程最佳實踐
6.1 前端存儲:優(yōu)先存 UTC 時間戳
在本地存儲(localStorage、IndexedDB)中,不要存儲格式化的字符串,而應(yīng)存儲 UTC 時間戳(毫秒)。這樣在讀取時可以直接傳給 day.js 或 Temporal 進(jìn)行格式化,避免時區(qū)混亂。
// 存儲
const timestamp = dayjs.utc().valueOf();
localStorage.setItem('lastVisit', timestamp);
// 讀取
const stored = parseInt(localStorage.getItem('lastVisit'));
const localTime = dayjs(stored).tz(userTimeZone);
6.2 接口傳輸:統(tǒng)一用 ISO 8601 格式
與后端交互時,統(tǒng)一使用 2026-03-24T06:30:00Z 這樣的 UTC 格式。不要使用本地格式字符串,也不要使用時間戳(除非特殊場景,如大量數(shù)據(jù)傳輸)。
6.3 避免依賴客戶端時區(qū)(敏感場景)
對于訂單創(chuàng)建時間、支付時間等敏感業(yè)務(wù),建議由后端直接返回展示格式(如北京時間字符串),避免前端時區(qū)轉(zhuǎn)換帶來的爭議。前端只負(fù)責(zé)展示后端已確定的時間。
6.4 性能優(yōu)化:減少頻繁創(chuàng)建 Date/Temporal 對象
在列表渲染或高頻操作中,避免在循環(huán)中創(chuàng)建大量時間對象??梢灶A(yù)先計算好需要的時間字符串,或使用 Intl.DateTimeFormat 批量格式化。
// 優(yōu)化前
items.forEach(item => {
item.displayTime = dayjs(item.time).format('YYYY-MM-DD');
});
// 優(yōu)化后:使用 formatter 批量格式化
const formatter = new Intl.DateTimeFormat('zh-CN', { dateStyle: 'short' });
items.forEach(item => {
item.displayTime = formatter.format(new Date(item.time));
});
七、總結(jié)與預(yù)告
7.1 JS時間處理核心建議
| 場景 | 推薦方案 |
|---|---|
| 簡單本地時間展示 | 原生 Date(注意坑) |
| 需要時區(qū)轉(zhuǎn)換、相對時間 | day.js + 插件 |
| 追求極致體積、函數(shù)式 | date-fns |
| 新項目,期望未來升級 | day.js,待 Temporal 穩(wěn)定后遷移 |
| 探索未來標(biāo)準(zhǔn) | 使用 @js-temporal/polyfill 體驗 |
核心原則:
- 后端統(tǒng)一返回 UTC ISO 8601 字符串
- 前端根據(jù)用戶時區(qū)轉(zhuǎn)換展示
- 存儲用時間戳或 UTC 字符串
- 敏感時間由后端決定展示格式
到此這篇關(guān)于JavaScript時間處理Date/moment/dayjs/Temporal方法全解的文章就介紹到這了,更多相關(guān)JavaScript時間處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
跟我學(xué)習(xí)javascript的Date對象
跟我學(xué)習(xí)javascript的Date對象,文章主要介紹了Date 日期和時間對象的操作,文章末尾為大家附加了兩個案例,幫助大家更好的學(xué)習(xí)javascript的Date對象,對這方面感興趣的小伙伴們可以參考一下2015-11-11
整理Javascript數(shù)組學(xué)習(xí)筆記
整理Javascript數(shù)組學(xué)習(xí)筆記,之前一系列的文章是跟我學(xué)習(xí)Javascript,本文就是進(jìn)一步學(xué)習(xí)javascript數(shù)組,希望大家繼續(xù)關(guān)注2015-11-11

