JavaScript 日期與時間(Date & Time)── 建立 Date 物件
簡介
在前端與後端的程式開發中,日期與時間是最常被使用的資料型別之一。無論是顯示使用者的最後登入時間、計算兩個事件之間的間隔,或是產生具備時區資訊的報表,都離不開 Date 物件。
JavaScript 內建的 Date 類別提供了完整的日期/時間操作介面,但若對它的建立方式不熟悉,往往會出現時區錯誤、月份錯位等常見問題。本文將從最基礎的 建立 Date 物件 開始說明,帶你一步步掌握正確的使用方法,並透過實務範例展示如何在真實專案中運用。
核心概念
1. 為什麼要使用 new Date()
Date 是 JavaScript 的原生建構子,使用 new Date() 會回傳一個 可變動(mutable)的物件,內部保存了自 1970 年 1 月 1 日 00:00:00 UTC 起的毫秒數(稱為 timestamp)。透過這個時間戳,我們可以:
- 取得或設定年、月、日、時、分、秒、毫秒
- 轉換成不同的字串格式(ISO、RFC、Locale)
- 計算時間差、做日期加減運算
注意:
Date物件本身是 可變 的,對同一個實例的修改會直接影響原始值。若需要不可變的時間點,請使用Date.now()取得純數值,或在修改前先clone。
2. 建立方式概覽
| 建構子呼叫方式 | 產生的 Date 內容 | 典型使用情境 |
|---|---|---|
new Date() |
目前本機時間 | 取得現在時間、顯示即時時鐘 |
new Date(value) |
value 為時間戳(毫秒)或 ISO 字串 |
由伺服器傳回的毫秒數或 ISO 8601 字串建立 |
new Date(dateString) |
解析字串(支援 ISO、RFC、簡易格式) | 直接使用固定字串如 '2024-12-31' |
new Date(year, monthIndex, day, hour, minute, second, millisecond) |
依參數建立,月以 0 起始(0=Jan) | 手動組合日期、時間,常用於表單或日曆功能 |
Date.now()(非建構子) |
直接回傳時間戳(毫秒) | 需要純數值作為鍵值或計算時 |
下面分別說明每種方式的細節與注意點。
2.1 new Date():取得現在時間
// 取得目前本機時間的 Date 物件
const now = new Date();
console.log(now); // 2025-11-19T07:45:32.123Z(依執行環境而異)
console.log(now.toString()); // Fri Nov 19 2025 15:45:32 GMT+0800 (Taipei Standard Time)
new Date() 會自動以 本機時區 產生時間。如果要取得 UTC 時間,可使用 now.toISOString() 或 now.getUTC*() 系列方法。
2.2 new Date(value):從時間戳或 ISO 字串建立
// 1. 以毫秒時間戳建立(1970-01-01 起算)
const timestamp = 1735689600000; // 2025-01-01T00:00:00.000Z
const dateFromTs = new Date(timestamp);
console.log(dateFromTs.toUTCString()); // "Wed, 01 Jan 2025 00:00:00 GMT"
// 2. 以 ISO 8601 字串建立
const isoString = '2025-11-19T12:30:00+08:00';
const dateFromIso = new Date(isoString);
console.log(dateFromIso.toString()); // "Fri Nov 19 2025 12:30:00 GMT+0800 (Taipei Standard Time)"
- 只要傳入 有效的數值(毫秒)或 ISO 8601 格式字串,
Date會自動解析為正確的時間點。 - 非 ISO 格式的字串(如
'2025/11/19')在不同瀏覽器的解析結果可能不一致,建議統一使用 ISO。
2.3 new Date(dateString):字串解析
// 簡易的日期字串(不含時區資訊)
// 會以本機時區當作解析基準
const simple = new Date('2025-12-31');
console.log(simple.toString()); // "Thu Dec 31 2025 00:00:00 GMT+0800 (Taipei Standard Time)"
// RFC 2822 格式
const rfc = new Date('Fri, 19 Nov 2025 15:00:00 GMT');
console.log(rfc.toUTCString()); // "Fri, 19 Nov 2025 15:00:00 GMT"
⚠️ 常見陷阱:
new Date('2025-12-31')只包含日期,時間會被補成00:00:00。- 若字串缺少時區資訊,會以 本機時區 解析,跨時區系統時需特別留意。
2.4 new Date(year, monthIndex, …):逐參數建立
// 注意:月份是 0 起始(0 = 1 月,11 = 12 月)
const birthday = new Date(1990, 4, 21, 8, 30, 0); // 1990-05-21 08:30:00
console.log(birthday.toLocaleString('zh-TW', { timeZone: 'Asia/Taipei' }));
// "1990/5/21 下午 8:30:00"
2.4.1 為什麼月份是 0 起始?
JavaScript 的設計者在早期為了與 Array 的索引保持一致,將月份設計為 0‑11。這是新手最常踩的坑,以下示範說明:
const march = new Date(2025, 2, 1); // 2025-03-01,因為 2 代表 3 月
console.log(march.getMonth()); // 2(仍是 0 起始)
若要顯示給使用者,務必使用 getMonth() + 1 或 toLocaleString() 直接格式化。
2.5 Date.now():取得純時間戳
const nowTs = Date.now(); // 例如 1737367532123
// 可直接用於排序、作為唯一鍵值等
const logs = [
{ id: 1, time: 1737367000000 },
{ id: 2, time: nowTs },
];
logs.sort((a, b) => a.time - b.time);
Date.now() 不會產生 Date 物件,對效能要求高的迴圈或計時器特別有用。
程式碼範例
以下提供 5 個實務上常見 的建立 Date 方式,並加上詳細註解。
範例 1:顯示即時時鐘(每秒更新)
function startClock(containerId) {
const el = document.getElementById(containerId);
function update() {
const now = new Date(); // 取得現在時間
// 以台北時區的本地化字串顯示
el.textContent = now.toLocaleString('zh-TW', {
hour12: false,
timeZone: 'Asia/Taipei'
});
}
update(); // 立即顯示一次
setInterval(update, 1000); // 每秒更新
}
實務意義:在儀表板、聊天訊息時間戳等 UI 中,常需要即時更新的時間顯示。
範例 2:將伺服器傳回的毫秒時間戳轉成可讀格式
// 假設從 API 取得的資料
fetch('/api/events')
.then(r => r.json())
.then(data => {
data.forEach(event => {
// event.time 為毫秒時間戳
const d = new Date(event.time);
const formatted = d.toLocaleDateString('zh-TW', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false,
timeZone: 'Asia/Taipei'
});
console.log(`事件 ${event.id} 發生於 ${formatted}`);
});
});
重點:使用
toLocaleDateString可以一次完成日期與時間的本地化格式化。
範例 3:手動建立「生日」Date 物件,並計算年齡
function calculateAge(year, month, day) {
// month 為 1~12,需減 1 轉為 0 起始
const birth = new Date(year, month - 1, day);
const now = new Date();
let age = now.getFullYear() - birth.getFullYear();
// 若當年尚未過生日,年齡減 1
if (
now.getMonth() < birth.getMonth() ||
(now.getMonth() === birth.getMonth() && now.getDate() < birth.getDate())
) {
age--;
}
return age;
}
console.log(calculateAge(1995, 8, 15)); // 依執行時間不同而異
應用:會員系統、保險投保年齡驗證等場景。
範例 4:跨時區顯示同一瞬間的本地時間
const utcNow = new Date('2025-11-19T04:00:00Z'); // UTC 時間
// 以不同時區格式化
const taipei = utcNow.toLocaleString('zh-TW', { timeZone: 'Asia/Taipei' });
const newYork = utcNow.toLocaleString('en-US', { timeZone: 'America/New_York' });
const london = utcNow.toLocaleString('en-GB', { timeZone: 'Europe/London' });
console.log('台北:', taipei); // 台北: 2025/11/19 12:00:00
console.log('紐約:', newYork); // 紐約: 11/18/2025, 11:00:00 PM
console.log('倫敦:', london); // 倫敦: 19/11/2025, 04:00:00
技巧:
toLocaleString的timeZone參數讓我們可以在同一Date物件上呈現多個時區的時間,避免手動計算時差。
範例 5:使用 Date.now() 實作簡易防止重複提交的 Token
function generateSubmitToken() {
// 使用當前毫秒數 + 隨機字元組成唯一字串
return `${Date.now().toString(36)}-${Math.random().toString(36).substr(2,5)}`;
}
// 表單送出前呼叫
const token = generateSubmitToken();
console.log('提交 Token:', token);
// => 1kz2e5c-9xv7q
實務價值:在需要防止重複提交(例如付款、訂單)時,快速產生唯一識別碼。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
| 月份 0 起始 | new Date(2025, 11, 1) 產生 12 月 1 日,new Date(2025, 12, 1) 會自動跳到次年 1 月 1 日。 |
建立日期前先 month - 1,或使用 Date.UTC() 直接以 1 起始的月份建立 UTC 日期。 |
| 字串解析不一致 | new Date('2025/11/19') 在 Chrome 會正確解析,但在 Safari 可能得到 Invalid Date。 |
統一使用 ISO 8601(YYYY-MM-DDTHH:mm:ss.sssZ)或手動分割字串後使用參數建構子。 |
| 時區自動轉換 | new Date('2025-11-19') 會以本機時區解讀,導致跨時區顯示錯誤。 |
若要固定為 UTC,使用 new Date(Date.UTC(year, month, day));或在格式化時指定 timeZone。 |
Date 物件可變 |
let d = new Date(); d.setFullYear(2000); 會改變原始物件。 |
若需要保留原始值,先 const clone = new Date(d.getTime()); 再操作 clone。 |
| 毫秒精度與性能 | 在大量計算(如動畫)中頻繁呼叫 new Date() 會產生額外 GC。 |
使用 performance.now()(高解析度)或 Date.now() 取得純數值,減少物件建立。 |
最佳實踐小結
- 統一使用 ISO 8601:避免跨瀏覽器解析差異。
- 明確指定時區:在
toLocaleString、toLocaleDateString、toLocaleTimeString時使用timeZone參數。 - 封裝日期工具:將常用的建立、格式化、計算功能抽成模組(如
dateUtils.js),提升可讀性與維護性。 - 避免直接操作全域
Date:盡量使用const now = Date.now();取得時間戳,再根據需求轉換。 - 測試跨時區情境:特別是涉及國際化的應用,務必在不同時區的環境下驗證結果。
實際應用場景
| 場景 | 需求 | 建議的 Date 建立方式 |
|---|---|---|
| 訂單系統:記錄下單時間、計算付款期限(48 小時) | 必須精確到毫秒,並避免時區漂移 | 使用 Date.now() 取得時間戳,存入資料庫;取出後 new Date(timestamp) 轉為 Date 物件計算。 |
| 行事曆 UI:顯示每月日曆,支援跨月切換 | 需要根據月份產生首日與最後一日 | new Date(year, monthIndex, 1) 產生當月第一天;new Date(year, monthIndex + 1, 0) 取得當月最後一天(day=0 會回到前一月的最後一天)。 |
| 國際化報表:同一筆交易在不同國家顯示本地時間 | 必須根據使用者時區渲染 | 先以 UTC 時間儲存(new Date(Date.UTC(...))),顯示時使用 toLocaleString('zh-TW', { timeZone: userTZ })。 |
| 倒數計時:活動倒數 10 天 5 小時 | 需要持續更新且效能佳 | 使用 Date.now() 與 setInterval,每秒計算 target - Date.now(),不必每次都建立 Date 物件。 |
| 日誌系統:高頻率寫入時間戳 | 需要低延遲、低記憶體占用 | 直接使用 Date.now(),避免 new Date() 產生額外物件。 |
總結
建立 Date 物件是 JavaScript 中處理時間的第一步,也是最容易產生錯誤的環節。本文從 基礎的 new Date()、時間戳與字串解析、逐參數建立,到 Date.now() 的差異,提供了完整的概念說明與實務範例。透過 統一使用 ISO 8601、明確指定時區、避免月份 0 起始的陷阱,以及 封裝工具函式 的最佳實踐,你可以在任何前端或後端專案中自信地操作日期與時間。
關鍵要點:
- 建立 時,優先選擇
new Date(value)(時間戳或 ISO)或new Date(year, monthIndex, …),避免瀏覽器差異。- 格式化 時,使用
toLocaleString並指定timeZone,確保跨時區正確顯示。- 計算 時,盡量操作毫秒時間戳(
Date.now()),減少不必要的物件建立。
掌握了這些技巧,你就能在專案中正確、有效率地處理所有日期與時間相關的需求。祝開發順利,時間掌控在你手中!