如何格式化时间跨度,如 2 小时 30 分钟
通过自动本地化以用户语言显示时长
简介
当你需要展示某个过程所需的时间时,必须以用户易于理解的方式显示该时长。例如,视频显示 2 小时 30 分钟的播放时长,健身应用记录锻炼持续时间,项目管理工具展示任务完成所需时间。如果没有本地化,你可能会这样编写代码:
const hours = 2;
const minutes = 30;
const timeSpan = `${hours}h ${minutes}m`;
这样无论用户使用什么语言,都会看到 "2h 30m"。法语用户会看到 "2h 30m",但他们期望的是 "2 h 30 min"。德语用户看到的是英文缩写,而不是 "2 Std. 30 Min"。西班牙语用户也不会看到单位之间的 "y" 连接词。
JavaScript 提供 Intl.DurationFormat API,可以根据用户的语言和文化习惯格式化时间跨度。本教程将介绍如何创建时长格式化器、构建时长对象,并为任意语言环境正确显示时间跨度。
什么是时间跨度
时间跨度表示一段时间长度,而不是某个具体时间点。比如 150 分钟是一个时长,而 2025 年 3 月 15 日下午 2:30 则是一个日期和时间。
这个区别很重要,因为日期涉及日历、时区和历史规则,而时间跨度仅仅表示经过的时间,与日历无关。你无法为时长添加时区,因为时长本身独立于任何具体时刻。
处理时间跨度时请使用 Intl.DurationFormat。处理日期和时间请用 Intl.DateTimeFormat。处理诸如“2 小时前”这样的相对表达请用 Intl.RelativeTimeFormat。
创建时长格式化器
Intl.DurationFormat 构造函数接收一个 locale 和一个 options 对象。locale 决定输出语言,options 控制格式化样式和单位显示。
const formatter = new Intl.DurationFormat('en', { style: 'long' });
调用 format() 并传入一个 duration 对象,可以生成格式化字符串。duration 对象包含各时间单位的数值属性。
const duration = { hours: 2, minutes: 30 };
formatter.format(duration);
// "2 hours and 30 minutes"
该 API 会根据 locale 自动处理缩写、连接词、词序和空格。
构建 duration 对象
duration 对象是一个普通的 JavaScript 对象,包含各时间单位的属性。只需包含你想要显示的单位。
const duration1 = { hours: 2, minutes: 30 };
const duration2 = { minutes: 5, seconds: 45 };
const duration3 = { hours: 1, minutes: 15, seconds: 30 };
该 API 支持以下时间单位:years、months、weeks、days、hours、minutes、seconds、milliseconds、microseconds、nanoseconds。
无需包含所有单位。你可以省略不需要显示的单位。
const formatter = new Intl.DurationFormat('en', { style: 'long' });
formatter.format({ hours: 2, minutes: 30 });
// "2 hours and 30 minutes"
formatter.format({ minutes: 30 });
// "30 minutes"
formatter.format({ hours: 2 });
// "2 hours"
选择格式化样式
style 选项用于控制输出密度。可用四种样式:long、short、narrow 和 digital。
长样式使用完整单词,适用于正文和主要内容区域。
const duration = { hours: 2, minutes: 30 };
new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "2 hours and 30 minutes"
短样式使用常见缩写,适合空间有限但需保证可读性的场景。
new Intl.DurationFormat('en', { style: 'short' }).format(duration);
// "2 hr and 30 min"
窄样式使用最少字符,适合移动端界面或数据表等紧凑显示。
new Intl.DurationFormat('en', { style: 'narrow' }).format(duration);
// "2h 30m"
数字样式以计时器格式输出,适用于媒体播放器和倒计时显示。
new Intl.DurationFormat('en', { style: 'digital' }).format(duration);
// "2:30:00"
数字样式要求从最大单位到最小单位全部包含。如果要格式化小时和分钟,也必须包含秒。
不同语言的时间跨度格式
同样的时间跨度在每种语言中的格式都不同。API 会自动处理所有本地化。
const duration = { hours: 2, minutes: 30 };
new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "2 hours and 30 minutes"
new Intl.DurationFormat('fr', { style: 'long' }).format(duration);
// "2 heures et 30 minutes"
new Intl.DurationFormat('de', { style: 'long' }).format(duration);
// "2 Stunden und 30 Minuten"
new Intl.DurationFormat('es', { style: 'long' }).format(duration);
// "2 horas y 30 minutos"
new Intl.DurationFormat('ja', { style: 'long' }).format(duration);
// "2時間30分"
请注意,每个语言环境使用的词汇和连接词都不同。法语用 "et",德语用 "und",西班牙语用 "y",日语则没有连接词。API 已内置每个语言环境的这些规则。
短格式和窄格式也会正确本地化。
new Intl.DurationFormat('fr', { style: 'short' }).format(duration);
// "2 h et 30 min"
new Intl.DurationFormat('de', { style: 'narrow' }).format(duration);
// "2 Std. 30 Min."
按用户语言环境格式化时间跨度
不要硬编码语言环境,而应使用浏览器中用户的首选语言。navigator.language 属性会返回用户的首选语言。
const userLocale = navigator.language;
const formatter = new Intl.DurationFormat(userLocale, { style: 'short' });
const duration = { hours: 2, minutes: 30 };
formatter.format(duration);
// Output varies by user's locale
// For en-US: "2 hr and 30 min"
// For de-DE: "2 Std. und 30 Min."
// For fr-FR: "2 h et 30 min"
这样可以根据每个用户的期望显示时间跨度,无需手动选择语言环境。
将毫秒数转换为持续时间对象
时间计算通常会产生毫秒数。通过除以相应的因子,将毫秒数转换为持续时间对象。
const milliseconds = 9000000; // 2 hours 30 minutes
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: 'long' }).format(duration);
// "2 hours, 30 minutes and 0 seconds"
除非需要显示,否则应省略为零的数值。
const duration = {};
if (hours > 0) duration.hours = hours;
if (minutes > 0) duration.minutes = minutes;
if (seconds > 0) duration.seconds = seconds;
new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "2 hours and 30 minutes"
通过两个日期计算时间跨度
通过相减时间戳计算两个日期之间的持续时间,然后将结果转换为持续时间对象。
const startTime = new Date('2025-10-15T10:00:00');
const endTime = new Date('2025-10-15T12:30:00');
const diffMs = endTime - startTime;
const hours = Math.floor(diffMs / 3600000);
const minutes = Math.floor((diffMs % 3600000) / 60000);
const duration = { hours, minutes };
new Intl.DurationFormat('en', { style: 'short' }).format(duration);
// "2 hr and 30 min"
这种方法适用于任何产生毫秒数的时间计算。
格式化视频播放器的时长
视频播放器会在控件中显示时长。可使用数字样式或窄样式以便紧凑显示。
function formatVideoDuration(totalSeconds) {
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = Math.floor(totalSeconds % 60);
const duration = hours > 0
? { hours, minutes, seconds }
: { minutes, seconds };
const locale = navigator.language;
return new Intl.DurationFormat(locale, { style: 'digital' }).format(duration);
}
formatVideoDuration(9000); // "2:30:00"
formatVideoDuration(330); // "5:30"
只有在需要时才会包含小时,对于短视频显示为 "5:30",长内容则显示为 "2:30:00"。
格式化锻炼时长
健身应用会跟踪锻炼时长。会话摘要建议使用长格式,紧凑列表视图建议使用窄格式。
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-10-15T07:00:00');
const workoutEnd = new Date('2025-10-15T09:30:00');
formatWorkoutDuration(workoutStart, workoutEnd, 'en');
// "2 hours and 30 minutes"
formatWorkoutDuration(workoutStart, workoutEnd, 'es');
// "2 horas y 30 minutos"
格式化项目任务时长
项目管理工具会显示任务所需的时长。仪表盘展示建议使用短格式。
function formatTaskDuration(minutes, locale) {
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
const duration = {};
if (hours > 0) duration.hours = hours;
if (mins > 0) duration.minutes = mins;
return new Intl.DurationFormat(locale, { style: 'short' }).format(duration);
}
formatTaskDuration(150, 'en');
// "2 hr and 30 min"
formatTaskDuration(45, 'en');
// "45 min"
formatTaskDuration(150, 'de');
// "2 Std. und 30 Min."
格式化不同时间单位
时间跨度不仅限于小时和分钟。可以格式化任意支持的单位组合。
const formatter = new Intl.DurationFormat('en', { style: 'long' });
formatter.format({ days: 3, hours: 2 });
// "3 days and 2 hours"
formatter.format({ minutes: 45, seconds: 30 });
// "45 minutes and 30 seconds"
formatter.format({ hours: 1, minutes: 30, seconds: 45 });
// "1 hour, 30 minutes and 45 seconds"
API 会根据任意单位组合自动处理合适的连接词和分隔符。
仅包含秒的时间跨度格式化
当时长小于一分钟时,仅显示秒数。
const formatter = new Intl.DurationFormat('en', { style: 'short' });
formatter.format({ seconds: 45 });
// "45 sec"
formatter.format({ seconds: 5 });
// "5 sec"
对于极短的时长,可以包含毫秒。
formatter.format({ seconds: 5, milliseconds: 500 });
// "5 sec and 500 ms"
复用格式化器实例以提升性能
创建新的格式化器需要加载本地化数据并处理选项。如果你需要用相同的语言环境和样式格式化多个时间跨度,建议只创建一次格式化器并复用。
const formatter = new Intl.DurationFormat('en', { style: 'short' });
const durations = [
{ hours: 1, minutes: 30 },
{ hours: 2, minutes: 15 },
{ minutes: 45 }
];
durations.map(d => formatter.format(d));
// ["1 hr and 30 min", "2 hr and 15 min", "45 min"]
这种模式在循环或多次渲染时格式化大量时长数据时能提升性能。
浏览器支持情况
Intl.DurationFormat API 于 2025 年 3 月成为 Baseline。它可在最新版 Chrome、Edge、Firefox 和 Safari 中使用。旧版浏览器不支持该 API。
使用该 API 前请先检查兼容性。
{/* CODE_PLACEHOLDER_3752fed2fb2f5fb1488c2919607b7 */}
这样可以在使用原生 API 的同时,为旧版浏览器提供降级方案。