DEBUG.WIKI
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →
雨云;给项目一台云服务器
RackNerd;$21.99/年 · 1GB KVM

date-fns eachHourOfInterval 传入 0.5 后卡住

date-fns 4.4.0 的 eachHourOfInterval 用小数小时步长可能无法推进游标。限制整数步长和区间规模,区分固定三十分钟与本地时区刻度,并检查同步循环边界。

调用 eachHourOfInterval() 后进程不再返回,区间本身却只有几小时。如果 options 中写了 step: 0.5,应先停用这条输入路径。这个参数不能用来生成半小时间隔。

date-fns 4.4.0 的正式实现按本地小时调用 Date.setHours(date.getHours() + step)。小于一的正小数被日期设置操作舍弃,下一轮仍停在同一小时,循环继续把同一个时刻加入数组。负小数取绝对值后也会碰到这一点。增加请求等待时间无法使游标前进。

把小时步长限制为非零整数

如果业务需要按整数小时枚举,先校验参数,再进入库函数。下面示例只允许非零安全整数,并限制可接受的区间长度;上限应按应用的数据规模另行确定:

import { eachHourOfInterval, isValid } from 'date-fns';

function listWholeHours(start, end, step) {
  if (!(start instanceof Date) || !(end instanceof Date)
      || !isValid(start) || !isValid(end)) {
    throw new RangeError('区间日期无效');
  }
  if (!Number.isSafeInteger(step) || step === 0) {
    throw new RangeError('小时步长必须是非零整数');
  }
  if (Math.abs(end.getTime() - start.getTime()) > 31 * 86400000) {
    throw new RangeError('区间超过此接口允许的范围');
  }
  return eachHourOfInterval({ start, end }, { step });
}

31 天只是此示例接口的限制,不是 date-fns 的上限;它也不是在定义“一个月”。业务应同时约束输入日期、输出规模及可用资源,不能只防住 0.5 后允许任意长区间一次性生成数组。

负整数在该函数里有反转结果顺序的含义。如果接口只允许正向输出,进一步要求 step 大于零,不要先取绝对值后悄悄改变调用方的排序要求。零步长在库中可返回空数组,这也不等于业务应把无效步长当成正常的空结果。

真正的半小时,要先明确时间规则

固定经过三十分钟与本地钟面上每半小时,不是同一份约定。跨夏令时可能存在跳过或重复的钟面时间;eachHourOfInterval 还会把起点归到小时起点,不能只改一个步长就保留任意分钟起点。

若接口约定的是固定经过 1800000 毫秒,应使用适合固定时长的枚举方式,并明确是否包含终点、最大项数和输出时区。若约定的是营业时间表中的钟面刻度,应按对应时区处理不存在或重复的本地时刻。不要在未确定这些规则时,把 step 从 0.5 改成 1 就算恢复了业务。

同类的同步循环也见于 Luxon 负时长切分,但两者的游标错误不同:本题是小数小时被舍弃后不前进,Luxon 是负 Duration 让游标持续后退,不能套用同一个版本修复结论。

卡住以后,同线程超时不会接管循环

Promise.race() 或外层 setTimeout 都不能打断已经占住 JavaScript 线程的同步 while。拒绝危险参数必须发生在调用前;不要在正式服务里反复运行 0.5 来观察超时。

截至 2026 年 10 月 26 日,正式 4.4.0 仍有这条小时设置路径。复查时让入口直接拒绝小数、NaN、无穷值和零,合法样本覆盖正反区间、整数步长与夏令时边界,确认排序、起点归整及终点规则符合原接口。