尧图精选

effect Cron 解析器本地化修复:让 cron 月份与星期别名摆脱宿主 Locale 依赖

🕒 发布时间:2026/9/13 7:36:30 📁 来源:尧图网络
effect Cron 解析器本地化修复让 cron 月份与星期别名摆脱宿主 Locale 依赖【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code本文基于 effect-smol 仓库中的 changeset 记录cron-locale-independent-aliases内容Normalize cron month and weekday aliases independently of the host locale.围绕 Effect 库Cron模块的 cron 表达式解析机制展开。读完本文你将理解 cron 表达式中月份/星期别名如jan、mon的解析链路为什么小写归一化这一步会与宿主运行时 locale 产生耦合、在何种环境下可能出偏差以及这个 patch 级修复所保证的行为边界。一、从一个 Changeset 看这次修复的范围该 changeset 位于 .repos/effect-smol/.changeset/pre/cron-locale-independent-aliases.md全文如下--- effect: patch --- Normalize cron month and weekday aliases independently of the host locale.三点信息可以直接读出影响包是effect本体而不是某个 peripheral 包版本级别是patch说明这是一次行为修正而非新增能力——别名解析的对外契约jan→1、mon→1 等映射关系不变变化的是别名在归一化之前如何处理大小写这一步的稳定性修复目标是与宿主 locale 无关independently of the host locale即同一份 cron 表达式在任何时区、任何区域设置的 Node/Deno/Bun 运行时上解析结果必须一致。对应的实现代码在 .repos/effect-smol/packages/effect/src/Cron.ts别名解析集中在文件末尾的monthOptions、weekdayOptions与aliasOrValue三个位置约 L1184-L1316。二、Cron 模块速览Cron值如何表示一个日历周期Cron值保存六个字段约束集合seconds、minutes、hours、days、months、weekdays加一个可选时区并提供比较、日期匹配、寻找上一次/下一次触发点的完整能力见模块头注释Cron.ts。字段取值范围在make构造器中通过restrictions统一校验Cron.ts字段合法区间备注seconds / minutes0–59非整数值或越界抛RangeErrorhours0–23同上daysday-of-month1–31空集合表示不受限months1–12空集合表示不受限weekdays0–77会被normalize归一化为0两者都表示周日一个值得注意的语义细节当days与weekdays同时受限时默认是OR 语义任一字段匹配即命中只有显式传入and: true才要求两个字段同时匹配。这与 Vixie cron 的行为一致。三、cron 表达式语法与解析链路Cron.parse(cron, tz?)返回ResultCron, CronParseErrorCron.tsCron.parseUnsafe则在其上直接getOrThrowCron.ts。解析流程如下切分字段按空白字符切分允许 5 段或 6 段5 段时在前面补一个0作为秒字段。段数不合法时返回CronParseError消息为Invalid number of segments in cron expression时区解析tz可以是DateTime.TimeZone值也可以是 IANA 时区名字符串如America/New_York字符串解析失败返回Invalid time zone in cron expression逐段解析parseSegment依次处理六个字段每段支持*通配等价于全范围首段*或*/1会让该字段变为空集合即不受限逗号分隔的值列表如5,*、MON,FRI范围8-14以及start-max/step形式如0-20/2步长/n必须是 1 到该字段max之间的正整数否则分别报Expected step value to be a positive integer/Expected step value to be greater than 0/Expected step value to be less than or equal to N月份与星期别名本修复的重点见下节组装Result.all汇总六段结果后调用make。and标志由days与weekdays的 wildcard 状态自动推导只有两个字段都以*开头例如0 0 9 * * *这类显式全范围写法且都非空时置为and: true这正是 parse 与match的 OR/AND 语义能对齐的原因Cron.ts。示例引自源码 JSDocimport { Cron, Result } from effect // 每月 8 至 14 日的 04:00 const cron Result.getOrThrow(Cron.parse(0 0 4 8-14 * *)) Array.from(cron.hours) // [4] Array.from(cron.days) // [8, 9, 10, 11, 12, 13, 14] // 带时区纽约时间 09:00 const cronWithTz Cron.parseUnsafe(0 0 9 * * *, America/New_York) Cron.match(cronWithTz, 2024-01-01T14:00:00Z) // true四、月份与星期别名解析表与归一化别名通过每段字段的SegmentOptions.aliases表注入Cron.ts月份monthOptionsmin 1 / max 12jan→1、feb→2、mar→3、apr→4、may→5、jun→6、jul→7、aug→8、sep→9、oct→10、nov→11、dec→12星期weekdayOptionsmin 0 / max 7sun→0、mon→1、tue→2、wed→3、thu→4、fri→5、sat→6同时normalize把7折叠为0。splitRange负责把A-B或单值拆开aliasOrValue负责单个 token 的求值Cron.tsfunction aliasOrValue(field: string, aliases?: Recordstring, number): number { return aliases?.[String.toLowerCase(field)] ?? (decimalRegex.test(field) ? Number(field) : NaN) }语义是先查别名表查不到再尝试按纯数字解析两者都失败得到NaN随后由上层报Expected a positive integer。别名表本身的 key 是小写因此查询前必须先对输入做小写归一化——这正是 changeset 描述的修复点归一化必须是 locale-independent 的否则同一段表达式在土耳其语、德语、爱沙尼亚语等 locale 的宿主上可能得到不同结果。五、为什么locale 无关是个真问题JavaScript 的大小写折叠并非全局一致的。宿主 locale 会改变String.prototype.toLowerCase对特定 Unicode 码点的行为最典型的两例土耳其语tr大写IU0049小写化为带点的ıU0131而不是i德语de与爱沙尼亚语etß的小写仍是ß其反向折叠大写SS在toUpperCase方向上才体现而爱沙尼亚语中b的大写是B、某些字符的折叠方向与英语不同。对于0 0 9 * JAN MON这类全大写别名归一化必须得到jan/mon才能命中别名表。如果某条小写化路径依赖宿主 locale例如经过toLocaleLowerCase而未固定 locale在tr-TR等 locale 下I可能变成ıJAN→jAN的折叠链条就可能得到jan之外的值别名查表落空、数字正则又不匹配最终退化为NaN解析失败。从源码结构看修复前的实现使用了会受宿主 locale 影响的归一化路径修复后的目标是把别名查询前的折叠固定在一个确定的、与运行环境无关的语义上当前仓库中该处表现为String.toLowerCase(field)这样的确定性小写化配合纯 ASCII 的别名表。无论具体走哪条实现对外可观察的行为契约由 changeset 与测试共同锁定任意大小写混写的别名Jan、JAN、jAn……在任意宿主 locale 下都必须解析成同一个数字。顺带说明范围边界这个修复只作用于别名查表前的大小写归一化。数字字面量、步长、范围与通配符的解析本来就不涉及 locale时区名字符串走DateTime的 IANA 数据库同样不受影响。六、匹配与推进别名解析完之后发生了什么解析出的集合交给match/next/prev/sequencematch(cron, date)Cron.ts按 cron 自带时区缺省用宿主时区把输入日期投影成日历分量逐字段检查空集合跳过days/weekdays同时受限时按上文 OR/AND 语义处理next/prevstepCronCron.ts基于预计算的next/prev查找表逐字段推进含 DST 漂移补偿adjustDst、31 日跨月跳过、闰日2 29等处理prev是严格搜索——若给定点本身命中返回更早的一次sequence无限迭代器反复调用next。别名解析正确性直接影响这些 API 的结果。测试文件 .repos/effect-smol/packages/effect/test/Cron.test.ts 中有大量以大写星期别名书写的用例例如Cron.parse(0 0 1-31 * MON) Cron.parse(0 0 */2 * MON) Cron.parse(0 1 * * MON,FRI) // ... assertTrue(match(0 0 */2 * MON, new Date(2024-01-15 00:00:00))) deepStrictEqual(next(Cron.parseUnsafe(0 0 29 2 MON, tz), after), new Date(2024-02-05T00:00:00.000Z))这些用例覆盖了别名参与范围1-31 * MON、步长*/2 * MON、逗号列表MON,FRI以及next/prev/match全链路是验证别名语义不受环境影响的主要回归依据。七、实用要点小结解析入口外部输入配置、用户填写的表达式优先用Cron.parse拿到Result再用Cron.isCronParseError收窄错误内部可信字符串用Cron.parseUnsafe别名只在 months 与 weekdays 两个字段有效且仅支持 Vixie cron 标准三字母形式jan–dec、sun–sat不是January/Monday长形式7与0等价都是周日make的normalize与weekdayOptions双重保证days/weekdays 同时受限时默认 OR需要 AND 时显式and: trueparse会在两字段均为显式*开头时自动置 ANDlocale 修复的意义别名解析从依赖宿主环境的折叠行为变为确定性行为跨 Node/Deno/Bun、跨部署区域LANG/ICU 差异的解析结果保持一致format是单向工具它丢弃时区与and信息把Cron值格式化回表达式字符串后不能保证 round-trip 等价不要用它做持久化格式Cron.ts。参考路径Changeset 原文.repos/effect-smol/.changeset/pre/cron-locale-independent-aliases.md实现.repos/effect-smol/packages/effect/src/Cron.tsmonthOptions/weekdayOptions见 L1184-L1216aliasOrValue见 L1314-L1316测试.repos/effect-smol/packages/effect/test/Cron.test.ts适用前提以上结论基于 effect-smol 仓库当前Cron模块源码与该 changeset 记录针对effect包的patch版本发布since标注显示Cron核心 API 自 2.0.0 起可用CronParseError相关接口自 4.0.0 起可用。【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →