DEBUG.WIKI
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →
雨云;给项目一台云服务器
晚安云;香港 CN2 ¥15/月

cron 预览夏令时结束日,下一次时间为什么早于起点

cron4.4.0在纽约重复小时的未来预览中可能返回过去的候选。检查UTC递增及当地钟表字段;递增防护不替代完整DST调度规则。

用 npm 的 cron 包预览未来执行时间,传入的起点是纽约时间 2026 年 11 月 1 日第二次出现的 01:00,结果却回到了第一次 01:30。在 UTC 上看,起点为 06:00Z,下一次返回 05:30Z,比起点还早。

这一具体问题出现在 cron 4.4.0 配合 Luxon 3.7.2 的 CronTime.getNextDateFrom() 预览路径。它不是 Linux crontab 的解析问题,也不能靠给业务进程固定加减一小时处理。

同一个 01:30,对应两个真实时刻

2026 年 11 月 1 日,America/New_York 从夏令时退回标准时。当地 01:30 可以对应 05:30Z,也可以对应 06:30Z。对于每天 01:30 的表达式,在 06:00Z 之后应继续找后一种时刻,而不是把已经过去的前一种返回。

下面仅构造预览输入,不创建定时任务,也不执行回调:

import { CronTime } from 'cron';

const zone = 'America/New_York';
const schedule = new CronTime('30 1 * * *', zone);
const start = new Date('2026-11-01T06:00:00.000Z');
const next = schedule.getNextDateFrom(start, zone);

console.log(start.toISOString(), next.toUTC().toISO());

这里用的是名为 cron 的 npm 包。另一个名为 node-cron 的包有不同接口,不能把两者的版本和修复记录混用。

4.4.0 的计算先按钟表字段寻找匹配,再用目标时区重建日期。重建重复小时存在歧义,后面的偏移调整也没有为所有返回路径统一保证结果晚于 start。预览未来日期时,这种选择还可能受计算当下的时区偏移影响;仅在夏令时切换以后再试一次,不能代替切换前的预览验收。

先拒绝逆序结果,不把防护当作完整调度器

截至 2026 年 10 月 25 日,正式 cron 4.4.0 仍保留这条实现。若页面或接口要展示一串未来执行时间,至少应拒绝无效日期和不向前推进的结果:

function checkedNext(schedule, start, zone) {
  const next = schedule.getNextDateFrom(start, zone);
  const startMillis = start instanceof Date ? start.getTime() : start.toMillis();
  if (!next.isValid || !Number.isFinite(startMillis)) {
    throw new Error('Invalid schedule preview input or result');
  }
  if (next.toMillis() <= startMillis) {
    throw new Error('Schedule preview did not advance');
  }
  return next;
}

这个入口适用于页面预览的错误处理:停止展示错误时间,保留表达式、时区、起点和依赖版本供排查。不要捕获后返回 start,也不要不断加一毫秒重试,把计算异常变成循环。

递增检查只能挡住倒退。夏令时附近也可能出现“结果晚于起点,但不符合表达式钟表时刻”的额外候选,因此还要核对每个候选在目标地区显示的年月日、小时、分钟和秒。上面的函数没有实现这个匹配检查,不能作为所有 DST 情况的修复,更不能直接替代生产调度循环。

需要明确一天执行一次,先定重复小时的规则

对于固定的一天和固定的当地时刻,Luxon 3.7.2 的 getPossibleOffsets 可以枚举重复小时的两个候选。应用可在检查日期有效且仍等于指定钟表字段后,按 UTC 排序,再依照业务约定选一次或两次。它不是任意 cron 表达式的替代解析器,也不能自行决定结算、账单或通知该执行几次。

若业务原本遵循当地时间,直接改为固定 UTC 会改变全年计划。若业务本来只要求固定 UTC,应明确修改其计划约定,而不是把它当作透明修复。运行在 Kubernetes 中的计划,还要区分控制器的 timeZone 与 Pod 内 TZ,设置业务容器时区不会改变另一层调度器。

验收应覆盖切换前预览、重复小时的前半段和后半段、切换后的普通日期,并逐项检查 UTC 递增与当地字段。预览通过不能证明实际任务没有重复或漏跑;生产调度还需独立验证回调记录和业务幂等性。