如何在 JavaScript 中获取格式化日期的各个部分

使用 formatToParts() 单独访问区域设置格式化日期的每个组件

介绍

format() 方法返回一个完整的格式化字符串,例如 "2025年1月15日" 或 "15/01/2025"。这种方法适用于简单的显示,但无法对各个部分进行不同的样式设置。您无法将月份名称加粗、将年份设置为不同的颜色,或者对特定组件应用自定义标记。

JavaScript 提供了 formatToParts() 方法来解决这个问题。它不是返回一个单一的字符串,而是返回一个对象数组,每个对象代表格式化日期的一个部分。每个部分都有一个类型,例如 monthdayyear,以及一个值,例如 1月152025。然后,您可以处理这些部分以应用自定义样式、构建复杂布局或将格式化日期集成到丰富的用户界面中。

为什么格式化字符串难以自定义

当您收到一个格式化字符串,例如 "2025年1月15日",您无法轻松地识别月份结束和日期开始的位置。不同的语言环境会以不同的顺序排列组件。一些语言环境使用不同的分隔符。可靠地解析这些字符串需要复杂的逻辑,这些逻辑重复了 Intl API 中已经实现的格式化规则。

以一个日历应用为例,该应用以加粗的月份名称显示日期。使用 format() 方法,您需要:

  1. 检测哪些字符代表月份名称
  2. 考虑组件之间的空格和标点符号
  3. 处理不同语言环境中的不同月份格式
  4. 仔细解析字符串以避免破坏日期

这种方法既脆弱又容易出错。任何语言环境格式规则的更改都会破坏您的解析逻辑。

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);
// 输出: "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" }
// ]

hourminutesecond 类型表示时间组件。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);
// 输出: "<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);
// 输出: "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);
// 输出: "<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 或 Mon
  • era:时代指示符,例如 BC、AD 或 BCE
  • year:年份,例如 2025
  • month:月份名称或数字,例如 January 或 01
  • day:月份中的日期,例如 15
  • dayPeriod:AM 或 PM 或其他特定于区域的时间段
  • hour:小时,例如 14 或 2
  • minute:分钟,例如 30
  • second:秒,例如 45
  • fractionalSecond:毫秒或其他小数秒
  • timeZoneName:时区名称,例如 PST 或 Pacific Standard Time
  • literal:由格式化添加的空格、标点或其他文本
  • 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);
// 部分将包括 { type: "timeZoneName", value: "PST" } 或类似内容

带有小数秒的日期:

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);
// 部分将包括 { 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));
// 输出: "January <span class="text-blue-600 font-bold">18</span>, 2025"

const monday = new Date(2025, 0, 13);
console.log(formatDateWithHighlight(monday));
// 输出: "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));
// 输出: "<time datetime="2025-01-15">January 15, 2025</time>"

这确保了屏幕阅读器可以正确朗读日期,同时显示的是本地化格式的版本。

部件如何保留特定于语言环境的格式

部件数组会自动维护特定于语言环境的格式规则。不同的语言环境会以不同的顺序排列组件并使用不同的格式,但 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" }
// ]

英国格式将日期放在月份之前。您的样式代码以相同的方式处理部件数组,无论语言环境如何,格式都会自动适应。

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: "日" }
// ]

日语格式使用不同的顺序,并包括诸如年(年)和日(日)之类的字符文字。部件数组会自动反映这些特定于语言环境的约定。

将 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()