Intl.DurationFormat API

使用 JavaScript 自动本地化格式化时间持续时间

介绍

当您显示某件事情所需的时间时,您需要为用户格式化持续时间值。例如,视频播放器显示运行时间,航班预订显示旅行时间,健身应用显示锻炼时长。如果没有本地化,您可能会编写如下代码:

const hours = 1;
const minutes = 46;
const seconds = 40;
const duration = `${hours}h ${minutes}m ${seconds}s`;

这会为所有用户生成 "1h 46m 40s",而不考虑语言。法语用户会看到 "1h 46m 40s",但他们期望的是 "1 h 46 min 40 s"。德语用户看到的仍然是相同的英文缩写。西班牙语用户则没有单位之间的 "y" 连接词。

Intl.DurationFormat API 解决了这个问题。它根据用户的语言和文化习惯格式化时间持续时间,而无需外部库或手动字符串拼接。

const duration = { hours: 1, minutes: 46, seconds: 40 };
new Intl.DurationFormat('fr-FR', { style: 'short' }).format(duration);
// "1 h, 46 min et 40 s"

格式化器会自动处理任何语言的缩写、连接词、词序和间距。

什么是持续时间

持续时间表示一段时间,而不是一个时间点。日期和时间标记某件事情发生的时间点。持续时间则衡量某件事情所需的时间。

这种区别在格式化时很重要。日期涉及日历、时区和历史规则。持续时间更简单:它以标准单位衡量经过的时间,而不涉及日历上下文。

Intl.DurationFormat API 处理持续时间。Intl.DateTimeFormat API 处理日期和时间。请为每项任务使用正确的工具。

创建一个持续时间格式化器

构造函数接受一个语言环境和一个选项对象。语言环境决定输出语言。选项控制格式化样式和单位显示。

const formatter = new Intl.DurationFormat('en', { style: 'long' });

使用 format() 方法传入一个持续时间对象。该对象包含时间单位的数值属性。只需包含您想要显示的单位。

const duration = { hours: 2, minutes: 30 };
formatter.format(duration);
// "2 hours and 30 minutes"

该 API 支持以下时间单位:yearsmonthsweeksdayshoursminutessecondsmillisecondsmicrosecondsnanoseconds。根据您的数据选择合适的单位。

选择格式样式

style 选项控制输出的密度。提供四种样式:longshortnarrowdigital

Long 样式使用完整的单词。适用于文章和无障碍内容。

const duration = { hours: 1, minutes: 46, seconds: 40 };
new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "1 hour, 46 minutes and 40 seconds"

Short 样式使用常见的缩写。适用于空间有限但需要保持可读性的场景。

new Intl.DurationFormat('en', { style: 'short' }).format(duration);
// "1 hr, 46 min and 40 sec"

Narrow 样式使用最少的字符。适用于移动界面等紧凑型显示。

new Intl.DurationFormat('en', { style: 'narrow' }).format(duration);
// "1h 46m 40s"

Digital 样式生成类似计时器的输出,使用冒号分隔。适用于媒体播放器和倒计时显示。

new Intl.DurationFormat('en', { style: 'digital' }).format(duration);
// "1:46:40"

跨语言的本地化

相同的时长在不同语言中格式化的方式不同。API 会自动处理所有本地化。

const duration = { hours: 1, minutes: 46, seconds: 40 };

new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "1 hour, 46 minutes and 40 seconds"

new Intl.DurationFormat('fr', { style: 'long' }).format(duration);
// "1 heure, 46 minutes et 40 secondes"

new Intl.DurationFormat('de', { style: 'long' }).format(duration);
// "1 Stunde, 46 Minuten und 40 Sekunden"

new Intl.DurationFormat('es', { style: 'long' }).format(duration);
// "1 hora, 46 minutos y 40 segundos"

new Intl.DurationFormat('ja', { style: 'long' }).format(duration);
// "1時間46分40秒"

注意每种语言使用了不同的单词、缩写和连词。例如,法语使用 "et",德语使用 "und",西班牙语使用 "y",日语则不使用连词。API 知道这些规则。

Short 和 Narrow 样式也会正确地本地化。

new Intl.DurationFormat('fr', { style: 'short' }).format(duration);
// "1 h, 46 min et 40 s"

new Intl.DurationFormat('de', { style: 'narrow' }).format(duration;
// "1 Std. 46 Min. 40 Sek."

从时间计算中构建持续时间对象

持续时间对象是带有时间单位属性的普通 JavaScript 对象。可以从任何时间计算中创建它们。

通过除以适当的因子,将毫秒转换为持续时间单位。

const milliseconds = 6400000; // 1 小时,46 分钟,40 秒
const hours = Math.floor(milliseconds / 3600000);
const minutes = Math.floor((milliseconds % 3600000) / 60000);
const seconds = Math.floor((milliseconds % 60000) / 1000);

const duration = { hours, minutes, seconds };
new Intl.DurationFormat('en', { style: 'short' }).format(duration);
// "1 hr, 46 min and 40 sec"

通过减去时间戳,从两个日期计算持续时间。

const start = new Date('2025-01-01T10:00:00');
const end = new Date('2025-01-01T11:46:40');
const diffMs = end - start;

const hours = Math.floor(diffMs / 3600000);
const minutes = Math.floor((diffMs % 3600000) / 60000);
const seconds = Math.floor((diffMs % 60000) / 1000);

const duration = { hours, minutes, seconds };

仅包含您需要的单位。除非您希望显示零值,否则可以省略它们。

const duration = { minutes: 5, seconds: 30 };
new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "5 minutes and 30 seconds"

控制显示的单位

默认情况下,格式化器会显示您在持续时间对象中提供的所有单位。可以通过选项对象控制单位的显示。

为每个单位单独指定显示样式。选项包括 longshortnarrownumeric2-digit

const duration = { hours: 1, minutes: 5, seconds: 3 };

new Intl.DurationFormat('en', {
  hours: 'long',
  minutes: 'numeric',
  seconds: '2-digit'
}).format(duration);
// "1 hour, 5:03"

numeric 样式仅显示数字,不带标签。2-digit 样式会添加零填充。将这些样式用于混合文本和数字的紧凑显示。

通过从持续时间对象和选项中省略单位来隐藏它们。

const duration = { hours: 2, minutes: 30 };
new Intl.DurationFormat('en', { style: 'short' }).format(duration);
// "2 hr and 30 min"

数字样式要求从最大到最小的所有单位都存在或明确配置。

const duration = { minutes: 5, seconds: 30 };
new Intl.DurationFormat('en', { style: 'digital' }).format(duration);
// "5:30"

格式化视频播放器的时长

视频播放器在控件中显示时长。使用窄格式或数字样式以实现紧凑的显示。

function formatVideoDuration(seconds) {
  const hours = Math.floor(seconds / 3600);
  const minutes = Math.floor((seconds % 3600) / 60);
  const secs = Math.floor(seconds % 60);

  const duration = hours > 0
    ? { hours, minutes, seconds: secs }
    : { minutes, seconds: secs };

  const locale = navigator.language;
  return new Intl.DurationFormat(locale, { style: 'digital' }).format(duration);
}

formatVideoDuration(6400); // "1:46:40"
formatVideoDuration(330);  // "5:30"

此方法通过有条件地包含小时数来处理短视频和长视频。

格式化航班和旅行时间

旅行预订界面显示行程时长。使用短格式以便在有限空间内提高可读性。

function formatFlightDuration(departureDate, arrivalDate, locale) {
  const diffMs = arrivalDate - departureDate;
  const hours = Math.floor(diffMs / 3600000);
  const minutes = Math.floor((diffMs % 3600000) / 60000);

  const duration = { hours, minutes };
  return new Intl.DurationFormat(locale, { style: 'short' }).format(duration);
}

const departure = new Date('2025-06-15T10:30:00');
const arrival = new Date('2025-06-15T18:45:00');

formatFlightDuration(departure, arrival, 'en');
// "8 hr and 15 min"

formatFlightDuration(departure, arrival, 'fr');
// "8 h et 15 min"

格式化锻炼和活动时长

健身应用程序会跟踪锻炼时长。使用长格式用于会话摘要,使用窄格式用于列表视图。

function formatWorkoutDuration(startTime, endTime, locale) {
  const diffMs = endTime - startTime;
  const hours = Math.floor(diffMs / 3600000);
  const minutes = Math.floor((diffMs % 3600000) / 60000);

  const duration = hours > 0
    ? { hours, minutes }
    : { minutes };

  return new Intl.DurationFormat(locale, { style: 'long' }).format(duration);
}

const workoutStart = new Date('2025-06-15T07:00:00');
const workoutEnd = new Date('2025-06-15T08:15:00');

formatWorkoutDuration(workoutStart, workoutEnd, 'en');
// "1 hour and 15 minutes"

获取格式化部分以自定义显示

formatToParts() 方法返回一个对象数组,每个对象代表格式化输出的一个部分。使用此方法可以对单独的部分进行样式化或构建自定义布局。

const duration = { hours: 1, minutes: 46, seconds: 40 };
const parts = new Intl.DurationFormat('en', { style: 'long' }).formatToParts(duration);

每个部分都有一个 typevalue。类型包括 hourminutesecondliteral,以及像 hourUnitminuteUnit 这样的单位标签。

[
  { type: "integer", value: "1" },
  { type: "literal", value: " " },
  { type: "unit", value: "hour" },
  { type: "literal", value: ", " },
  { type: "integer", value: "46" },
  { type: "literal", value: " " },
  { type: "unit", value: "minutes" },
  { type: "literal", value: " and " },
  { type: "integer", value: "40" },
  { type: "literal", value: " " },
  { type: "unit", value: "seconds" }
]

通过映射部分来应用自定义样式。

function StyledDuration({ duration }) {
  const parts = new Intl.DurationFormat('en', { style: 'long' }).formatToParts(duration);

  return parts.map((part, i) => {
    if (part.type === 'integer') {
      return <strong key={i}>{part.value}</strong>;
    }
    return <span key={i}>{part.value}</span>;
  });
}

重用格式化实例以提高性能

为每个持续时间创建一个新的格式化器会增加开销。创建一次格式化器并重复使用它。

const formatter = new Intl.DurationFormat('en', { style: 'short' });

const durations = [
  { hours: 1, minutes: 30 },
  { hours: 2, minutes: 15 },
  { hours: 0, minutes: 45 }
];

durations.map(d => formatter.format(d));
// ["1 hr and 30 min", "2 hr and 15 min", "45 min"]

这种模式在循环或渲染中格式化多个持续时间时可以提高性能。

从手动字符串构建迁移

使用 API 替代手动字符串拼接。这可以减少代码量并添加本地化支持。

之前:

function formatDuration(hours, minutes) {
  if (hours > 0 && minutes > 0) {
    return `${hours}h ${minutes}m`;
  } else if (hours > 0) {
    return `${hours}h`;
  } else {
    return `${minutes}m`;
  }
}

之后:

function formatDuration(hours, minutes, locale) {
  const duration = {};
  if (hours > 0) duration.hours = hours;
  if (minutes > 0) duration.minutes = minutes;

  return new Intl.DurationFormat(locale, { style: 'narrow' }).format(duration);
}

API 版本会自动处理所有条件逻辑,并支持多种语言。

从库迁移

像 Moment.js 和 date-fns 这样的库提供了持续时间格式化功能,但会增加包的体积。原生 API 消除了这种依赖。

替换 Moment.js 的持续时间格式化:

// 之前:Moment.js
const duration = moment.duration(6400, 'seconds');
const formatted = duration.hours() + 'h ' + duration.minutes() + 'm';

// 之后:Intl.DurationFormat
const duration = {
  hours: Math.floor(6400 / 3600),
  minutes: Math.floor((6400 % 3600) / 60)
};
new Intl.DurationFormat('en', { style: 'narrow' }).format(duration);

替换 date-fns 的持续时间格式化:

// 之前:date-fns
import { formatDuration, intervalToDuration } from 'date-fns';
const duration = intervalToDuration({ start: 0, end: 6400000 });
const formatted = formatDuration(duration);

// 之后:Intl.DurationFormat
const ms = 6400000;
const duration = {
  hours: Math.floor(ms / 3600000),
  minutes: Math.floor((ms % 3600000) / 60000),
  seconds: Math.floor((ms % 60000) / 1000)
};
new Intl.DurationFormat('en', { style: 'long' }).format(duration);

原生 API 提供了相同的功能且无需依赖。

浏览器支持和 polyfills

Intl.DurationFormat API 于 2025 年 3 月成为基线功能。它可以在最新版本的 Chrome、Edge、Firefox 和 Safari 中使用。

在使用前检查支持情况:

if (typeof Intl.DurationFormat !== 'undefined') {
  const formatter = new Intl.DurationFormat('en', { style: 'short' });
  return formatter.format(duration);
} else {
  // 旧版浏览器的回退方案
  return `${duration.hours}h ${duration.minutes}m`;
}

为了更广泛的支持,可以使用 polyfill。FormatJS 项目提供了 @formatjs/intl-durationformat

npm install @formatjs/intl-durationformat

导入并在需要时进行 polyfill:

if (!Intl.DurationFormat) {
  await import('@formatjs/intl-durationformat/polyfill');
}

该 polyfill 压缩后约为 15KB。根据需要有条件地加载它,以避免对现代浏览器的额外开销。

何时使用 DurationFormat

在显示经过时间、剩余时间或持续时间测量值时,请使用 Intl.DurationFormat。这包括视频播放器、计时器、倒计时、跟踪应用、旅行预订和会话持续时间显示。

不要将其用于日期、时间或时间戳。对于这些,请使用 Intl.DateTimeFormat。不要将其用于诸如“2 小时前”之类的相对时间表达。对于这些,请使用 Intl.RelativeTimeFormat

该 API 用于格式化持续时间,而不是时间计算。您必须自行从日期、时间戳或其他来源计算持续时间值。格式化器仅处理显示。