Luxon 升级指南:从 1.x / 2.x 迁移到 3.0 的破坏性变更全解析
【免费下载链接】luxon⏱ A library for working with dates and times in JS项目地址: https://gitcode.com/gh_mirrors/lu/luxon
Luxon 是专为 JavaScript 设计的日期与时间处理库,提供了DateTime、Duration、Interval等不可变、可链式调用的 API,并原生支持时区与Intl。本文基于仓库中的 docs/upgrading.md,完整梳理 Luxon 从 1.x 到 2.0、再到 3.0 的全部破坏性变更:读完你不仅能逐条对照迁移存量代码,还能理解"system 与 default 时区语义""fromObject 双参数签名""Settings.defaultZone 重构"等变更背后的源码实现原理,以及如何用仓库内的测试用例验证迁移结果。
一、3.0 的唯一破坏性变更:"system" 时区语义修正
Luxon 3.0 只引入了一个破坏性变更:当以字符串"system"指定时区时,始终解析为运行环境的系统时区(SystemZone),与全局默认时区设置无关。而要想拿到"当前默认时区(无论它被设置成什么)",必须显式使用"default":
Settings.defaultZone = "America/Chicago"; DateTime.now().setZone("default") // 结果是芝加哥时间(America/Chicago) DateTime.now().setZone("system") // 使用用户机器的系统时区如果觉得这个语义"理所当然",请务必注意:在 3.0 之前它并不是这样工作的——这正是它被列为破坏性变更的原因。在旧版本中"system"的表现受默认时区影响,行为不一致,3.0 将其固定为"系统时区",并新增"default"作为"跟随默认设置"的显式关键字。
源码印证:normalizeZone 中的关键字分流
"system"与"default"的分流逻辑位于 src/impl/zoneUtil.js 的normalizeZone()函数中:
if (lowered === "default") return defaultZone; else if (lowered === "local" || lowered === "system") return SystemZone.instance; else if (lowered === "utc" || lowered === "gmt") return FixedOffsetZone.utcInstance; else return FixedOffsetZone.parseSpecifier(lowered) || IANAZone.create(input);可以看到"default"命中第一分支,返回的是传入的defaultZone(即Settings.defaultZone的当前值);而"system"与遗留的"local"都会命中第二分支,直接返回SystemZone.instance单例,从而彻底与默认时区解耦。
SystemZone的实现位于 src/zones/systemZone.js:它通过单例模式暴露instance,其type恒为"system",name由new Intl.DateTimeFormat().resolvedOptions().timeZone动态获取(即浏览器/Node 所在机器的系统时区名),偏移量由-new Date(ts).getTimezoneOffset()计算,isValid恒为true。
测试印证:系统时区与默认时区被严格区分
仓库测试 test/datetime/zone.test.js 中有多组用例专门验证这一语义:
DateTime#setZone accepts "system" and uses the system zone:断言DateTime.utc().setZone("system").zoneName等于Settings.defaultZone.name(系统时区名);DateTime#setZone accepts "default" and uses the default zone:在Helpers.withDefaultZone("Europe/Paris", ...)包裹下,断言setZone("default").zoneName === "Europe/Paris";Setting the default zone to 'system' gives you back the system zone:先设置Settings.defaultZone = "Asia/Tokyo",再改回"system",断言DateTime.local().zoneName恢复为系统时区名。
这三组用例恰好对应 3.0 语义修正后应当成立的行为,迁移时可以仿照它们为你的代码补上回归测试。
二、2.0 的破坏性变更总览
Luxon 2.0 是一轮更大规模的 API 重构,破坏性变更主要分为三类:环境支持收紧、方法签名统一、时区命名澄清。下面逐项展开。
三、环境支持收紧与 polyfill 构建移除
Luxon 2.0 起,不再支持 Node < 12,也不再支持任何版本的 IE,仅支持较新版本的主流浏览器。这一决策让 Luxon 可以在代码中大胆假设环境能力(例如Intl、原生时区支持等),从而大幅简化内部实现。
与此对应,官方不再提供 polyfill 版本构建——Luxon 依赖的所有能力在目标浏览器中均已原生可用。具体支持范围可参考 docs/matrix.md(Support Matrix)。这意味着如果你的项目仍需要兼容老 IE 或超低版本 Node,需要自行评估并停留在 1.x 版本。
四、方法签名变更:fromObject 双参数化
2.0 起,DateTime.fromObject()与Duration.fromObject()统一改为接受两个参数:第一个是字段对象,第二个是选项对象。此前混在同一个对象里的时区、语言等配置项必须拆出来:
// Luxon 1.x DateTime.fromObject({ hour: 3, minute: 2, zone: "America/New_York", locale: "ru" }); Duration.fromObject({ hours: 3, minutes: 2, conversionAccuracy: "casual", locale: "ru" }); // vs Luxon 2.x DateTime.fromObject({ hour: 3, minute: 2 }, { zone: "America/New_York", locale: "ru" }); Duration.fromObject({ hours: 3, minutes: 2 }, { conversionAccuracy: "casual", locale: "ru" });源码印证:fromObject 的选项解析路径
在 src/datetime.js 中,DateTime.fromObject(obj, opts = {})的第一步就是通过normalizeZone(opts.zone, Settings.defaultZone)解析选项里的时区,再通过Locale.fromObject(opts)构造本地化配置。也就是说,zone、locale等配置只能来自第二个参数opts,混在字段对象里的旧写法在 2.x 下将不再生效。
Duration.fromObject的选项同样独立承载:在 src/duration.js 中,conversionAccuracy选项决定采用哪一套换算矩阵——"longterm"(长期平均)还是默认的"casual"(按每月 30 天、每年 365 天等近似换算),并在 src/duration.js 的构造函数中据此选择accurateMatrix或casualMatrix。
五、toLocaleString:Intl 选项与 DateTime 配置分离
在 1.x 中,toLocaleString()允许把Intl.DateTimeFormat的格式化选项与locale之类的 DateTime 配置混在同一个 options 参数里。2.0 起拆成两个参数:第一个参数为 Intl 格式化选项(或 Luxon 预设常量),第二个参数为 DateTime 配置覆盖项:
// Luxon 1.x DateTime.now().toLocaleString({ hour: "2-digit", locale: "ru" }); // vs Luxon 2.x DateTime.now().toLocaleString({ hour: "2-digit" }, { locale: "ru" });源码印证:双参数的实际消费方式
在 src/datetime.js 中,toLocaleString(formatOpts = Formats.DATE_SHORT, opts = {})用this.loc.clone(opts)把第二个参数并入实例的本地化配置,再用Formatter.create(this.loc.clone(opts), formatOpts).formatDateTime(this)完成格式化。从源码结构看,formatOpts只负责传给Intl.DateTimeFormat层面的呈现样式,而locale、numberingSystem、outputCalendar等行为性配置统一经由opts注入——二者职责边界清晰,也正是迁移时最容易踩坑的地方:记得把locale从格式化选项对象中挪到第二个参数。
六、时区命名澄清:"local" 更名为 "system"
2.0 将"运行环境的时区"(即运行 Luxon 的电脑/浏览器所设置的时区)从"local"更名为"system",以消除歧义(避免与"本地时间"的日常语义混淆):
DateTime.fromObject({}, { zone: "local" }) // 仍然可用(向后兼容) DateTime.fromObject({}, { zone: "system" }) // 推荐写法 DateTime.fromObject({}, { zone: "system" }).zone // => 类型为 SystemZone DateTime.fromObject({}, { zone: "system" }).zone.type // => "system"这一点与第一节中 3.0 的语义修正是同一条主线的延续:"local"作为"system"的别名在 src/impl/zoneUtil.js 中继续被接受,但语义始终指向SystemZone.instance。
七、Settings.defaultZone 重构与 defaultZoneName 移除
2.0 清理了Settings.defaultZone的取值与赋值语义,统一为"写时可传字符串或 Zone 实例,读时恒返回 Zone 实例":
// 赋值 Settings.defaultZone = "America/New_York"; // 可以是字符串 Settings.defaultZone = IANAZone.create("America/New_York"); // 也可以是 Zone 实例 // 读取 Settings.defaultZone //=> 总是返回一个 Zone 实例最显著的破坏性变更:Settings.defaultZoneName这个属性彻底不复存在。迁移时所有读取Settings.defaultZoneName的代码都要改为读取Settings.defaultZone.name(Zone 实例的name属性),或直接使用返回的 Zone 实例。
源码印证:defaultZone 的读写实现
在 src/settings.js 中:
static set defaultZone(zone) { defaultZone = zone; } static get defaultZone() { return normalizeZone(defaultZone, SystemZone.instance); }可见存储层只是保留原始值(字符串、Zone 实例或"system"),而读取时统一经normalizeZone归一化为 Zone 实例;模块级初始值defaultZone = "system"(src/settings.js)也印证了"默认默认值就是系统时区"。normalizeZone对字符串的解析顺序(default→system/local→utc/gmt→ 固定偏移 → IANA 时区)直接决定了Settings.defaultZone接受哪些合法输入,详见 src/impl/zoneUtil.js。
另外注意:setZone()方法内部同样调用normalizeZone(zone, Settings.defaultZone)(src/datetime.js),所以"default"关键字在setZone与Settings.defaultZone两条路径上的语义是一致的。
八、其他破坏性变更清单
除了上述大项,2.0 还有一批小规模但会影响编译或运行结果的变更,迁移时应逐条排查:
| 变更项 | 1.x | 2.x |
|---|---|---|
toObject的配置输出 | DateTime#toObject({ includeConfig: true })可附带配置 | includeConfig选项被移除,不再支持 |
| 本地化解析结果方法名 | resolvedLocaleOpts() | 更名为resolvedLocaleOptions() |
| Zone 的通用性判断属性 | Zone#universal | 更名为Zone#isUniversal |
其中toObject()的实现位于 src/datetime.js(opts.includeConfig分支在 2.0 中被移除,仅保留纯粹的日历字段输出);resolvedLocaleOptions()的实现在 src/datetime.js,返回{ locale, numberingSystem, outputCalendar }三元组;isUniversal则定义在抽象基类 src/zone.js 上,各子类(如SystemZone、FixedOffsetZone、IANAZone)分别覆写。
九、非破坏性变更:local/utc 获得选项参数
作为配套增强,DateTime.local()与DateTime.utc()在 2.0 起也支持在末尾传入一个 options 参数来设置zone与locale,与fromObject()保持一致。它们的实现位于 src/datetime.js:通过lastOpts(arguments)从参数列表末尾剥离选项对象,DateTime.utc()还会强制opts.zone = FixedOffsetZone.utcInstance。示例:
DateTime.local(2017, 3, 12, { locale: "fr" }); // 2017-03-12T00:00:00,法语 locale DateTime.utc(2017, 3, 12, 5, 45, { locale: "fr" }); // 2017-03-12T05:45:00Z,法语 locale十、版本演进脉络与迁移建议
从源码和官方文档(docs/upgrading.md)可以看出,Luxon 2.0 的变更目标高度聚焦:统一选项参数的放置位置、澄清时区语义、简化环境支持假设。正如原文档所述,Luxon 团队原本对 2.0 有更宏大的规划(TypeScript 移植、错误处理重构等),但因浏览器演进速度与维护精力的现实约束,最终只落地了这一组"为后续演进腾出操作空间"的基础变更。这与当前仓库仍在持续推进的事实相符:README(README.md)中正公开征集 Luxon 4.0 的路线图反馈。
给存量项目的最终迁移清单:
- 若用到
Settings.defaultZoneName,改为Settings.defaultZone.name; - 把
fromObject/toLocaleString中混入的配置项(zone、locale、conversionAccuracy等)拆分到第二个参数; - 将
Zone#universal改为Zone#isUniversal、resolvedLocaleOpts改为resolvedLocaleOptions; - 移除
toObject({ includeConfig: true })的调用; - 检查所有
"local"字符串,业务语义是"系统时区"就改写成"system",语义是"跟随默认设置"就改写成"default"; - 确认运行环境满足 2.0+ 的要求(Node ≥ 12、现代浏览器、无需 polyfill 构建);
- 参考 test/datetime/zone.test.js 中的断言模式,为时区相关迁移补充回归测试。
关于每个变更的完整 API 行为细节,可继续查阅仓库内的 docs/calendars.md、docs/zones.md、docs/settings.md 相关章节 等文档,以及 src/datetime.js、src/duration.js、src/settings.js 等源码。
【免费下载链接】luxon⏱ A library for working with dates and times in JS项目地址: https://gitcode.com/gh_mirrors/lu/luxon
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考