← 全部文章

无需库的 JavaScript 时区正确日期格式化

JavaScript 内置的 Intl.DateTimeFormat 无需 moment.js 或 date-fns 即可处理 IANA 时区格式化。学习显式 timeZone 选项、对夏令时安全的显示、formatToParts、挂钟转换的限制,以及何时 Temporal 或时区库仍然有用。

为什么 Date 没有时区属性

JavaScript 的 Date 始终是一个 UTC 毫秒计数。对象内部不存储时区。当你不带选项调用 .toString() 或 .toLocaleString() 时,JavaScript 使用来自操作系统的运行时本地时区。即使底层时间戳完全相同,同一段代码在纽约的服务器和东京的笔记本上也会产生不同输出。

用 Intl.DateTimeFormat 格式化

Intl.DateTimeFormat 是用于按区域设置和时区进行格式化的内置 API。它支持 IANA 时区标识符,自动处理夏令时切换,并在现代浏览器和 Node.js 中可用。关键是显式传入 timeZone 选项,而不是依赖运行时默认值。

  • new Intl.DateTimeFormat('en-US', { timeZone: 'America/New_York', dateStyle: 'full', timeStyle: 'long' }).format(date)
  • date.toLocaleString('en-GB', { timeZone: 'Europe/London', hour12: false })
  • date.toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' })
  • new Intl.DateTimeFormat('en-US', { timeZone: 'UTC', hour12: false }).format(date)

用 formatToParts 提取各个部分

使用 formatToParts() 把日期的各个组成部分取为 {type, value} 对象,以构建自定义日期字符串。这比拆分本地化的日期字符串更好,因为标点、顺序和文字系统因区域设置而异。

  • const parts = new Intl.DateTimeFormat('en-US', { timeZone: 'America/Chicago', year: 'numeric', month: '2-digit', day: '2-digit' }).formatToParts(date)
  • parts.find(p => p.type === 'year').value → '2023'
  • parts.find(p => p.type === 'month').value → '11'
  • Object.fromEntries(parts.map(p => [p.type, p.value])) → { year, month, day, hour, minute, second }

把挂钟时间转换为 UTC(困难的方向)

用经典 Date API 从给定时区的挂钟时间走向 UTC 更困难。把某个瞬间格式化为 America/New_York 很容易;但构造 America/New_York 中 2026-03-08 02:30 所表示的瞬间则不然,因为该本地时间在夏令时切换期间可能被跳过或重复。

  • Temporal 在 2026 年达到 Stage 4,但浏览器原生支持仍不普及
  • 若要在今天所有浏览器上用于生产,Temporal polyfill 或 date-fns-tz 的 toDate() 仍是实用选择
  • 避免手动做 UTC 偏移算术 —— 夏令时切换在不同年份发生于不同的本地时刻
  • 本站的 zonedToEpochMs() 使用一次迭代的偏移校正 —— 见 src/timeUtils.ts

选择正确的时区标识符

使用 America/New_York、Europe/London、Asia/Tokyo 或 UTC 这类 IANA 时区名称。面向用户的时间应避免使用 UTC-5 这类固定偏移,因为偏移会随夏令时变化。America/New_York 在 1 月可能是 UTC-5,在 7 月是 UTC-4;IANA 名称让平台为所选日期应用正确的历史规则。

  • 好:America/Los_Angeles —— 包含历史和未来的夏令时规则
  • 好:Europe/Berlin —— 自动处理中欧夏令时
  • 好:Asia/Shanghai —— 无夏令时的稳定 UTC+8 显示
  • 日志、API 存储、数据库时间戳和跨区域比较使用 UTC
  • 产品界面的最终显示使用用户的 IANA 时区

时区格式化常见问题

Two daylight-saving transitions a year break the assumption that "wall-clock time + timezone = a unique moment". Both are encoded in the IANA timezone database (still often called the Olson database) — the authoritative source of timezone rules used by every modern OS, browser, and language runtime.

  • DST gap: 02:00 to 02:59 local time does not exist on the spring-forward day
  • DST overlap: 01:00 to 01:59 local time occurs twice on the fall-back day
  • Ambiguous local time: in the overlap window you must pick "earlier" or "later" explicitly
  • Python uses a fold flag; JS Temporal uses a disambiguation option; java.time defaults to the earlier offset
  • Olson database / IANA tzdata / zoneinfo are three names for the same dataset, updated several times a year
  • Each IANA name (America/Los_Angeles, Europe/Berlin) maps to a list of (transition, offset, abbreviation) tuples
  • Stale tzdata on a device produces silently-wrong local times after any DST or zone change
  • Server best practice: do all date arithmetic in UTC, convert to local only at display

FAQ

Can JavaScript format a date in another timezone without a library?
Yes. Use Intl.DateTimeFormat or toLocaleString with a timeZone option, such as America/New_York or Asia/Tokyo.
Does Intl.DateTimeFormat handle daylight saving time?
Yes. When you use an IANA timezone identifier, the runtime applies the correct offset for that timezone and date.
Should I store timezone offsets or timezone names?
Store UTC for event instants. If you need the user's local context, store an IANA timezone name such as America/New_York, not only a numeric offset.
How do I get the user's timezone in JavaScript?
Use Intl.DateTimeFormat().resolvedOptions().timeZone, which returns an IANA name like America/New_York. Store that name, not a numeric offset, when you need to reconstruct the user's local time later.
What is the DST gap (spring-forward)?
The DST gap is the wall-clock hour that is skipped when daylight saving time begins. In most US zones, when the clock would tick from 02:00 to 02:59 it instead jumps to 03:00. Local times in that window do not exist on the transition day. Unix timestamps themselves never skip; only the wall-clock interpretation does.
What is the DST overlap (fall-back)?
The DST overlap is the wall-clock hour that occurs twice when daylight saving time ends. In most US zones, the clock ticks from 01:59 back to 01:00, so every minute from 01:00 to 01:59 happens twice. A wall-clock time in that window maps to two Unix timestamps; converters and schedulers must specify "earlier" or "later".