手写 cron 表达式解析器:六个容易踩的边界
为什么自己写
给内部小工具加定时能力时,我先用了现成的 cron 库,遇到两个问题:表达式写错时只返回空数组,不知道错在哪;*/7 这种不能整除的步长,实际命中和我以为的不一样。于是自己写了一个五百行不到的解析器,把六个边界整理如下。
一、步长的起点不是零
*/7 在分钟位,是从 0 开始每 7 分钟,命中 0、7、14、21、28、35、42、49、56,共 9 个值,不是 60 除以 7 取整的 8 个。最后一组只剩 4 个,所以总数是 9。
换成 5-30/7,起点变成 5,命中 5、12、19、26。很多人写 1-31/7 期待每 7 天,实际命中 1、8、15、22、29 共 5 天,多出的两天常被忽略。我的做法是把字段展开成具体取值集合再交给调度器,而不是把表达式原样透传。
二、日与星期的语义分歧
标准 Vixie cron 里,日和星期都不是星号时是或的关系:每月 1 号或每周一都触发。很多人按并且理解,结果任务跑得比预期频繁。Quartz 又是第三套语义。我的做法是在界面上写清当前实现用哪一种,并对两者都指定的情况加醒目说明。歧义不可怕,悄悄歧义才可怕。
三、不会命中的表达式
0 0 30 2 * 指 2 月 30 日,永远不命中。朴素实现会每分钟往前推,页面表现为卡死。我加了 60 万次循环上限(约 416 天),超过就判定无匹配时间点并提示检查日期组合。一年最多 525600 分钟,60 万足够覆盖闰年和跨年,又不会让浏览器失去响应。
四、月末与闰日
每月 31 号在 2、4、6、9、11 月都不会触发。这不算 bug,但用户往往期待本月最后一天。我不偷偷改写语义,而是在解析明细里标出该字段一年只有 7 个月能命中,让写的人自己决定要不要拆成多条。闰日同理,四年才触发一次,界面上直接显示下次执行时间,一眼看出问题。
五、时区
cron 表达式不带时区。同一个 0 9 * * * 在 UTC 服务器上跑出来是北京时间 17 点。我踩过一次:容器里 TZ 只设了环境变量、系统时区还是 UTC,结果每天 9 点的任务在下午 5 点执行。后来在镜像里同时设 TZ 和 /etc/localtime,并在解析器里显式要求传入时区才解决。
六、错误提示要指向字段
最初统一抛表达式格式错误,用户只能一个字段一个字段试。改成逐字段校验后,提示变成:小时超出范围 0-23;分钟无法识别取值 bad;日起点不能大于终点。配合把出错输入框标红,定位成本从猜降到看。
小结
解析阶段展开成有序数组,后续基于数组判断;时间推算按字段跳跃而不是逐分钟;加循环上限防住永不命中的表达式;时区显式传入;错误提示带字段名、取值范围和原始输入。自己用几百行足够;生产环境建议用成熟库,但至少要验证一遍这六个边界,因为它们的表现往往是任务跑了,但不是你要的那一次。
- 点赞
- 收藏
- 关注作者
评论(0)