如何格式化时间跨度,例如 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 处理日期和时间。使用 Intl.RelativeTimeFormat 处理诸如 "2 小时前" 之类的相对表达。

创建一个时长格式化器

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

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

调用 format() 并传入一个时长对象即可生成格式化字符串。时长对象包含时间单位的数值属性。

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

该 API 会根据语言环境自动处理缩写、连接词、词序和间距。

构建持续时间对象

持续时间对象是一个普通的 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);
// 输出因用户的语言环境而异
// 对于 en-US: "2 hr and 30 min"
// 对于 de-DE: "2 Std. und 30 Min."
// 对于 fr-FR: "2 h et 30 min"

这会根据每个用户的期望显示时间跨度,而无需手动选择语言环境。

将毫秒转换为持续时间对象

时间计算通常会产生毫秒值。通过除以适当的因子,将毫秒转换为持续时间对象。

const milliseconds = 9000000; // 2 小时 30 分钟

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('zh', { style: 'long' }).format(duration);
// "2 小时 30 分钟 0 秒"

除非特别需要显示零值,否则应省略零值。

const duration = {};
if (hours > 0) duration.hours = hours;
if (minutes > 0) duration.minutes = minutes;
if (seconds > 0) duration.seconds = seconds;

new Intl.DurationFormat('zh', { style: 'long' }).format(duration);
// "2 小时 30 分钟"

计算两个日期之间的时间跨度

通过减去时间戳来计算两个日期之间的持续时间,然后将结果转换为持续时间对象。

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('zh', { style: 'short' }).format(duration);
// "2 小时 30 分钟"

这种方法适用于任何产生毫秒值的时间计算。

格式化视频播放器的持续时间

视频播放器在控件中显示持续时间。使用数字或紧凑样式以实现紧凑显示。

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 秒"

formatter.format({ seconds: 5 });
// "5 秒"

对于非常短的持续时间,可以包含毫秒。

formatter.format({ seconds: 5, milliseconds: 500 });
// "5 秒和 500 毫秒"

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

创建一个新的格式化器需要加载区域设置数据并处理选项。当您使用相同的区域设置和样式格式化多个时间跨度时,请创建一次格式化器并重复使用它。

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 小时 30 分钟", "2 小时 15 分钟", "45 分钟"]

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

浏览器支持

Intl.DurationFormat API 于 2025 年 3 月成为基线功能。它适用于最新版本的 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}小时 ${duration.minutes}分钟`;
}

这为旧版浏览器提供了回退,同时在可用时使用原生 API。