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 用于处理日期和时间。请根据需求选择合适的工具。
创建时长格式化器
构造函数接收一个 locale 和一个 options 对象。locale 决定输出的语言,options 控制格式化样式和单位显示方式。
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。
长格式使用完整单词。适用于正文和无障碍场景。
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('en', { style: 'short' }).format(duration);
// "1 hr, 46 min and 40 sec"
窄格式使用最少字符。适用于移动端等紧凑显示。
new Intl.DurationFormat('en', { style: 'narrow' }).format(duration);
// "1h 46m 40s"
数字格式以计时器样式输出,带有冒号。适用于媒体播放器和倒计时显示。
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 已内置这些规则。
短格式和窄格式同样会正确本地化。
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 hour, 46 minutes, 40 seconds
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"
控制显示哪些单位
默认情况下,格式化器会显示 duration 对象中提供的所有单位。可以通过 options 对象来控制单位的显示。
可以为每个单位分别指定显示样式。可选项包括 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 样式会补零。适用于需要紧凑显示文本和数字混合内容的场景。
如需隐藏某些单位,只需在 duration 对象和 options 中都省略它们。
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"
格式化视频播放器时长
视频播放器会在控件中显示时长。建议使用 narrow 或 digital 样式以便紧凑显示。
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"
该方法可根据需要有条件地包含小时数,适用于长短视频。
格式化航班和旅行时长
旅行预订界面会显示行程时长。建议使用 short 样式,便于在有限空间内阅读。
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"
格式化锻炼和活动时长
健身应用会跟踪锻炼时长。会话摘要建议用 long 样式,列表视图建议用 narrow 样式。
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>;
});
}
复用 formatter 实例以提升性能
为每个 duration 创建新的 formatter 会增加开销。建议只创建一次 formatter 并复用。
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"]
在循环或渲染多个 duration 时,这种模式可以提升格式化性能。
从手动字符串拼接迁移
用 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 等库可以格式化 duration,但会增加 bundle 体积。原生 API 可消除这些依赖。
替换 Moment.js 的 duration 格式化:
// Before: Moment.js
const duration = moment.duration(6400, 'seconds');
const formatted = duration.hours() + 'h ' + duration.minutes() + 'm';
// After: 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 的 duration 格式化:
// Before: date-fns
import { formatDuration, intervalToDuration } from 'date-fns';
const duration = intervalToDuration({ start: 0, end: 6400000 });
const formatted = formatDuration(duration);
// After: 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 提供相同功能且无需依赖。
浏览器支持与 polyfill
Intl.DurationFormat API 于 2025 年 3 月成为 Baseline。现已支持最新版本的 Chrome、Edge、Firefox 和 Safari。
使用前请检查支持情况:
if (typeof Intl.DurationFormat !== 'undefined') {
const formatter = new Intl.DurationFormat('en', { style: 'short' });
return formatter.format(duration);
} else {
// Fallback for older browsers
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 仅用于格式化时长,不进行时间计算。您需要自行根据日期、时间戳或其他来源计算时长数值,格式化器只负责展示。