Back

使用 Intl API 格式化日期和数字

使用 Intl API 格式化日期和数字

你之前使用过 Intl.DateTimeFormatIntl.NumberFormat。但如果你的认知模型还停留在几年前,你很可能错过了一些重要的功能——特别是 Intl.NumberFormat 的舍入控制,以及 Temporal 和 Intl 在 2025 年末运行时中的协同工作方式。

本文提供了 JavaScript 国际化(通过 Intl API)的最新技术概述,重点介绍了哪些方面发生了变化,以及经验丰富的开发者经常犯的错误。

核心要点

  • Intl API 将值格式化为区域感知的字符串,但不解析、计算或存储数据——保持展示层与应用逻辑的分离。
  • Intl.NumberFormat 现在支持高级舍入控制,包括 roundingModeroundingIncrementtrailingZeroDisplay
  • Temporal 类型通过自己的 toLocaleString() 方法处理格式化,而 Intl.DateTimeFormat 继续格式化 Date 对象。
  • 始终重用格式化器实例以提升性能,避免在测试中硬编码预期的输出字符串。

Intl 实际上做什么

Intl 命名空间处理值的区域感知格式化。它不解析、计算或操作数据——它根据文化习惯将值转换为人类可读的字符串。

两个关键点:

  1. Intl 负责格式化;它不存储或计算。 你的应用逻辑与展示层保持分离。
  2. 输出因运行时而异。 底层的区域数据来自浏览器和 Node.js 捆绑的 ICU 库。Chrome 和 Safari 之间,或不同 Node 版本之间,确切的字符串可能略有不同。

永远不要在测试中硬编码预期的输出字符串。请使用 formatToParts() 或结构化断言。

Intl.DateTimeFormat:超越基本选项

格式化 Date 对象

Intl.DateTimeFormat 仍然是格式化 JavaScript Date 对象的标准方式:

const formatter = new Intl.DateTimeFormat('de-DE', {
  dateStyle: 'long',
  timeStyle: 'short',
  timeZone: 'Europe/Berlin'
});

formatter.format(new Date()); // "27. Juni 2025 um 14:30"

dateStyletimeStyle 选项提供预设配置。当你需要精细控制时,使用单独的选项,如 weekdaymonthhourtimeZoneName

Temporal 类型与区域感知格式化

Temporal 正在现代 JavaScript 引擎中积极实现,但尚未在所有浏览器和环境中广泛支持。在可用的地方,Temporal.PlainDateTemporal.ZonedDateTime 和其他 Temporal 类型通过 toLocaleString() 自行格式化,而不是直接传递给 Intl.DateTimeFormat.prototype.format()

const date = Temporal.PlainDate.from('2025-06-27');
date.toLocaleString('ja-JP', { dateStyle: 'full' });
// "2025年6月27日金曜日"

Intl.DateTimeFormat 接受 Date 对象。Temporal 类型处理自己的格式化逻辑,可能在内部委托区域敏感的行为,但它们本身不由 Intl.DateTimeFormat 格式化。在设计接受日期输入的 API 时,这种区别很重要。

使用 formatRange 格式化日期范围

要显示日期范围,使用 formatRange():

const start = new Date(2025, 5, 27);
const end = new Date(2025, 6, 3);

new Intl.DateTimeFormat('en-US', { dateStyle: 'medium' })
  .formatRange(start, end);
// "Jun 27 – Jul 3, 2025"

格式化器会根据区域习惯智能地折叠冗余部分。

Intl.NumberFormat 舍入和显示控制

现代舍入选项

Intl.NumberFormat 的舍入行为在最近的规范中显著扩展,支持程度因运行时而异。除了 minimumFractionDigitsmaximumFractionDigits,你现在还有:

  • roundingMode: 控制值的舍入方式(ceilfloorhalfExpandhalfEven 等)
  • roundingIncrement: 舍入到特定增量(5、10、50 等)
  • trailingZeroDisplay: 控制是否显示尾随零(autostripIfInteger)
const formatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD',
  roundingMode: 'halfEven',
  maximumFractionDigits: 2
});

formatter.format(2.225); // "$2.22" (银行家舍入)
formatter.format(2.235); // "$2.24"

区域感知的数字格式化模式

紧凑表示法和单位格式化:

// 紧凑表示法
new Intl.NumberFormat('en-US', {
  notation: 'compact',
  compactDisplay: 'short'
}).format(1234567); // "1.2M"

// 单位格式化
new Intl.NumberFormat('de-DE', {
  style: 'unit',
  unit: 'kilometer-per-hour',
  unitDisplay: 'short'
}).format(120); // "120 km/h"

实践注意事项

重用格式化器实例

创建格式化器有开销。在格式化多个值时缓存它们:

// 推荐做法
const priceFormatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
});
prices.map(p => priceFormatter.format(p));

// 不推荐做法
prices.map(p => new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
}).format(p));

特性检测

以编程方式检查较新的选项:

try {
  new Intl.NumberFormat('en', { roundingMode: 'halfEven' });
  // 特性支持
} catch (e) {
  // 回退到默认舍入
}

区域设置 vs. 时区

这是两个独立的概念。区域设置(en-GB)决定格式化约定。时区(Europe/London)决定显示的时钟时间。你可以使用德语约定格式化日期,同时显示东京时间。

结论

用于日期格式化和区域感知数字格式化的 Intl API 已经相当成熟。规范现在包括舍入模式、显示控制和范围格式化,消除了使用外部库的大部分理由。

Temporal 类型与 Intl 并存——它们处理自己的 toLocaleString() 调用,而 Intl.DateTimeFormat 继续格式化 Date 对象。围绕这种分离构建你的认知模型,在测试时不要硬编码字符串预期,并重用格式化器实例以提升性能。

常见问题

不可以。Intl.DateTimeFormat.prototype.format() 只接受 Date 对象。Temporal 类型(如 PlainDate 和 ZonedDateTime)有自己的 toLocaleString() 方法。它们不由 Intl.DateTimeFormat 本身格式化,尽管它们在内部可能使用类似的区域数据。

Intl 依赖于每个运行时捆绑的 ICU 区域数据。Chrome、Safari、Firefox 和 Node.js 可能附带不同的 ICU 版本,输出略有差异。避免在测试中硬编码预期字符串。使用 formatToParts() 或结构化断言来可靠地验证格式化行为。

银行家舍入(halfEven)将中点值舍入到最接近的偶数数字,减少累积舍入偏差。它通常用于金融和会计环境,但确切结果仍可能受到二进制浮点表示的影响。

尝试使用启用该选项构造格式化器。如果运行时不支持它,可能会抛出 RangeError 或静默忽略该选项,具体取决于引擎。始终包含回退策略。

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay