本文 AI 產出,尚未審核

計算時間差(Date & Time)

簡介

在日常的 Web 開發或 Node.js 應用中,時間差的計算常常是必不可少的功能:從倒數計時、活動截止時間、到兩筆資料的更新間隔,都需要精確地算出兩個時間點之間的長度。
JavaScript 內建的 Date 物件提供了完整的時間資訊,但直接操作時容易踩到時區、毫秒與人類可讀格式之間的落差。因此,掌握 如何正確取得時間戳、轉換單位以及避免常見陷阱,是每位前端或後端開發者的基本功。

本篇文章將從概念說明出發,透過 5 個實用範例 示範如何在 JavaScript 中計算時間差,並提供最佳實踐與真實應用情境,幫助讀者從「會寫」升級到「寫得好、寫得安全」。


核心概念

1. Date 物件與時間戳(timestamp)

  • new Date() 會產生一個代表 本機時間Date 實例。
  • date.getTime()date.valueOf() 會回傳 自 1970-01-01 00:00:00 UTC 起的毫秒數(Unix timestamp in milliseconds)。
  • 毫秒 是計算時間差最直接的單位,之後再根據需求換算成秒、分、時、天等。
const now = new Date();               // 2025-11-20T08:30:00.000Z(示意)
const timestamp = now.getTime();      // 1732045800000

2. 取得兩個時間點的毫秒差

只要把兩個 Date 物件的時間戳相減,即可得到 毫秒差。負值代表前者晚於後者。

const start = new Date('2025-11-20T08:00:00');
const end   = new Date('2025-11-20T09:15:30');
const diffMs = end - start;           // 4530000 ms

3. 單位換算

將毫秒差轉成更直觀的「天、時、分、秒」時,只需要除以對應的常數:

單位 常數(毫秒)
1 000
60 000
3 600 000
86 400 000

4. Intl.RelativeTimeFormat(國際化相對時間)

ES2020 起提供的 API,可直接產生 語系化的相對時間字串(如「3 天前」或「in 2 hours」),省去自行拼接的麻煩。

const rtf = new Intl.RelativeTimeFormat('zh-TW', { numeric: 'auto' });
rtf.format(-3, 'day');   // "3 天前"
rtf.format(2, 'hour');   // "2 小時後"

程式碼範例

下面提供 5 個從基礎到進階 的範例,每段都有詳細註解,方便初學者快速上手。

範例 1️⃣:最簡單的毫秒差

// 取得兩個時間點的毫秒差
const start = new Date('2025-11-01T12:00:00');
const end   = new Date('2025-11-01T12:05:30');

const diffMs = end - start;               // 330000 ms
console.log(`差距:${diffMs} 毫秒`);

重點:直接使用 - 運算子即可得到毫秒差,Date 物件在算術運算時會自動轉成時間戳。


範例 2️⃣:換算成「天、時、分、秒」的可讀格式

function formatDiff(ms) {
  const sec  = Math.floor(ms / 1000) % 60;
  const min  = Math.floor(ms / (1000 * 60)) % 60;
  const hour = Math.floor(ms / (1000 * 60 * 60)) % 24;
  const day  = Math.floor(ms / (1000 * 60 * 60 * 24));

  return `${day} 天 ${hour} 小時 ${min} 分 ${sec} 秒`;
}

const start = new Date('2025-10-31T08:00:00');
const end   = new Date('2025-11-20T10:45:20');
const diffMs = end - start;

console.log('時間差:', formatDiff(diffMs));
// 輸出:時間差: 20 天 2 小時 45 分 20 秒

技巧:使用 % 取餘數可以避免前面的單位「溢出」到下一層。


範例 3️⃣:產生「XX 天前 / XX 天後」的相對時間字串

function relativeTime(from, to = new Date()) {
  const diff = to - from;                     // 正值表示 from 在過去
  const rtf  = new Intl.RelativeTimeFormat('zh-TW', { numeric: 'auto' });

  const seconds = Math.round(diff / 1000);
  if (Math.abs(seconds) < 60) return rtf.format(-seconds, 'second');

  const minutes = Math.round(seconds / 60);
  if (Math.abs(minutes) < 60) return rtf.format(-minutes, 'minute');

  const hours = Math.round(minutes / 60);
  if (Math.abs(hours) < 24) return rtf.format(-hours, 'hour');

  const days = Math.round(hours / 24);
  return rtf.format(-days, 'day');
}

// 範例:顯示「3 天前」或「5 小時後」
console.log(relativeTime(new Date('2025-11-17T12:00:00'))); // 3 天前
console.log(relativeTime(new Date('2025-11-20T15:00:00'))); // 5 小時後

說明:利用 Intl.RelativeTimeFormat 可以一次解決 語系化單位自動切換 的需求。


範例 4️⃣:跨時區的時間差(使用 UTC)

// 假設使用者在台北(+08:00),伺服器在 UTC
const taipei = new Date('2025-11-20T08:00:00+08:00'); // 2025-11-20T00:00:00Z
const utc    = new Date('2025-11-20T02:30:00Z');      // 同一天的 UTC 時間

// 直接相減會得到正確的毫秒差,因為 Date 內部皆以 UTC 儲存
const diffMs = utc - taipei;   // 9,000,000 ms = 2.5 小時
console.log(`跨時區差距:${diffMs / 3600000} 小時`);

關鍵永遠以 UTC 為基礎計算,避免因本機時區或夏令時間(DST)造成誤差。


範例 5️⃣:倒數計時器(利用 setInterval

function countdown(target) {
  const timer = setInterval(() => {
    const now = new Date();
    const diff = target - now;

    if (diff <= 0) {
      clearInterval(timer);
      console.log('時間到!');
      return;
    }

    const { day, hour, minute, second } = (() => {
      const sec   = Math.floor(diff / 1000) % 60;
      const min   = Math.floor(diff / (1000 * 60)) % 60;
      const hour  = Math.floor(diff / (1000 * 60 * 60)) % 24;
      const day   = Math.floor(diff / (1000 * 60 * 60 * 24));
      return { day, hour, minute: min, second: sec };
    })();

    console.log(`倒數:${day}d ${hour}h ${minute}m ${second}s`);
  }, 1000);
}

// 設定 2025-12-01 00:00:00 為倒數目標
countdown(new Date('2025-12-01T00:00:00'));

實務提醒:在瀏覽器環境中,setInterval 的最小間隔受限於 最小 4ms(在背景分頁可能更長),若需高精度計時,請改用 requestAnimationFrame 或 Web Workers。


常見陷阱與最佳實踐

陷阱 說明 解決方式
直接使用 Date.parse() 解析不標準字串 Date.parse('2025/11/20') 在不同瀏覽器會有不同結果。 使用 ISO 8601 標準格式 YYYY-MM-DDTHH:mm:ss 或透過 new Date(year, month-1, day, ...)
忽略時區差異 本機時間與伺服器時間不一致時,計算結果會偏差。 統一使用 UTCDate.UTC().getTime())或在 API 中傳遞 ISO 8601 帶時區的字串。
毫秒除以 1000 後直接四捨五入 會產生「1 秒 999 毫秒」被算成 2 秒的錯誤。 取整 (Math.floor) 再進行單位換算,或使用 Math.round 依需求決定。
使用 setTimeout/setInterval 產生長時間倒數 瀏覽器在非活躍分頁會降頻,導致倒數不準。 使用 Web WorkersServer‑Side 計算,前端只負責渲染結果。
忘記處理負值(未來時間) end - start 為負時,直接顯示負數會不易閱讀。 在顯示前先 取絕對值,並根據正負決定文字(如「後」或「前」)。

最佳實踐

  1. 統一時間基準:全專案盡量使用 UTC,前端顯示時再根據使用者時區轉換。
  2. 封裝工具函式:將時間差、換算、格式化等功能抽成獨立模組,避免重複程式碼。
  3. 避免硬編碼:把「毫秒/秒/分/時」等常數定義為常量(const MS_IN_SEC = 1000),提升可讀性與維護性。
  4. 使用現代 APIIntl.RelativeTimeFormatTemporal(Stage 3)等新 API 能減少自行計算的錯誤。
  5. 單元測試:特別是跨時區或夏令時間的邏輯,務必寫測試確保正確。

實際應用場景

場景 需求 可能的實作
活動倒數 顯示活動開始前的天、時、分、秒 範例 5 的倒數計時器,配合 requestAnimationFrame 提升流暢度
訂單處理時間 計算訂單從下單到出貨的耗時,顯示在後台報表 使用 UTC 時間戳,換算成「X 天 Y 小時」後儲存於資料庫
聊天訊息時間 顯示「3 分鐘前」或「2 小時前」的相對時間 Intl.RelativeTimeFormat 搭配自訂的 relativeTime 函式
API 請求超時 判斷最後一次成功回應與現在的差距,決定是否重試 取得 Date.now(),與上一次成功的時間戳比較
日曆排程衝突檢測 判斷兩個會議是否重疊 先把開始與結束時間都轉成毫秒,檢查 startA < endB && startB < endA

總結

  • 時間差的核心是把 兩個 Date 物件的時間戳相減,得到毫秒值。
  • 透過 除以 1000、60、3600、86400 等常數,可輕鬆換算為秒、分、時、天。
  • 為了 跨時區、語系化易讀性,建議使用 UTC 作為計算基礎,並善用 Intl.RelativeTimeFormat 或未來的 Temporal API。
  • 常見的坑包括 字串解析不一致、時區忽略、負值處理,只要遵守「統一基準、封裝工具、寫測試」的最佳實踐,就能寫出 可靠且易維護 的時間差程式。

掌握以上概念與範例後,你就能在 倒數計時、報表分析、即時聊天 等多種情境中,準確且優雅地處理時間差,為使用者提供更好的體驗。祝開發順利 🎉!