Carbon Utilities 日期时间格式化指南:基于 Intl API 的 dateTimeFormat 封装详解
【免费下载链接】carbonA design system built by IBM项目地址: https://gitcode.com/GitHub_Trending/carbo/carbon
导读
本文聚焦 IBM Carbon Design System 中@carbon/utilities包提供的dateTimeFormat工具,一个对原生Intl.RelativeTimeFormat、Intl.DateTimeFormat与Intl.DurationFormat的轻量封装。无论你需要在产品界面中展示"3 minutes ago"这类相对时间、Apr 4, 2024这类绝对日期,还是1h 23m 45s这类时长,dateTimeFormat都能以统一、可本地化的 API 在几行代码内完成。读完本文,你将掌握其全部三个子模块(relative / absolute / duration)的调用方式、样式参数与默认值、时区处理,以及底层实现原理与测试验证方法。
说明:本工具的完整用法遵循 Carbon for IBM Products 站点的 Date and time guidance 设计指南(仓库内开发者手册亦对日期时间相关规范有整体约束),本文重点讲解 API 本身。
dateTimeFormat 是什么
dateTimeFormat是@carbon/utilities(package.json)中提供的一组日期时间格式化封装。它本身不重复造轮子,而是围绕三个 ECMAScript Intl API 做薄封装:
Intl.RelativeTimeFormat—— 相对时间(如"3 minutes ago")Intl.DateTimeFormat—— 绝对日期时间(如"Apr 4, 2024 at 3:47 PM")Intl.DurationFormat—— 时长(如"1h 23m 45s")
在 index.ts 中可以看到它的聚合导出方式:
import * as relative from './relative'; import * as absolute from './absolute'; import * as duration from './duration'; export const dateTimeFormat = { relative, absolute, duration, };即dateTimeFormat是一个命名空间对象,由三个子模块组成。而 src/index.ts 再将其作为@carbon/utilities包的公开 API 导出,因此可以这样引入:
import { dateTimeFormat } from '@carbon/utilities';所有格式化结果都由底层 Intl API 按locale生成本地化文本,因此天然支持多语言,无需手写任何翻译映射。
相对时间:dateTimeFormat.relative
relative模块对应Intl.RelativeTimeFormat,用于将某个时间点相对"现在"进行人性化描述。
样式参数
- 支持的样式:
"long" | "short" | "narrow" - 默认样式:
"long"
基本用法
import { dateTimeFormat } from '@carbon/utilities'; dateTimeFormat.relative.format(timestamp); // 3 minutes ago dateTimeFormat.relative.format(timestamp, { locale: 'de-DE' }); // vor 3 Minuten dateTimeFormat.relative.format(timestamp, { style: 'short' }); // 3 min. ago可以看到,传入locale即可获得本地化文案,传入style可切换详细程度(long完整、short缩写、narrow最紧凑)。
底层实现:单位自动推导
从 relative.ts 的源码可以看到,format接收Date | number,内部完成"时间差 → 单位选择"的自动换算:
const seconds = Math.floor((now - d.getTime()) / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); const weeks = Math.floor(days / 7); const months = Math.floor(weeks / 4); const years = Math.floor(days / 365);随后按阶梯选择最合适的单位:
- 差值绝对值小于 60 秒 → 以
numeric: 'auto'的Intl.RelativeTimeFormat输出(例如"now") - 小于 60 分钟 → 输出分钟
- 小于 24 小时 → 输出小时
- 小于 7 天 → 输出天
- 小于 4 周 → 输出周
- 小于 365 天 → 输出月
- 其余 → 输出年
注意,months是按"4 周"估算的,years按 365 天估算,因此这是面向展示的精简近似换算,并非日历级的精确月/年差计算。测试用例 relative-test.js 覆盖了从 1 分钟到 2 年、以及未来时间(+)与过去时间(-)两个方向的全部单位分支,例如['hours', 23, 60 * 60]验证 23 小时内仍归入 hours 单位。
绝对时间:dateTimeFormat.absolute
absolute模块对应Intl.DateTimeFormat,提供四种粒度的格式化方法:formatTime、formatDate、format、formatRange。
formatTime:仅时间
- 支持的样式:
"full" | "long" | "medium" | "short" - 默认样式:
"short"
import { dateTimeFormat } from '@carbon/utilities'; dateTimeFormat.absolute.formatTime(timestamp); // 3:47 PM dateTimeFormat.absolute.formatTime(timestamp, { locale: 'de-DE' }); // 15:47 dateTimeFormat.absolute.formatTime(timestamp, { style: 'long' }); // 3:47:12 PM底层实现(absolute.ts)直接构造Intl.DateTimeFormat并传入timeStyle:
const dtf = new Intl.DateTimeFormat(options?.locale, { timeStyle: options?.style ?? 'short', timeZone: options?.timeZone, }); return dtf.format(date);formatDate:仅日期
- 支持的样式:
"full" | "long" | "medium" | "short" - 默认样式:
"medium"
import { dateTimeFormat } from '@carbon/utilities'; dateTimeFormat.absolute.formatDate(timestamp); // Apr 4, 2024 dateTimeFormat.absolute.formatDate(timestamp, { locale: 'de-DE' }); // 04.04.2024 dateTimeFormat.absolute.formatDate(timestamp, { style: 'full' }); // Thursday, April 4, 2024format:日期 + 时间
format是使用频率最高的方法,支持同时输出日期与时间,并额外提供"tooltip"快捷样式。
- 支持的样式:
"full" | "long" | "medium" | "short" | "tooltip""tooltip"是timeStyle: "long", dateStyle: "full"的简写
- 支持的时间样式:
"full" | "long" | "medium" | "short" - 支持的日期样式:
"full" | "long" | "medium" | "short" - 默认时间样式:
"short" - 默认日期样式:
"medium"
import { dateTimeFormat } from '@carbon/utilities'; dateTimeFormat.absolute.format(timestamp); // Apr 4, 2024 at 3:47 PM dateTimeFormat.absolute.format(timestamp, { locale: 'de-DE' }); // 04.04.2024, 15:47 dateTimeFormat.absolute.format(timestamp, { timeStyle: 'medium', dateStyle: 'short', }); // April 4, 2024 at 3:47:12 PM dateTimeFormat.absolute.format(timestamp, { style: 'short' }); // 4/4/24, 3:47 PM dateTimeFormat.absolute.format(timestamp, { style: 'tooltip' }); // Thursday, April 4, 2024 at 3:47:12 PM GMT+2从源码可以看到tooltip是如何展开的,以及各参数的优先级逻辑(absolute.ts):
const timeStyle = options?.timeStyle ?? (options?.style === 'tooltip' ? 'long' : options?.style) ?? 'short'; const dateStyle = options?.dateStyle ?? (options?.style === 'tooltip' ? 'full' : options?.style) ?? 'medium';即:显式传入的timeStyle/dateStyle优先级最高;其次是由style展开(其中'tooltip'映射为long+full);最后落到默认值short/medium。
formatRange:区间
formatRange用于展示一段起止时间区间,底层调用Intl.DateTimeFormat.prototype.formatRange。
- 支持的样式:
"full" | "long" | "medium" | "short" - 支持的时间样式:
"full" | "long" | "medium" | "short" | null - 支持的日期样式:
"full" | "long" | "medium" | "short" | null - 默认时间样式:
"short" - 默认日期样式:
"medium"
import { dateTimeFormat } from '@carbon/utilities'; dateTimeFormat.absolute.formatRange(startDate, endDate); // Apr 4, 2024, 3:47 PM – Apr 25, 2024, 4:29 PM dateTimeFormat.absolute.formatRange(startDate, endDate, { locale: 'de-DE' }); // 04.04.2024, 15:47 – 25.04.2024, 16:29 dateTimeFormat.absolute.formatRange(startDate, endDate, { timeStyle: 'medium', dateStyle: 'short', }); // 4/4/24, 3:47:12 PM – 4/25/24, 4:29:38 PM dateTimeFormat.absolute.formatRange(startDate, endDate, { style: 'short' }); // 4/4/24, 3:47 PM – 4/25/24, 4:29 PM仅日期区间
将timeStyle设为null即可省略时间,只展示日期区间:
import { dateTimeFormat } from '@carbon/utilities'; dateTimeFormat.absolute.formatRange(startDate, endDate, { timeStyle: null }); // Apr 28, 2016 – Jul 1, 2018仅时间区间
将dateStyle设为null可以只展示时间,但有一个重要前提:起止日期必须是同一天,否则省略日期会产生歧义。
import { dateTimeFormat } from '@carbon/utilities'; dateTimeFormat.absolute.formatRange(startDate, sameDayEndDate, { dateStyle: null, }); // 3:47 – 4:29 PM从 absolute.ts 源码可见,null会被转换为undefined传入Intl.DateTimeFormat,从而让该维度不参与渲染:
const timeStyle = options?.timeStyle === null ? undefined : (options?.timeStyle ?? options?.style ?? 'short'); const dateStyle = options?.dateStyle === null ? undefined : (options?.dateStyle ?? options?.style ?? 'medium');absolute-test.js(absolute-test.js)中用sameDayEndDate(与startDate同一天)专门验证了dateStyle: null这一分支,且对全部四种样式 × 时区组合做了与原生Intl.DateTimeFormat输出逐字一致的断言。
时区(timeZone)
absolute的所有方法都支持可选属性timeZone,例如你想展示 UTC 时间而非本地时区:
import { dateTimeFormat } from '@carbon/utilities'; dateTimeFormat.absolute.format(timestamp); // Apr 4, 2024 at 3:47 PM dateTimeFormat.absolute.format(timestamp, { timeZone: 'UTC' }); // Apr 4, 2024 at 10:47 PM时区选项遵循 ECMAScript 2026 Internationalization API Specification(Intl.DateTimeFormat的timeZone取值,如'UTC'、'Asia/Tokyo'等 IANA 时区名)。测试中即以UTC与Asia/Tokyo两组时区对formatTime、formatDate、format、formatRange全部进行了验证。
时长:dateTimeFormat.duration
duration模块对应Intl.DurationFormat,用于格式化时间段(如视频时长、任务耗时)。
- 支持的样式:
"long" | "short" | "narrow" | "digital" - 默认样式:
"narrow"
import { dateTimeFormat } from '@carbon/utilities'; dateTimeFormat.duration.format(time); // 1h 23m 45s dateTimeFormat.duration.format(time, { locale: 'de-DE' }); // 1h, 23 Min. und 45 Sek. dateTimeFormat.duration.format(time, { style: 'long' }); // 1 hour, 23 minutes, 45 seconds输入是一个以Intl.DurationFormatUnit为键的部分记录(duration.ts),例如{ hours: 1, minutes: 23, seconds: 45 },允许只传入需要的字段。默认样式"narrow"对应最紧凑的输出(1h 23m 45s),"digital"对应如1:23:45的数字风格,"long"输出完整单词。
duration-test.js 验证了"未传 style 时默认等于narrow",并对四种样式逐一与原生Intl.DurationFormat输出比对。
方法签名速查
| 子模块 | 方法 | 输入类型 | 可选参数 | 默认值 |
|---|---|---|---|---|
relative | format(date) | Date \| number | locale,style: 'long' \| 'short' \| 'narrow' | style: 'long' |
absolute | formatTime(date) | Date \| number | locale,style,timeZone | style: 'short' |
absolute | formatDate(date) | Date \| number | locale,style,timeZone | style: 'medium' |
absolute | format(date) | Date \| number | locale,style(含'tooltip'),timeStyle,dateStyle,timeZone | timeStyle: 'short',dateStyle: 'medium' |
absolute | formatRange(start, end) | Date \| number | locale,style,timeStyle(可为null),dateStyle(可为null),timeZone | timeStyle: 'short',dateStyle: 'medium' |
duration | format(duration) | Partial<Record<Intl.DurationFormatUnit, number>> | locale,style: 'long' \| 'short' \| 'narrow' \| 'digital' | style: 'narrow' |
所有date参数均接受Date对象或时间戳数字(Date \| number);locale遵循 BCP 47 语言标签(如'en-US'、'de-DE');所有输出结果完全交由浏览器或 Node.js 运行时中的 Intl 实现生成,因此无需引入任何额外的 i18n 依赖。
测试与正确性保障
仓库为每个子模块都提供了独立的测试文件,采用"与原生 Intl 输出逐字比对"的策略来保证封装行为的正确性:
- relative-test.js:覆盖三种样式,对过去/未来两个方向、从 30 秒到 2 年的 12 组单位 × 数量组合进行断言;
- absolute-test.js:对
formatTime/formatDate/format/formatRange的每种样式、timeStyle/dateStyle手动组合、null省略分支以及UTC、Asia/Tokyo时区,全部与直接构造的Intl.DateTimeFormat输出比对; - duration-test.js:验证默认样式为
narrow,并对long、short、narrow、digital四种样式逐一比对。
由于该测试策略不依赖固定字符串,而是实时计算期望值,即使运行环境(Node/浏览器)的 ICU 数据或 Intl 实现版本不同,测试依然成立——这恰好也说明dateTimeFormat的输出是"运行时 Intl 能力"的直接反映。
使用建议与注意事项
- 相对时间的单位近似:
relative中月按 4 周、年按 365 天折算,适合"几分钟前 / 上个月 / 去年"这类展示场景;若需要精确的日历月差,请自行计算或改用其他方案。 - 仅时间区间的限制:
formatRange的dateStyle: null仅适用于起止同日,跨日时必须保留日期,否则语义不完整。 - 时区语义:
timeZone只在absolute系列方法上可用(relative与duration与本地时间无关,不提供该参数)。 - 运行环境要求:
Intl.DurationFormat是较新的 API,请确认目标运行环境(Node 版本 / 浏览器版本)已支持;Intl.RelativeTimeFormat与Intl.DateTimeFormat的timeStyle/dateStyle/formatRange在现代环境中支持良好。 - 零成本接入:工具本身仅做参数透传与默认值处理,不携带任何格式化数据或翻译表,包体积开销极小。
以上就是@carbon/utilities中dateTimeFormat的完整用法。在 Carbon 相关的 React、Web Components 等产品界面中,需要展示时间戳、时间区间或时长的任何位置,都可以直接引入它获得一致、可本地化、且贴合 Carbon 设计语言时间展示规范的输出。
【免费下载链接】carbonA design system built by IBM项目地址: https://gitcode.com/GitHub_Trending/carbo/carbon
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考