本文 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.DateTimeFormatTemporal(提案階段)自動處理 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 的字串,或使用可靠的日期套件。

最佳實踐清單

  1. 所有儲存的時間(資料庫、Log、API)都使用 UTC
  2. 前端顯示 時,使用 Intl.DateTimeFormat 指定使用者的時區。
  3. 計算時間差 時,直接使用毫秒值 (dateA - dateB) 再轉換為天、時、分。
  4. 避免自行硬編時區偏移,除非確定不會受 DST 影響。
  5. 若專案需要頻繁操作時區,考慮 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-fnsformatDistanceToNow 並傳入 addSuffix: true, locale: zhTW,自動根據使用者瀏覽器時區呈現相對時間。

總結

時區與 UTC 是 跨地域應用的基石,掌握 Date 內部的 UTC 時間戳記、善用 Intl.DateTimeFormat 以及遵守「資料庫儲存 UTC、前端顯示本地」的原則,可大幅降低時間相關 Bug 的風險。

  • 記住Date 只保存 UTC 毫秒,所有本地化的顯示都需要透過相應的 API 或套件轉換。
  • 避免:自行硬編時區偏移、忽略夏令時間、使用不標準的字串格式。
  • 實踐:在專案中統一 UTC 儲存、使用 IntlTemporal 處理時區,並在文件與團隊規範中明確寫下時間處理的流程。

透過上述概念與實作範例,你已具備在 JavaScript 中安全、正確處理時區與 UTC 的能力,無論是簡單的日期顯示,還是複雜的跨國排程,都能從容應對。祝開發順利!