如何在 JavaScript 中获取格式化日期的各个部分
使用 formatToParts() 单独访问本地化格式日期的每个组成部分
简介
format() 方法会返回一个完整的格式化字符串,例如 "2025年1月15日" 或 "15/01/2025"。这种方式适合简单展示,但无法对各个部分分别设置样式。你无法让月份名称加粗、年份变色,或为特定组件应用自定义标记。
JavaScript 提供了 formatToParts() 方法来解决这个问题。它不会返回单一字符串,而是返回一个对象数组,每个对象代表格式化日期的一个部分。每个部分都有一个类型,比如 month、day 或 year,以及一个值,比如 January、15 或 2025。你可以处理这些部分,实现自定义样式、构建复杂布局,或将格式化日期集成到富交互界面中。
为什么格式化字符串难以自定义
当你拿到像 "2025年1月15日" 这样的格式化字符串时,很难准确判断月份结束和日期开始的位置。不同的本地化环境会采用不同的组件顺序,有些还会用不同的分隔符。要可靠地解析这些字符串,需要复杂的逻辑,实际上是在重复 Intl API 已经实现的格式化规则。
假设你有一个日历应用,需要将月份名称加粗显示。如果用 format(),你需要:
- 检测哪些字符代表月份名称
- 处理各组件之间的空格和标点
- 兼容不同本地化环境下的月份格式
- 仔细解析字符串,避免破坏日期
这种方法不够健壮且容易出错。只要本地化格式规则发生变化,你的解析逻辑就会被破坏。
formatToParts() 方法通过分别提供各个组件,彻底解决了这个问题。你会收到结构化数据,能够准确知道每个部分的含义,无论使用哪种本地化格式。
使用 formatToParts 获取日期组件
formatToParts() 方法的工作方式与 format() 完全相同,唯一的区别在于返回值。你可以用相同的选项创建格式化器,然后调用 formatToParts(),而不是 format()。
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
console.log(parts);
这会输出一个对象数组:
[
{ type: "month", value: "January" },
{ type: "literal", value: " " },
{ type: "day", value: "15" },
{ type: "literal", value: ", " },
{ type: "year", value: "2025" }
]
每个对象都包含一个 type 属性,用于标识该部分的类型,以及一个 value 属性,包含实际的字符串。各部分的顺序与格式化输出时一致。
你可以通过将所有值拼接在一起来验证这一点:
const formatted = parts.map(part => part.value).join("");
console.log(formatted);
// Output: "January 15, 2025"
拼接后的各部分会生成与直接调用 format() 完全相同的输出。
了解各部分类型
type 属性用于标识每个组件。不同的格式化选项会生成不同的部分类型。
对于基础日期格式化:
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
console.log(parts);
// [
// { type: "month", value: "January" },
// { type: "literal", value: " " },
// { type: "day", value: "15" },
// { type: "literal", value: ", " },
// { type: "year", value: "2025" }
// ]
month 类型表示月份名称或数字。day 类型表示日期。year 类型表示年份。literal 类型表示格式化器插入的空格、标点或其他文本。
对于包含星期的日期:
const formatter = new Intl.DateTimeFormat("en-US", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric"
});
const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
console.log(parts);
// [
// { type: "weekday", value: "Wednesday" },
// { type: "literal", value: ", " },
// { type: "month", value: "January" },
// { type: "literal", value: " " },
// { type: "day", value: "15" },
// { type: "literal", value: ", " },
// { type: "year", value: "2025" }
// ]
weekday 类型表示星期几。
对于包含时间的日期:
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
second: "numeric"
});
const date = new Date(2025, 0, 15, 14, 30, 45);
const parts = formatter.formatToParts(date);
console.log(parts);
// [
// { type: "month", value: "January" },
// { type: "literal", value: " " },
// { type: "day", value: "15" },
// { type: "literal", value: ", " },
// { type: "year", value: "2025" },
// { type: "literal", value: " at " },
// { type: "hour", value: "2" },
// { type: "literal", value: ":" },
// { type: "minute", value: "30" },
// { type: "literal", value: ":" },
// { type: "second", value: "45" },
// { type: "literal", value: " " },
// { type: "dayPeriod", value: "PM" }
// ]
hour、minute 和 second 类型表示时间组件。dayPeriod 类型表示 12 小时制中的上午或下午。
为日期部分应用自定义样式
formatToParts() 的主要用例是为不同组件应用不同的样式。你可以处理 parts 数组,将特定类型包裹在 HTML 元素中。
将月份名称加粗:
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
const html = parts
.map(part => {
if (part.type === "month") {
return `<strong>${part.value}</strong>`;
}
return part.value;
})
.join("");
console.log(html);
// Output: "<strong>January</strong> 15, 2025"
这种方法适用于任何标记语言。你可以通过处理 parts 数组生成 HTML、JSX 或其他格式。
为年份设置不同样式:
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
const html = parts
.map(part => {
if (part.type === "year") {
return `<span class="text-gray-500">${part.value}</span>`;
}
return part.value;
})
.join("");
console.log(html);
// Output: "January 15, <span class="text-gray-500">2025</span>"
在日历显示中,这种模式很常见,不同组件需要不同的视觉强调。
使用多种样式构建自定义日期显示
复杂界面通常会结合多种样式规则。你可以同时为不同部分类型应用不同的类或元素。
const formatter = new Intl.DateTimeFormat("en-US", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric"
});
const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
const html = parts
.map(part => {
switch (part.type) {
case "weekday":
return `<span class="weekday">${part.value}</span>`;
case "month":
return `<span class="month">${part.value}</span>`;
case "day":
return `<span class="day">${part.value}</span>`;
case "year":
return `<span class="year">${part.value}</span>`;
case "literal":
return `<span class="literal">${part.value}</span>`;
default:
return part.value;
}
})
.join("");
console.log(html);
// Output: "<span class="weekday">Wednesday</span><span class="literal">, </span><span class="month">January</span><span class="literal"> </span><span class="day">15</span><span class="literal">, </span><span class="year">2025</span>"
这种细粒度的控制可以为每个组件实现精确的样式。之后你可以用 CSS 分别设置每个类的样式。
创建自定义日期布局
你可以将日期组件重新排列,形成不同于标准本地格式的自定义布局。提取特定部分并以任意顺序组合。
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
const day = parts.find(p => p.type === "day").value;
const month = parts.find(p => p.type === "month").value;
const year = parts.find(p => p.type === "year").value;
const customLayout = `
<div class="date-card">
<div class="day-large">${day}</div>
<div class="month-small">${month}</div>
<div class="year-small">${year}</div>
</div>
`;
console.log(customLayout);
这样可以创建一个垂直卡片布局,突出显示日期,然后是月份和年份。即使布局不同于标准格式,各组件依然能正确本地化。
所有可用的部分类型
type 属性根据所用格式化选项可以有以下值:
weekday:星期几,如 Monday 或 Monera:纪元标识,如 BC、AD 或 BCEyear:年份,如 2025month:月份名称或数字,如 January 或 01day:日期,如 15dayPeriod:上午、下午或其他本地化日间时段hour:小时,如 14 或 2minute:分钟,如 30second:秒,如 45fractionalSecond:毫秒或其他小数秒timeZoneName:时区名称,如 PST 或 Pacific Standard Timeliteral:格式化时添加的空格、标点或其他文本relatedYear:替代历法系统中的公历年yearName:某些历法系统中的命名年份unknown:未识别的标记
并非每种格式选项都会生成所有类型的部分。实际返回的部分取决于日期值和格式化器的配置。
带有纪元指示符的日期:
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
era: "short"
});
const date = new Date(-100, 0, 1);
const parts = formatter.formatToParts(date);
console.log(parts);
// [
// { type: "year", value: "101" },
// { type: "literal", value: " " },
// { type: "era", value: "BC" }
// ]
带有时区的日期:
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
timeZoneName: "short"
});
const date = new Date(2025, 0, 15, 14, 30);
const parts = formatter.formatToParts(date);
console.log(parts);
// Parts will include { type: "timeZoneName", value: "PST" } or similar
带有小数秒的日期:
const formatter = new Intl.DateTimeFormat("en-US", {
hour: "numeric",
minute: "numeric",
second: "numeric",
fractionalSecondDigits: 3
});
const date = new Date(2025, 0, 15, 14, 30, 45, 123);
const parts = formatter.formatToParts(date);
// Parts will include { type: "fractionalSecond", value: "123" }
按条件高亮日期组件
某些应用会根据业务逻辑高亮特定的日期组件。通过 formatToParts(),你可以在保持正确格式的同时,根据日期值应用样式。
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
function formatDateWithHighlight(date) {
const parts = formatter.formatToParts(date);
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
const html = parts
.map(part => {
if (part.type === "day" && isWeekend) {
return `<span class="text-blue-600 font-bold">${part.value}</span>`;
}
return part.value;
})
.join("");
return html;
}
const saturday = new Date(2025, 0, 18);
console.log(formatDateWithHighlight(saturday));
// Output: "January <span class="text-blue-600 font-bold">18</span>, 2025"
const monday = new Date(2025, 0, 13);
console.log(formatDateWithHighlight(monday));
// Output: "January 13, 2025"
日期会根据本地化规则正确格式化,同时根据业务逻辑应用条件样式。
创建无障碍日期显示
你可以使用 formatToParts() 为格式化日期添加无障碍属性,帮助屏幕阅读器正确朗读日期值。
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
function formatAccessibleDate(date) {
const parts = formatter.formatToParts(date);
const formatted = parts.map(part => part.value).join("");
const isoDate = date.toISOString().split('T')[0];
return `<time datetime="${isoDate}">${formatted}</time>`;
}
const date = new Date(2025, 0, 15);
console.log(formatAccessibleDate(date));
// Output: "<time datetime="2025-01-15">January 15, 2025</time>"
这样可以确保屏幕阅读器正确朗读日期,同时界面显示本地化格式。
如何保留本地化格式的各个部分
parts 数组会自动维护本地化格式规则。不同的本地化环境会采用不同的顺序和格式,但 formatToParts() 会自动处理这些差异。
const usFormatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
const date = new Date(2025, 0, 15);
console.log(usFormatter.formatToParts(date));
// [
// { type: "month", value: "January" },
// { type: "literal", value: " " },
// { type: "day", value: "15" },
// { type: "literal", value: ", " },
// { type: "year", value: "2025" }
// ]
const ukFormatter = new Intl.DateTimeFormat("en-GB", {
year: "numeric",
month: "long",
day: "numeric"
});
console.log(ukFormatter.formatToParts(date));
// [
// { type: "day", value: "15" },
// { type: "literal", value: " " },
// { type: "month", value: "January" },
// { type: "literal", value: " " },
// { type: "year", value: "2025" }
// ]
英式格式会将日期放在月份前。无论本地化环境如何,你的样式代码都以相同方式处理 parts 数组,格式会自动适配。
const jpFormatter = new Intl.DateTimeFormat("ja-JP", {
year: "numeric",
month: "long",
day: "numeric"
});
console.log(jpFormatter.formatToParts(date));
// [
// { type: "year", value: "2025" },
// { type: "literal", value: "年" },
// { type: "month", value: "1月" },
// { type: "day", value: "15" },
// { type: "literal", value: "日" }
// ]
日式格式采用不同顺序,并包含“年”“日”等字符字面量。parts 数组会自动反映这些本地化约定。
将 formatToParts 与框架组件结合使用
现代框架(如 React)可以利用 formatToParts() 高效构建组件。
function DateDisplay({ date, locale, options }) {
const formatter = new Intl.DateTimeFormat(locale, options);
const parts = formatter.formatToParts(date);
return (
<span className="date-display">
{parts.map((part, index) => {
if (part.type === "month") {
return <strong key={index}>{part.value}</strong>;
}
if (part.type === "year") {
return <span key={index} className="text-sm text-gray-500">{part.value}</span>;
}
return <span key={index}>{part.value}</span>;
})}
</span>
);
}
该组件可对不同部分应用不同样式,同时保证任何本地化环境下的正确格式。
何时使用 formatToParts 与 format
当你只需要一个简单的格式化字符串且无需自定义时,使用 format()。这适用于大多数日期显示的常见场景。
当你需要以下功能时,请使用 formatToParts():
- 对日期的不同部分应用不同样式
- 使用格式化日期构建 HTML 或 JSX
- 为特定组件添加属性或元数据
- 将日期组件重新排列为自定义布局
- 将格式化日期集成到复杂布局中
- 以编程方式处理格式化输出
formatToParts() 方法相比 format() 有略高的开销,因为它会生成一个对象数组而不是单一字符串。对于典型应用来说,这种差异可以忽略,但如果你每秒需要格式化成千上万个日期,format() 的性能更优。
对于大多数应用,建议根据样式需求而非性能来选择。如果不需要自定义输出,请使用 format()。如需自定义样式或标记,则使用 formatToParts()。