本文 AI 產出,尚未審核
JavaScript 日期與時間:時區與 UTC
簡介
在前端與後端開發中,日期與時間的處理往往是最容易出錯的環節。不同的使用者可能身處不同的時區,伺服器又可能部署在另一個時區,若沒有統一的時間基準,資料庫的時間戳記、排程任務或跨國報表都可能產生錯誤。
ECMAScript 為我們提供了 Date 物件與 UTC(Coordinated Universal Time) 作為全球統一的時間參考。了解如何在 JavaScript 中正確使用 UTC、轉換時區,才能保證程式在任何地域都能正確運作。本文將從概念說明、實作範例、常見陷阱與最佳實踐,帶你一步步掌握時區與 UTC 的使用技巧。
核心概念
1. 為什麼要使用 UTC?
- 唯一性:UTC 不受夏令時間或地域變化影響,所有時間點都能唯一對應。
- 跨系統一致性:伺服器、資料庫、前端 UI 都可使用同一基準,避免「同一筆資料在不同地方顯示不同」的問題。
- 簡化計算:在 UTC 上做加減法(如加 8 小時得到台北時間)比在本地時區上直接運算更直觀。
2. Date 物件的兩套 API
| 方法類別 | 代表的時間基準 | 常用方法 |
|---|---|---|
| 本地時間 | 依照執行環境的時區 | getFullYear()、getMonth()、getDate()、getHours()… |
| UTC 時間 | 以 UTC 為基準 | getUTCFullYear()、getUTCMonth()、getUTCDate()、getUTCHours()… |
註:
Date內部只儲存 毫秒級的 UTC 時間戳記(自 1970-01-01T00:00:00Z 起的毫秒數),上述兩套 API 只是「顯示」的方式不同。
3. 建立 UTC 時間的 Date 物件
// 1. 使用 ISO 8601 字串,直接以 UTC 解析
const utcFromString = new Date('2025-12-31T23:59:59Z'); // Z 表示 UTC
// 2. 使用 Date.UTC() 產生時間戳記,再建構 Date
const timestamp = Date.UTC(2025, 11, 31, 23, 59, 59); // 月份 0~11,注意 11 代表 12 月
const utcFromTimestamp = new Date(timestamp);
console.log(utcFromString.toISOString()); // 2025-12-31T23:59:59.000Z
console.log(utcFromTimestamp.toISOString()); // 同上
Date.UTC() 會回傳 UTC 毫秒數,因此即使執行環境在 GMT+8,產生的 Date 仍是同一個瞬間。
4. 本地時間 ↔ UTC 的相互轉換
// 假設現在的本地時間是台北 (UTC+8)
const localNow = new Date(); // 依系統時區產生
const utcMillis = localNow.getTime(); // 同樣是 UTC 毫秒數
const utcDate = new Date(utcMillis); // 直接用 UTC 建構
// 取得本地時間的各項資訊
console.log('本地時間:', localNow.toString()); // 例如 "Tue Dec 31 2025 07:59:59 GMT+0800 (台北標準時間)"
// 轉成 UTC 字串
console.log('UTC 時間:', utcDate.toISOString()); // "2025-12-31T23:59:59.000Z"
5. 手動調整時區偏移(常見需求:顯示特定時區時間)
/**
* 取得指定時區(以 hour 為單位)的時間字串
* @param {Date} date 基礎 Date 物件(視為 UTC)
* @param {number} offsetHours 時區偏移,正值表示東區 (UTC+),負值表示西區 (UTC-)
* @returns {string} 格式化後的時間,例如 "2025-12-31 15:00:00"
*/
function formatInZone(date, offsetHours) {
const msOffset = offsetHours * 60 * 60 * 1000;
const zoned = new Date(date.getTime() + msOffset); // 直接加上偏移量
const pad = n => String(n).padStart(2, '0');
return `${zoned.getUTCFullYear()}-${pad(zoned.getUTCMonth() + 1)}-${pad(zoned.getUTCDate())} ` +
`${pad(zoned.getUTCHours())}:${pad(zoned.getUTCMinutes())}:${pad(zoned.getUTCSeconds())}`;
}
// 範例:把 UTC 時間轉成紐約時間 (UTC-5, 冬季)
const utcNow = new Date(); // 仍是 UTC 時間
console.log('紐約時間:', formatInZone(utcNow, -5));
重點:在計算時使用
getUTC*方法,避免因本地時區自動套用造成誤差。
6. 使用國際化 API(Intl.DateTimeFormat)自動處理時區
const date = new Date('2025-12-31T23:00:00Z');
// 以「台北」時區顯示
const formatterTW = new Intl.DateTimeFormat('zh-TW', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit',
timeZone: 'Asia/Taipei',
});
console.log('台北時間:', formatterTW.format(date)); // "2025/12/31 07:00:00"
// 以「洛杉矶」時區顯示
const formatterLA = new Intl.DateTimeFormat('en-US', {
year: 'numeric', month: 'short', day: 'numeric',
hour: 'numeric', minute: 'numeric', second: 'numeric',
timeZone: 'America/Los_Angeles',
});
console.log('洛杉矶時間:', formatterLA.format(date)); // "Dec 31, 2025, 3:00:00 PM"
Intl 會自動考慮 夏令時間、歷史時區變更,是目前最安全、最簡潔的時區顯示方式。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
直接使用 new Date() 再轉成字串 |
toString() 會依執行環境時區輸出,跨國使用時易產生混淆。 |
盡量使用 toISOString()(UTC)或 Intl.DateTimeFormat 指定時區。 |
| 月份從 0 開始 | Date 建構子與 Date.UTC 的月份參數以 0 為基礎(0=1月),新手常忘記。 |
註解或使用 moment/dayjs 等套件的 month() 方法(1~12)以提升可讀性。 |
| 夏令時間(DST)未考慮 | 手動加減時區偏移會在夏令時間切換時出錯。 | 使用 Intl.DateTimeFormat 或 Temporal(提案階段)自動處理 DST。 |
| 時間字串缺少時區資訊 | new Date('2025-12-31 23:00:00') 會被視為本地時區,導致不同環境結果不同。 |
永遠使用 ISO 8601 並加上 Z 或明確的時區標示(如 2025-12-31T23:00:00+08:00)。 |
使用 Date.parse() 解析非標準字串 |
不同瀏覽器對非標準格式的支援度不一。 | 只解析符合 RFC 3339 / ISO 8601 的字串,或使用可靠的日期套件。 |
最佳實踐清單
- 所有儲存的時間(資料庫、Log、API)都使用 UTC。
- 前端顯示 時,使用
Intl.DateTimeFormat指定使用者的時區。 - 計算時間差 時,直接使用毫秒值 (
dateA - dateB) 再轉換為天、時、分。 - 避免自行硬編時區偏移,除非確定不會受 DST 影響。
- 若專案需要頻繁操作時區,考慮 Temporal(Stage 3)或成熟的第三方庫(
dayjs+utc/timezone插件、luxon)。
實際應用場景
1. 多國電商的訂單時間紀錄
- 需求:訂單建立時間必須在資料庫保持唯一,且在管理後台顯示時要依使用者所在時區自動轉換。
- 做法:前端在送出訂單時,將
new Date().toISOString()(UTC)作為orderTime欄位;後台使用Intl.DateTimeFormat依使用者timezone(可從 JWT 或瀏覽器Intl.DateTimeFormat().resolvedOptions().timeZone取得)顯示。
2. 排程系統(Cron)與伺服器時區不一致
- 需求:每日凌晨 02:00(台北時間)執行資料匯入。
- 做法:在伺服器(可能在 UTC)上設定 Cron 為
02:00 UTC+8→ 實際寫成18:00 UTC,或在程式內使用node-cron並傳入tz: 'Asia/Taipei'參數,由套件自行處理時區與 DST。
3. 跨時區聊天室訊息時間戳
- 需求:每則訊息顯示「5 分鐘前」或「2025/12/31 15:20」等相對時間。
- 做法:後端儲存訊息的
createdAt為 UTC,前端收到後利用date-fns的formatDistanceToNow並傳入addSuffix: true, locale: zhTW,自動根據使用者瀏覽器時區呈現相對時間。
總結
時區與 UTC 是 跨地域應用的基石,掌握 Date 內部的 UTC 時間戳記、善用 Intl.DateTimeFormat 以及遵守「資料庫儲存 UTC、前端顯示本地」的原則,可大幅降低時間相關 Bug 的風險。
- 記住:
Date只保存 UTC 毫秒,所有本地化的顯示都需要透過相應的 API 或套件轉換。 - 避免:自行硬編時區偏移、忽略夏令時間、使用不標準的字串格式。
- 實踐:在專案中統一 UTC 儲存、使用
Intl或Temporal處理時區,並在文件與團隊規範中明確寫下時間處理的流程。
透過上述概念與實作範例,你已具備在 JavaScript 中安全、正確處理時區與 UTC 的能力,無論是簡單的日期顯示,還是複雜的跨國排程,都能從容應對。祝開發順利!