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 支持以下时间单位:years、months、weeks、days、hours、minutes、seconds、milliseconds、microseconds、nanoseconds。根据您的数据选择合适的单位。
选择格式样式
style 选项控制输出的密度。提供四种样式:long、short、narrow 和 digital。
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"
控制显示的单位
默认情况下,格式化器会显示您在持续时间对象中提供的所有单位。可以通过选项对象控制单位的显示。
为每个单位单独指定显示样式。选项包括 long、short、narrow、numeric 和 2-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);
每个部分都有一个 type 和 value。类型包括 hour、minute、second、literal,以及像 hourUnit、minuteUnit 这样的单位标签。
[
{ 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 用于格式化持续时间,而不是时间计算。您必须自行从日期、时间戳或其他来源计算持续时间值。格式化器仅处理显示。