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"
不能将样式快捷选项与组件选项混用。每个格式化器请选择一种方法。
样式快捷选项
dateStyle 和 timeStyle 选项提供四种预设格式级别。
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_York、Europe/London 或 Asia/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甲辰年腊月初五"
同一日期可转换为不同的日历系统。可用的日历包括 gregory、japanese、islamic、islamic-umalqura、islamic-tbla、islamic-civil、islamic-rgsa、chinese、hebrew、indian、persian 等。
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);
数组中的每个对象都包含 type 和 value 属性。
[
{ 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);
在使用 dayPeriod、fractionalSecondDigits 以及某些 timeZoneName 值等高级功能时,请检查浏览器兼容性。所有现代浏览器都支持核心功能,但新选项在旧浏览器上可能需要降级处理。