← JavaScript Learning Path

JAVASCRIPT V2 · ADVANCED · 129

Date / Intl / Time:時間不是「一個漂亮字串」

時間 bug 常不是語法錯,而是 domain model 錯:把 instant、local wall-clock time、timezone、duration、display format 混成同一件事。這一課先建立足以避免多數前端時間錯誤的安全模型。

Learning outcomes

1. Why now:同一個時間點可以有很多顯示方式

2026-10-06T14:30:00Z

Taipei:
2026-10-06 22:30

New York:
different local clock time

這些可以代表同一個 instant。顯示字串不同,不代表事件發生在不同瞬間。

2. Mental model:先分五個概念

Instant
→ timeline 上的一個點

Timestamp
→ instant 的機器表示

Timezone
→ instant ↔ local clock rules

Locale
→ 顯示語言/格式習慣

Duration
→ 兩個時間點之間的經過量

如果資料模型先分清楚,API、排序、顯示與報表就不容易互相污染。

3. Worked Example A:Date.now 與 Date instance

const nowMs =
  Date.now();

const now =
  new Date(nowMs);

console.log(
  now.toISOString()
);

Date.now() 回毫秒 timestamp;Date object 封裝相同時間點並提供 parsing/formatting/getters。

4. ISO 8601 與 Z

const createdAt =
  new Date(
    "2026-10-06T14:30:00Z"
  );

createdAt.toISOString();
// same instant in UTC form

Z 表示 UTC。API contract 若交換時間點,使用明確 offset/UTC 格式比模糊字串安全。

5. Worked Example B:Intl.DateTimeFormat 做 Presentation

const formatter =
  new Intl.DateTimeFormat(
    "zh-TW",
    {
      dateStyle: "medium",
      timeStyle: "short",
      timeZone: "Asia/Taipei"
    }
  );

formatter.format(
  createdAt
);

不要在 storage layer 儲存「10/6 晚上 10:30」這種 locale display string,再期待它能可靠排序與交換。

6. Execution trace:同一 instant,不同 timezone display

instant:
2026-10-06T14:30:00Z

format timeZone=Asia/Taipei
→ local display A

format timeZone=UTC
→ local display B

instant did not change

Timezone formatting 改的是「如何投影到當地時鐘」,不是把事件本身移動到另一個 instant。

7. Duration:用差值,不要拿 Date 當 stopwatch UI model

const start =
  performance.now();

// work

const elapsedMs =
  performance.now()
  - start;

如果你測量短期 elapsed duration,monotonic-style timing API 比 wall clock 更適合。Wall clock 可能被系統時間調整影響。

8. Worked Example C:Intl.NumberFormat 也是 Presentation Boundary

const currency =
  new Intl.NumberFormat(
    "zh-TW",
    {
      style: "currency",
      currency: "TWD"
    }
  );

currency.format(1234.5);

和 DateTimeFormat 一樣:內部保存數值,UI 才根據 locale 格式化。

9. Common mistakes

A. 用 display string 排序

不同 locale 格式的字串 lexicographic order 不等於 chronological order。

B. 模糊 parsing

new Date("10/11/12")

這種輸入的語意不清,API contract 應避免。

C. 把 duration 當 local clock time

經過 90 分鐘不是「某地區 01:30」。

D. 假設所有一天都是固定 24 小時

涉及 daylight-saving transitions 的 timezone,local calendar arithmetic 可能不是單純毫秒加減。

10. Debug evidence:先印 canonical instant,再看 display

console.log({
  raw,
  iso: date.toISOString(),
  timestamp: date.getTime(),
  resolved:
    formatter
      .resolvedOptions()
});

遇到「時間差 8 小時」時,不要立刻手動 +8。先確認輸入是否含 offset、內部 instant 是什麼、formatter timeZone 是什麼。

11. Guided exercise:一個 Instant,兩種 Display

給定 ISO UTC timestamp,分別用 Asia/Taipei 與 UTC 顯示;證明 getTime() 沒有因 formatting 改變。

const instant =
  new Date(
    "2026-01-02T03:04:05Z"
  );

12. Independent exercise:Trip duration formatter

輸入 startMs/endMs,回傳純 duration object:

{
  totalSeconds,
  minutes,
  seconds
}

不得用 DateTimeFormat 假裝 duration 是某個 clock time。

13. Challenge:API Contract Review

{
  "meetingTime":
    "2026-10-06 22:30"
}

指出這個 contract 缺哪些資訊。如果這是「某個絕對會議瞬間」,你會如何改?如果這是「每週二當地晚上 22:30」的 recurring wall-clock rule,又需要哪些不同資訊?

Project checkpoint:Grade Tracker Audit Timestamp

function stampUpdate(
  student
) {
  return {
    ...student,
    updatedAt:
      new Date()
        .toISOString()
  };
}

資料層保存 canonical timestamp;view 層才依使用者 locale/timezone 顯示。

14. Vocabulary / Summary

15. Further reading

Knowledge check · Mastery

  1. 區分 instant / timezone / locale / duration。
  2. 解釋為何 display string 不該當 canonical storage。
  3. 完成 dual-timezone formatting 與 duration exercise。
  4. 完成頁面下方 Executable JS timezone tests。