如何按照用户的本地习惯格式化时间

使用 JavaScript 根据每位用户的地区习惯显示时间

引言

世界各地的时间显示方式各不相同。美国用户通常会看到 3:30 PM 显示为 3:30 PM,而大多数欧洲用户则期望看到 15:30。如果你将时间格式写死,就默认所有用户都遵循同一种习惯。

以不熟悉的格式显示时间会让用户感到困惑。习惯 24 小时制的用户看到 3:30 PM 时,需要在脑海中转换,才能判断是上午还是下午。这种认知负担会在应用中每一个时间点上不断累积。

JavaScript 提供了 Intl.DateTimeFormat API,可自动处理时间格式化。本教程将解释为何各地时间格式不同、该 API 的工作原理,以及如何为任意地区正确格式化时间。

为什么时间格式因地区而异

不同地区形成了不同的时间显示习惯。这些习惯反映了历史传统、教育体系和文化偏好。没有一种格式是全球通用的。

在美国、加拿大、澳大利亚和菲律宾,时间采用带有 AM 和 PM 标记的 12 小时制。下午 3:30 显示为 3:30 PM

在大多数欧洲国家、拉丁美洲和亚洲,时间采用不带 AM 或 PM 标记的 24 小时制。同一时间显示为 15:30

小时与分钟之间的分隔符也有差异。英语国家使用冒号,而有些地区则用句点或其他标点符号。

AM 和 PM 的显示方式也不同。英语使用 AMPM,西班牙语使用 a.m.p.m.,有些地区甚至会把这些标记放在时间前面而不是后面。

在显示时间时,需要符合用户对小时制和具体格式规范的预期。

使用 Intl.DateTimeFormat 格式化时间

Intl.DateTimeFormat 构造函数会创建一个应用特定区域格式规范的格式化器。要格式化时间,请将区域标识符作为第一个参数传入,并在第二个参数中指定与时间相关的选项。

const formatter = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric'
});

const date = new Date('2025-03-15T15:30:00');
console.log(formatter.format(date));
// Output: "3:30 PM"

这会创建一个用于美式英语的格式化器,显示小时和分钟。hourminute 选项指示格式化器包含这些时间组件。format() 方法会将 Date 对象转换为带有适当格式的字符串。

Date 构造函数接受类似 2025-03-15T15:30:00 的 ISO 8601 日期时间字符串。这会创建一个表示 2025 年 3 月 15 日下午 3:30 的 Date 对象。格式化器随后会将其转换为特定区域的时间字符串。

为不同区域格式化相同时间

通过更改传递给构造函数的区域标识符,可以为不同区域格式化相同的时间。

const date = new Date('2025-03-15T15:30:00');

const usFormatter = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric'
});
console.log(usFormatter.format(date));
// Output: "3:30 PM"

const gbFormatter = new Intl.DateTimeFormat('en-GB', {
  hour: 'numeric',
  minute: 'numeric'
});
console.log(gbFormatter.format(date));
// Output: "15:30"

const deFormatter = new Intl.DateTimeFormat('de-DE', {
  hour: 'numeric',
  minute: 'numeric'
});
console.log(deFormatter.format(date));
// Output: "15:30"

const frFormatter = new Intl.DateTimeFormat('fr-FR', {
  hour: 'numeric',
  minute: 'numeric'
});
console.log(frFormatter.format(date));
// Output: "15:30"

每个格式化器都会应用不同的格式规范。美国格式化器使用带有 AM/PM 的 12 小时制。英国、德国和法国格式化器均使用不带 AM/PM 的 24 小时制。

无需了解每个区域使用哪种时间格式。API 会根据区域标识符自动处理这些细节。

在时间显示中包含秒

可以添加 second 选项,以在显示小时和分钟的同时显示秒数。

const formatter = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric',
  second: 'numeric'
});

const date = new Date('2025-03-15T15:30:45');
console.log(formatter.format(date));
// Output: "3:30:45 PM"

second 选项的用法与 hourminute 相同。将其设置为 'numeric' 即可在输出中包含秒数。

使用 2-digit 控制数字补零

hourminutesecond 选项可接受两个值:'numeric''2-digit''numeric' 值会显示不带补零的数字,而 '2-digit' 始终以两位数字显示,前面补零。

const date = new Date('2025-03-15T09:05:03');

const numericFormatter = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric',
  second: 'numeric'
});
console.log(numericFormatter.format(date));
// Output: "9:05:03 AM"

const twoDigitFormatter = new Intl.DateTimeFormat('en-US', {
  hour: '2-digit',
  minute: '2-digit',
  second: '2-digit'
});
console.log(twoDigitFormatter.format(date));
// Output: "09:05:03 AM"

数字格式化器会将 9:05:03 AM 的小时部分显示为单个数字。两位数格式化器会将 09:05:03 AM 的小时部分前面补零。分钟和秒始终显示为两位数,因为这些值通常无论设置如何都会补零。

强制 12 小时或 24 小时制

默认情况下,API 会使用当前区域偏好的时间格式。你可以通过 hour12 选项进行覆盖。

const date = new Date('2025-03-15T15:30:00');

const hour12Formatter = new Intl.DateTimeFormat('en-GB', {
  hour: 'numeric',
  minute: 'numeric',
  hour12: true
});
console.log(hour12Formatter.format(date));
// Output: "3:30 pm"

const hour24Formatter = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric',
  hour12: false
});
console.log(hour24Formatter.format(date));
// Output: "15:30"

设置 hour12: true 可强制使用 12 小时制,即使该区域通常采用 24 小时制。设置 hour12: false 可强制使用 24 小时制,即使该区域通常采用 12 小时制。

区域设置仍然决定其他格式细节,如标点和间距。英国格式化器配合 hour12: true 时,会以小写 pm 显示 3:30 pm,而美国格式化器则会以大写 PM 显示 3:30 PM

为用户的区域设置格式化时间

无需硬编码特定区域设置,你可以使用浏览器中用户的首选语言。navigator.language 属性会返回用户的首选语言。

const userLocale = navigator.language;
const formatter = new Intl.DateTimeFormat(userLocale, {
  hour: 'numeric',
  minute: 'numeric'
});

const date = new Date('2025-03-15T15:30:00');
console.log(formatter.format(date));
// Output varies by user's locale
// For en-US: "3:30 PM"
// For en-GB: "15:30"
// For de-DE: "15:30"
// For fr-FR: "15:30"

这种方式可以根据每位用户的期望显示时间,无需他们手动选择区域设置。浏览器会提供语言偏好,Intl API 会应用相应的格式化规范。

您还可以传递整个首选语言数组,以启用回退机制。

const formatter = new Intl.DateTimeFormat(navigator.languages, {
  hour: 'numeric',
  minute: 'numeric'
});

const date = new Date('2025-03-15T15:30:00');
console.log(formatter.format(date));

API 会使用该数组中第一个支持的语言环境。当用户的首选语言不可用时,这样可以更好地实现回退处理。

创建要格式化的时间

您可以通过多种方式创建包含时间信息的 Date 对象。最可靠的方法是使用 ISO 8601 日期时间字符串。

const time1 = new Date('2025-03-15T09:00:00');
const time2 = new Date('2025-03-15T15:30:00');
const time3 = new Date('2025-03-15T23:45:30');

const formatter = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric'
});

console.log(formatter.format(time1));
// Output: "9:00 AM"

console.log(formatter.format(time2));
// Output: "3:30 PM"

console.log(formatter.format(time3));
// Output: "11:45 PM"

ISO 8601 日期时间字符串采用 YYYY-MM-DDTHH:MM:SS 格式。T 用于分隔日期和时间。该格式没有歧义,并且在所有语言环境和时区下都能保持一致。

从时间戳格式化时间

您还可以通过 Unix 时间戳创建 Date 对象。Unix 时间戳表示自 1970 年 1 月 1 日 UTC 起的毫秒数。

const timestamp = 1710515400000; // March 15, 2025 at 3:30 PM
const date = new Date(timestamp);

const formatter = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric'
});
console.log(formatter.format(date));
// Output: "3:30 PM"

当您从 API、数据库或其他以数字表示时间的系统接收时间戳时,这种方法非常适用。

您也可以将时间戳直接传递给 format() 方法,无需先创建 Date 对象。

const formatter = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric'
});

const timestamp = 1710515400000;
console.log(formatter.format(timestamp));
// Output: "3:30 PM"

API 同时支持 Date 对象和时间戳。请根据您的代码需求选择合适的方法。

格式化当前时间

要格式化当前时间,可以创建一个不带参数的 Date 对象。这样会生成一个表示当前时刻的 Date 对象。

const formatter = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric',
  second: 'numeric'
});

const now = new Date();
console.log(formatter.format(now));
// Output: "3:45:12 PM" (or current time when run)

您也可以直接传递 Date.now(),它会以数字形式返回当前时间戳。

const formatter = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric',
  second: 'numeric'
});

console.log(formatter.format(Date.now()));
// Output: "3:45:12 PM" (or current time when run)

这两种方式得到的结果完全相同。

复用格式化器以提升性能

创建新的 Intl.DateTimeFormat 实例时需要加载语言环境数据并处理选项。如果需要用相同的语言环境和设置多次格式化时间,建议只创建一次格式化器并重复使用。

const formatter = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric'
});

const times = [
  new Date('2025-03-15T09:00:00'),
  new Date('2025-03-15T12:30:00'),
  new Date('2025-03-15T18:45:00')
];

times.forEach(time => {
  console.log(formatter.format(time));
});
// Output:
// "9:00 AM"
// "12:30 PM"
// "6:45 PM"

这种方法比每次都创建一个新的格式化器更高效。当需要格式化包含数百或数千个时间值的数组时,性能差异会非常明显。

在模板中格式化时间

你可以在任何向用户展示时间的地方使用 Intl.DateTimeFormat。这包括将格式化后的时间插入到 HTML 模板、在表格中显示时间,或在用户界面中展示时间戳。

const formatter = new Intl.DateTimeFormat(navigator.language, {
  hour: 'numeric',
  minute: 'numeric'
});

const eventStart = new Date('2025-03-15T14:00:00');
const eventEnd = new Date('2025-03-15T16:30:00');

document.getElementById('start-time').textContent = formatter.format(eventStart);
document.getElementById('end-time').textContent = formatter.format(eventEnd);

格式化后的字符串与其他字符串值一样,可以插入到文本内容、属性或任何向用户展示信息的场景中。