如何格式化时间跨度,如 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 的构造函数接收一个语言环境和一个选项对象。语言环境决定输出语言,选项则控制显示的样式和单位。

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 会根据不同语言环境,自动处理缩写、连接词、词序和空格问题。

构建 duration 对象

duration 对象是一个包含各种时间单位属性的普通 JavaScript 对象。只需包含你想展示的单位。

const duration1 = { hours: 2, minutes: 30 };
const duration2 = { minutes: 5, seconds: 45 };
const duration3 = { hours: 1, minutes: 15, seconds: 30 };

API 支持这些时间单位:yearsmonthsweeksdayshoursminutessecondsmillisecondsmicrosecondsnanoseconds

你可以不包含所有单位。想隐藏哪个单位,就不用写进对象里。

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 选项用于控制输出的紧凑程度。共有四种样式可选:longshortnarrowdigital

长格式使用完整单词,适合正文或主要内容区域。

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 前请先检查是否支持。

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`;
}

这样可以在旧版浏览器使用回退方案,在支持的环境下用原生 API。