Intl.DateTimeFormat API

无需外部库,为任意本地化环境格式化日期和时间

简介

世界各地的日期显示方式各不相同。2024 年 1 月 15 日在美国显示为 1/15/2024,在英国为 15/1/2024,在日本则为 2024/1/15。时间格式也有差异。美国人习惯使用带有 AM 和 PM 的 12 小时制,而大多数国家则采用 24 小时制。

为不同本地化环境手动编写日期格式化逻辑既复杂又容易出错。你需要处理日、月顺序、分隔符、时间格式、时区转换,以及如非公历等特殊情况。

Intl.DateTimeFormat API 解决了这个问题。它在所有现代浏览器中都提供了内置的、支持本地化的日期和时间格式化功能,无需任何外部库。

基本用法

最简单的日期格式化方式是创建一个 Intl.DateTimeFormat 实例,并调用其 format() 方法。

const date = new Date(2024, 0, 15, 14, 30);

const formatter = new Intl.DateTimeFormat("en-US");
formatter.format(date);
// "1/15/2024"

切换本地化环境以查看不同的格式化规范。

const ukFormatter = new Intl.DateTimeFormat("en-GB");
ukFormatter.format(date);
// "15/01/2024"

const japanFormatter = new Intl.DateTimeFormat("ja-JP");
japanFormatter.format(date);
// "2024/1/15"

同一个日期,根据本地化规范有三种不同的格式化结果。

了解本地化环境

本地化环境(locale)是一个用于标识语言和地区偏好的字符串。其格式遵循 BCP 47 标准:语言代码,可选跟随地区代码。

常见的本地化环境模式:

"en"      // English (generic)
"en-US"   // English (United States)
"en-GB"   // English (United Kingdom)
"es"      // Spanish (generic)
"es-MX"   // Spanish (Mexico)
"es-ES"   // Spanish (Spain)
"zh-CN"   // Chinese (China, Simplified)
"zh-TW"   // Chinese (Taiwan, Traditional)

如果省略 locale 参数,浏览器会使用用户的默认本地化环境。

const formatter = new Intl.DateTimeFormat();
formatter.format(date);
// Output varies based on user's browser locale

你也可以传递一个本地化环境数组。浏览器会使用第一个支持的本地化环境。

const formatter = new Intl.DateTimeFormat(["es-MX", "es", "en"]);
// Uses Spanish (Mexico) if available, falls back to generic Spanish, then English

格式化选项概览

Intl.DateTimeFormat 构造函数接受一个 options 对象,用于控制格式化输出的内容。格式化有两种方式。

第一种方法使用样式快捷选项。这些选项可实现快速、常规的格式化。

const formatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "full",
  timeStyle: "short"
});

formatter.format(date);
// "Monday, January 15, 2024 at 2:30 PM"

第二种方法使用组件选项。这样可以对日期的每个部分进行细致控制。

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric",
  hour: "numeric",
  minute: "numeric"
});

formatter.format(date);
// "January 15, 2024 at 2:30 PM"

不能将样式快捷选项与组件选项混用。每个格式化器请选择一种方法。

样式快捷选项

dateStyletimeStyle 选项提供四种预设格式级别。

dateStyle 选项用于格式化日期部分。

const date = new Date(2024, 0, 15);

const full = new Intl.DateTimeFormat("en-US", { dateStyle: "full" });
full.format(date);
// "Monday, January 15, 2024"

const long = new Intl.DateTimeFormat("en-US", { dateStyle: "long" });
long.format(date);
// "January 15, 2024"

const medium = new Intl.DateTimeFormat("en-US", { dateStyle: "medium" });
medium.format(date);
// "Jan 15, 2024"

const short = new Intl.DateTimeFormat("en-US", { dateStyle: "short" });
short.format(date);
// "1/15/24"

timeStyle 选项用于格式化时间部分。

const date = new Date(2024, 0, 15, 14, 30, 45);

const full = new Intl.DateTimeFormat("en-US", { timeStyle: "full" });
full.format(date);
// "2:30:45 PM Eastern Standard Time"

const long = new Intl.DateTimeFormat("en-US", { timeStyle: "long" });
long.format(date);
// "2:30:45 PM EST"

const medium = new Intl.DateTimeFormat("en-US", { timeStyle: "medium" });
medium.format(date);
// "2:30:45 PM"

const short = new Intl.DateTimeFormat("en-US", { timeStyle: "short" });
short.format(date);
// "2:30 PM"

结合这两个选项可同时格式化日期和时间。

const formatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "medium",
  timeStyle: "short"
});

formatter.format(date);
// "Jan 15, 2024, 2:30 PM"

样式快捷选项会自动适应本地化习惯。

const usFormatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "short",
  timeStyle: "short"
});
usFormatter.format(date);
// "1/15/24, 2:30 PM"

const deFormatter = new Intl.DateTimeFormat("de-DE", {
  dateStyle: "short",
  timeStyle: "short"
});
deFormatter.format(date);
// "15.1.24, 14:30"

德语格式使用点号作为分隔符,日月顺序与美式相反,并采用 24 小时制。美式格式使用斜杠,月份在前,并采用带 AM/PM 的 12 小时制。相同选项,不同语言环境下输出不同。

组件选项

组件选项可精确控制显示内容及其方式。每个选项指定日期或时间的某一部分。

year 选项用于显示年份。

const formatter = new Intl.DateTimeFormat("en-US", { year: "numeric" });
formatter.format(date);
// "2024"

const twoDigit = new Intl.DateTimeFormat("en-US", { year: "2-digit" });
twoDigit.format(date);
// "24"

month 选项可用多种格式显示月份。

const numeric = new Intl.DateTimeFormat("en-US", { month: "numeric" });
numeric.format(date);
// "1"

const twoDigit = new Intl.DateTimeFormat("en-US", { month: "2-digit" });
twoDigit.format(date);
// "01"

const long = new Intl.DateTimeFormat("en-US", { month: "long" });
long.format(date);
// "January"

const short = new Intl.DateTimeFormat("en-US", { month: "short" });
short.format(date);
// "Jan"

const narrow = new Intl.DateTimeFormat("en-US", { month: "narrow" });
narrow.format(date);
// "J"

day 选项用于显示日期中的日。

const formatter = new Intl.DateTimeFormat("en-US", { day: "numeric" });
formatter.format(date);
// "15"

const twoDigit = new Intl.DateTimeFormat("en-US", { day: "2-digit" });
twoDigit.format(date);
// "15"

weekday 选项用于显示星期几。

const long = new Intl.DateTimeFormat("en-US", { weekday: "long" });
long.format(date);
// "Monday"

const short = new Intl.DateTimeFormat("en-US", { weekday: "short" });
short.format(date);
// "Mon"

const narrow = new Intl.DateTimeFormat("en-US", { weekday: "narrow" });
narrow.format(date);
// "M"

组合多个组件选项以构建自定义日期格式。

const formatter = new Intl.DateTimeFormat("en-US", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric"
});

formatter.format(date);
// "Monday, January 15, 2024"

浏览器会根据本地习惯自动处理空格和标点符号。

时间格式化

时间组件选项用于控制小时、分钟和秒的显示。

const date = new Date(2024, 0, 15, 14, 30, 45);

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

formatter.format(date);
// "2:30:45 PM"

hour12 选项用于控制 12 小时制和 24 小时制的显示。

const hour12 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  hour12: true
});
hour12.format(date);
// "2:30 PM"

const hour24 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  hour12: false
});
hour24.format(date);
// "14:30"

hourCycle 选项可更精细地控制小时显示,共有四种选项。

// h11: 0-11
const h11 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  hourCycle: "h11"
});
h11.format(new Date(2024, 0, 15, 0, 30));
// "0:30 AM"

// h12: 1-12
const h12 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  hourCycle: "h12"
});
h12.format(new Date(2024, 0, 15, 0, 30));
// "12:30 AM"

// h23: 0-23
const h23 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  hourCycle: "h23"
});
h23.format(new Date(2024, 0, 15, 0, 30));
// "0:30"

// h24: 1-24
const h24 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  hourCycle: "h24"
});
h24.format(new Date(2024, 0, 15, 0, 30));
// "24:30"

在午夜时分,这种差异尤为重要。h11 循环使用 0,而 h12 使用 12。同样,h23 使用 0,而 h24 使用 24。

dayPeriod 选项为 12 小时制添加描述性时间段标记。

const morning = new Date(2024, 0, 15, 10, 30);
const afternoon = new Date(2024, 0, 15, 14, 30);
const night = new Date(2024, 0, 15, 22, 30);

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

formatter.format(morning);
// "10:30 in the morning"

formatter.format(afternoon);
// "2:30 in the afternoon"

formatter.format(night);
// "10:30 at night"

dayPeriod 选项仅适用于 12 小时制时间格式。

fractionalSecondDigits 选项用于显示亚秒级精度。

const date = new Date(2024, 0, 15, 14, 30, 45, 123);

const oneDigit = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  second: "numeric",
  fractionalSecondDigits: 1
});
oneDigit.format(date);
// "2:30:45.1 PM"

const threeDigits = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  second: "numeric",
  fractionalSecondDigits: 3
});
threeDigits.format(date);
// "2:30:45.123 PM"

你可以指定一位、两位或三位小数。

时区处理

timeZone 选项可将日期转换为指定时区。

const date = new Date("2024-01-15T14:30:00Z"); // UTC time

const newYork = new Intl.DateTimeFormat("en-US", {
  timeZone: "America/New_York",
  dateStyle: "full",
  timeStyle: "long"
});
newYork.format(date);
// "Monday, January 15, 2024 at 9:30:00 AM EST"

const tokyo = new Intl.DateTimeFormat("ja-JP", {
  timeZone: "Asia/Tokyo",
  dateStyle: "full",
  timeStyle: "long"
});
tokyo.format(date);
// "2024年1月15日月曜日 23:30:00 日本標準時"

const london = new Intl.DateTimeFormat("en-GB", {
  timeZone: "Europe/London",
  dateStyle: "full",
  timeStyle: "long"
});
london.format(date);
// "Monday, 15 January 2024 at 14:30:00 GMT"

同一时刻在不同的时区显示结果不同。请使用 IANA 时区标识符,如 America/New_YorkEurope/LondonAsia/Tokyo

timeZoneName 选项用于显示时区名称。

const formatter = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  timeZone: "America/New_York",
  timeZoneName: "short"
});
formatter.format(date);
// "9:30 AM EST"

const long = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  timeZone: "America/New_York",
  timeZoneName: "long"
});
long.format(date);
// "9:30 AM Eastern Standard Time"

const shortOffset = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  timeZone: "America/New_York",
  timeZoneName: "shortOffset"
});
shortOffset.format(date);
// "9:30 AM GMT-5"

const longOffset = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  timeZone: "America/New_York",
  timeZoneName: "longOffset"
});
longOffset.format(date);
// "9:30 AM GMT-05:00"

不同的 timeZoneName 值会提供不同的详细级别。

日历系统与数字系统

calendar 选项支持非公历(日历)系统。

const date = new Date(2024, 0, 15);

const gregorian = new Intl.DateTimeFormat("en-US", {
  calendar: "gregory",
  year: "numeric",
  month: "long",
  day: "numeric"
});
gregorian.format(date);
// "January 15, 2024"

const japanese = new Intl.DateTimeFormat("ja-JP", {
  calendar: "japanese",
  year: "numeric",
  month: "long",
  day: "numeric"
});
japanese.format(date);
// "令和6年1月15日"

const islamic = new Intl.DateTimeFormat("ar-SA", {
  calendar: "islamic",
  year: "numeric",
  month: "long",
  day: "numeric"
});
islamic.format(date);
// "٦ رجب ١٤٤٥"

const chinese = new Intl.DateTimeFormat("zh-CN", {
  calendar: "chinese",
  year: "numeric",
  month: "long",
  day: "numeric"
});
chinese.format(date);
// "2023甲辰年腊月初五"

同一日期可转换为不同的日历系统。可用的日历包括 gregoryjapaneseislamicislamic-umalquraislamic-tblaislamic-civilislamic-rgsachinesehebrewindianpersian 等。

numberingSystem 选项可用不同数字书写系统显示数字。

const date = new Date(2024, 0, 15);

const western = new Intl.DateTimeFormat("en-US", {
  numberingSystem: "latn",
  year: "numeric",
  month: "numeric",
  day: "numeric"
});
western.format(date);
// "1/15/2024"

const arabic = new Intl.DateTimeFormat("en-US", {
  numberingSystem: "arab",
  year: "numeric",
  month: "numeric",
  day: "numeric"
});
arabic.format(date);
// "١‏/١٥‏/٢٠٢٤"

const devanagari = new Intl.DateTimeFormat("en-US", {
  numberingSystem: "deva",
  year: "numeric",
  month: "numeric",
  day: "numeric"
});
devanagari.format(date);
// "१/१५/२०२४"

const bengali = new Intl.DateTimeFormat("en-US", {
  numberingSystem: "beng",
  year: "numeric",
  month: "numeric",
  day: "numeric"
});
bengali.format(date);
// "১/১৫/২০২৪"

可用的数字系统包括 latn(西方数字)、arab(阿拉伯-印度数字)、arabext(扩展阿拉伯-印度数字)、beng(孟加拉数字)、deva(天城数字)等。

日期范围格式化

formatRange() 方法可智能省略冗余信息,格式化日期范围。

const start = new Date(2024, 0, 15);
const end = new Date(2024, 0, 20);

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

formatter.formatRange(start, end);
// "January 15 – 20, 2024"

格式化器会省略重复信息。两个日期都在 2024 年 1 月,因此输出只包含一次月份和年份。

如果范围跨越不同月份,则会显示两个日期。

const start = new Date(2024, 0, 15);
const end = new Date(2024, 1, 20);

formatter.formatRange(start, end);
// "January 15 – February 20, 2024"

如果范围跨越不同年份,则会显示所有信息。

const start = new Date(2024, 0, 15);
const end = new Date(2025, 1, 20);

formatter.formatRange(start, end);
// "January 15, 2024 – February 20, 2025"

formatRange() 方法同样适用于时间范围。

const start = new Date(2024, 0, 15, 14, 30);
const end = new Date(2024, 0, 15, 16, 45);

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

formatter.formatRange(start, end);
// "January 15, 2:30 – 4:45 PM"

由于两个时间都在同一天,日期只显示一次。

获取格式化后的各部分

formatToParts() 方法返回一个对象数组,每个对象代表格式化日期的一个部分。这便于自定义格式化逻辑。

const date = new Date(2024, 0, 15, 14, 30);

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric",
  hour: "numeric",
  minute: "numeric"
});

const parts = formatter.formatToParts(date);

数组中的每个对象都包含 typevalue 属性。

[
  { type: "month", value: "January" },
  { type: "literal", value: " " },
  { type: "day", value: "15" },
  { type: "literal", value: ", " },
  { type: "year", value: "2024" },
  { type: "literal", value: " at " },
  { type: "hour", value: "2" },
  { type: "literal", value: ":" },
  { type: "minute", value: "30" },
  { type: "literal", value: " " },
  { type: "dayPeriod", value: "PM" }
]

你可以筛选和处理这些部分,以构建自定义格式。

const dateParts = parts.filter(part =>
  ["month", "day", "year"].includes(part.type)
);

const dateString = dateParts.map(part => part.value).join("/");
// "January/15/2024"

formatRangeToParts() 方法为日期区间提供了相同的功能。

const start = new Date(2024, 0, 15);
const end = new Date(2024, 0, 20);

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const parts = formatter.formatRangeToParts(start, end);

每个 part 对象都包含一个 source 属性,用于指示该对象来自起始日期还是结束日期。

[
  { type: "month", value: "January", source: "startRange" },
  { type: "literal", value: " ", source: "startRange" },
  { type: "day", value: "15", source: "startRange" },
  { type: "literal", value: " – ", source: "shared" },
  { type: "day", value: "20", source: "endRange" },
  { type: "literal", value: ", ", source: "shared" },
  { type: "year", value: "2024", source: "shared" }
]

最佳实践

在使用相同选项格式化多个日期时,应复用 formatter 实例。创建 formatter 涉及本地化协商和选项解析,会带来一定的性能开销。

// Less efficient
dates.forEach(date => {
  const formatted = new Intl.DateTimeFormat("en-US").format(date);
  console.log(formatted);
});

// More efficient
const formatter = new Intl.DateTimeFormat("en-US");
dates.forEach(date => {
  const formatted = formatter.format(date);
  console.log(formatted);
});

尽量省略 locale 参数,使用用户浏览器的本地设置。这可以尊重用户的偏好。

const formatter = new Intl.DateTimeFormat();
// Uses browser locale automatically

在面向特定地区时,提供备用 locale。如果首选 locale 不可用,浏览器会使用下一个选项。

const formatter = new Intl.DateTimeFormat(["fr-CA", "fr", "en"]);
// Prefers French (Canada), falls back to French, then English

对于常见的日期和时间显示,建议使用 style 快捷方式。它们会自动适应本地习惯,且配置更简单。

const formatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "medium",
  timeStyle: "short"
});

如需精确控制输出格式,请使用 component 选项。

const formatter = new Intl.DateTimeFormat("en-US", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric"
});

为远程用户或计划事件显示时间时,务必指定时区。否则,日期会以用户本地时区格式化,可能与您的预期不符。

const formatter = new Intl.DateTimeFormat("en-US", {
  timeZone: "America/New_York",
  dateStyle: "full",
  timeStyle: "long"
});

对于日期区间,建议使用 formatRange(),而不是分别格式化每个日期后拼接。该方法能智能省略冗余信息。

// Less clear
const startFormatted = formatter.format(start);
const endFormatted = formatter.format(end);
const range = `${startFormatted} to ${endFormatted}`;

// Better
const range = formatter.formatRange(start, end);

在使用 dayPeriodfractionalSecondDigits 以及某些 timeZoneName 值等高级功能时,请检查浏览器兼容性。所有现代浏览器都支持核心功能,但新选项在旧浏览器上可能需要降级处理。