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"

同一个日期,根据本地约定以三种不同的方式格式化。

了解区域设置

区域设置是一个标识语言和区域偏好的字符串。其格式遵循 BCP 47 标准:语言代码后可选地跟随区域代码。

常见的区域设置模式:

"en"      // 英语(通用)
"en-US"   // 英语(美国)
"en-GB"   // 英语(英国)
"es"      // 西班牙语(通用)
"es-MX"   // 西班牙语(墨西哥)
"es-ES"   // 西班牙语(西班牙)
"zh-CN"   // 中文(中国,简体)
"zh-TW"   // 中文(台湾,繁体)

如果省略区域设置参数,浏览器将使用用户的默认区域设置。

const formatter = new Intl.DateTimeFormat();
formatter.format(date);
// 输出取决于用户的浏览器区域设置

您还可以提供一个区域设置数组。浏览器将使用第一个支持的区域设置。

const formatter = new Intl.DateTimeFormat(["es-MX", "es", "en"]);
// 如果可用,使用西班牙语(墨西哥),否则回退到通用西班牙语,然后是英语

格式选项概述

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

第一种方法使用样式快捷方式。这些快捷方式提供快速、常规的格式化。

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 时间

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);
// "2024年1月15日 – 20日"

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

当范围跨越不同月份时,会显示两个日期的月份。

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

formatter.formatRange(start, end);
// "2024年1月15日 – 2月20日"

当范围跨越不同年份时,会显示所有信息。

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

formatter.formatRange(start, end);
// "2024年1月15日 – 2025年2月20日"

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);
// "2024年1月15日,下午2:30 – 4:45"

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

访问格式化的部分

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: "1月" },
  { type: "literal", value: " " },
  { type: "day", value: "15" },
  { type: "literal", value: "," },
  { type: "year", value: "2024" },
  { type: "literal", value: " " },
  { type: "hour", value: "2" },
  { type: "literal", value: ":" },
  { type: "minute", value: "30" },
  { type: "literal", value: " " },
  { type: "dayPeriod", value: "下午" }
]

您可以过滤和操作这些部分以构建自定义格式。

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

const dateString = dateParts.map(part => part.value).join("/");
// "1月/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);

每个部分对象都包含一个 source 属性,指示它来自开始日期还是结束日期。

[
  { type: "month", value: "1月", 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" }
]

最佳实践

在使用相同选项格式化多个日期时,重用格式化器实例。创建格式化器涉及到区域设置协商和选项解析,这会有一定的性能开销。

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

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

尽可能使用用户浏览器的区域设置,通过省略区域设置参数来实现。这可以尊重用户的偏好。

const formatter = new Intl.DateTimeFormat();
// 自动使用浏览器的区域设置

在针对特定地区时提供备用区域设置。如果首选区域设置不可用,浏览器会使用下一个选项。

const formatter = new Intl.DateTimeFormat(["fr-CA", "fr", "en"]);
// 优先使用法语(加拿大),然后是法语,最后是英语

对于常见的日期和时间显示,使用样式快捷方式。它们会自动适应区域设置的约定,并且需要更少的配置。

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

当需要精确控制输出格式时,使用组件选项。

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() 格式化日期范围,而不是分别格式化每个日期并将它们连接起来。该方法可以智能地省略冗余信息。

// 不够清晰
const startFormatted = formatter.format(start);
const endFormatted = formatter.format(end);
const range = `${startFormatted} to ${endFormatted}`;

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

检查浏览器对高级功能的兼容性,例如 dayPeriodfractionalSecondDigits 和某些 timeZoneName 值。所有现代浏览器都支持核心功能,但较新的选项可能需要为旧浏览器提供备用方案。