在 TypeScript 项目里使用 Luxon 处理日期时间展示时,调用 DateTime.toLocaleString 传入手写对象虽然能运行,但字段拼写错误、互斥选项同时出现、预设风格不一致等问题都不容易被发现。Luxon 底层虽然复用了 Intl 的国际化能力,但它对 TypeScript 类型的暴露并不够精细,业务层需要再封装一层 Options 类型来统一管理格式化配置。下面会从类型约束、预设解析到实际调用,给出一个可以直接落地的封装方案。

一、为什么需要封装 Options 类型
Luxon 的 toLocaleString 方法接受两个参数:第一个参数是 Intl.DateTimeFormatOptions 的兼容对象,第二个参数是 LocaleOptions,可以指定语言。直接使用原生类型有几个问题:可选字段过多,团队中容易写出 { weekday: 'long', year: 'numeric', month: '2-digit', day: 'numeric' } 这类重复定义;同时原生的 dateStyle 与 timeStyle 不能和具体组件字段同时出现,但 TypeScript 默认并不会阻止这种冲突。
业务上往往还会定义一些预设风格,比如短日期、完整时间等。如果每次调用都手动拼对象,不同页面显示格式可能不一致。封装一个统一的类型与格式化函数,可以在编译阶段就限制字段范围,并在运行时把预设转换成 Luxon 接受的参数,这样多语言支持和可维护性都会明显提升。
二、核心类型设计与互斥约束
先定义基础字段。可以不用泛型工具直接写一个接口,明确每个键的可选值。例如 year 只允许 'numeric' 或 '2-digit',month 允许 'numeric' | '2-digit' | 'long' | 'short' | 'narrow'。同时把 dateStyle 和 timeStyle 单独拿出来,因为它们与组件字段互斥。下面是一个基础接口:
interface BaseDateTimeFormatOptions {
weekday?: 'long' | 'short' | 'narrow';
era?: 'long' | 'short' | 'narrow';
year?: 'numeric' | '2-digit';
month?: 'numeric' | '2-digit' | 'long' | 'short' | 'narrow';
day?: 'numeric' | '2-digit';
hour?: 'numeric' | '2-digit';
minute?: 'numeric' | '2-digit';
second?: 'numeric' | '2-digit';
hour12?: boolean;
hourCycle?: 'h11' | 'h12' | 'h23' | 'h24';
timeZoneName?: 'short' | 'long';
numberingSystem?: string;
}
然后定义两种互斥变体。一种只允许使用 dateStyle 或 timeStyle,另一种只允许使用具体组件字段。可以用交叉类型和 never 禁止冲突,但为了避免复杂泛型,可以直接使用联合类型和两个接口:
interface StyleBasedOptions {
dateStyle?: 'full' | 'long' | 'medium' | 'short';
timeStyle?: 'full' | 'long' | 'medium' | 'short';
}
interface ComponentBasedOptions extends BaseDateTimeFormatOptions {
dateStyle?: never;
timeStyle?: never;
}
type LocalizedFormatOptions = StyleBasedOptions | ComponentBasedOptions;
这样设计后,当开发者同时传入 dateStyle 和 year 时,TypeScript 会因为 year 不在 StyleBasedOptions 中,且 ComponentBasedOptions 中 dateStyle 被设为 never 而报错。同样地,传入 timeStyle 与 hour 也会被拦截。不过交叉类型报错信息可能不够直观,可以在类型上添加注释,或者使用更细粒度的联合类型来提升体验。
三、预设解析与本地化参数合并
定义一个扩展类型加入预设字段 preset 和语言字段 locale。预设与组件字段也是互斥的,可以采用函数重载或单独的配置接口。为了简单,我们定义一个 FormatOptionsWithPreset 类型,并把最终函数参数改为联合类型:
interface PresetOptions {
preset?: 'short' | 'medium' | 'long' | 'full';
locale?: string;
}
type DateTimeFormatOptions = (LocalizedFormatOptions | PresetOptions) & {
locale?: string;
};
接下来写解析函数。函数内部先判断是否传了 preset,如果传了则映射到对应的 dateStyle 与 timeStyle,否则直接使用组件配置。还需要把 locale 传给 Luxon 第二个参数。代码示例:
import { DateTime } from 'luxon';
function resolveIntlOptions(
options: DateTimeFormatOptions
): Intl.DateTimeFormatOptions {
if ('preset' in options && options.preset) {
const styleMap = {
short: { dateStyle: 'short', timeStyle: 'short' },
medium: { dateStyle: 'medium', timeStyle: 'medium' },
long: { dateStyle: 'long', timeStyle: 'long' },
full: { dateStyle: 'full', timeStyle: 'full' },
} as const;
return styleMap[options.preset];
}
const { preset, locale, ...rest } = options as PresetOptions & LocalizedFormatOptions;
return rest as Intl.DateTimeFormatOptions;
}
function formatDateTime(
dt: DateTime,
options: DateTimeFormatOptions = {}
): string {
const { locale } = options;
const intlOptions = resolveIntlOptions(options);
return dt.setLocale(locale ?? 'zh-CN').toLocaleString(intlOptions);
}
上面示例中,如果同时传 preset 和组件字段,类型系统会阻止,因为 PresetOptions 与 LocalizedFormatOptions 没有公共组件字段。不过函数内部仍需要处理运行时可能存在的冲突。因此可以在 resolveIntlOptions 中加一个运行时校验,如果发现 preset 与具体字段共存,就丢弃预设并采用组件字段。这能在数据来自外部 JSON 时避免异常。
四、使用示例与边界情况
展示几个调用例子。短预设格式化、自定义日期组件、指定语言与数字系统。
const now = DateTime.now();
// 使用预设:短日期 + 短时间
const shortText = formatDateTime(now, {
preset: 'short',
locale: 'zh-CN',
});
// 使用组件字段:完整星期 + 年月日
const detailText = formatDateTime(now, {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
locale: 'en-US',
});
// 指定 12 小时制与数字系统
const hour12Text = formatDateTime(now, {
hour: 'numeric',
minute: '2-digit',
hour12: true,
numberingSystem: 'arab',
locale: 'ar',
});
还有一些边界情况需要注意。比如 dateStyle 与 timeStyle 可以与 hour12 同时使用吗?根据 Intl 规范,dateStyle 和 timeStyle 可以与 hour12 共存,但不能与 hour 等组件字段共存。我们的类型设计允许 StyleBasedOptions 中没有 hour12,如果业务需要,可以把 hour12 加到 StyleBasedOptions 中,但要小心不同浏览器实现差异。另一个边界是 locale 使用小程序或旧版本浏览器时,某些值可能不被支持,Luxon 会回退到默认区域设置。
如果函数被用于服务器端渲染,需要确保 DateTime 对象的 zone 设置正确,否则本地化的时区偏移可能会造成时间显示不一致。建议在封装函数中只负责格式与语言,不修改 DateTime 的时区,由调用方统一创建带正确时区的实例。
TypeScriptLuxon日期时间格式化修改时间:2026-09-30 04:26:17