DEBUG.WIKI
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
RackNerd;$21.99/年 · 1GB KVM
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

date-fns 加 10 天,结果却跳到了很远的日期

表单或 URL 里的天数通常是字符串。date-fns addDays 接收 number,字符串可能参与日期拼接;在输入边界拒绝空值并检查整数和结果日期。

同样是“加 10 天”,程序里写数字 10 正常,从表单读取的值却把日期推到很远的将来。结果仍是一个有效 Date,因此只检查 Invalid Date,可能发现不了问题。

先看传给 date-fns addDays() 的 amount 类型。表单控件和 URL 查询参数取得的 '10' 是字符串,不会因为内容看起来是数字就自动符合 number 参数。

日期加法遇到字符串,先发生了拼接

正式 date-fns 4.4.0 的 addDays 先取得原日期的日数,再与 amount 相加,交给 setDate。若原日期是 10 日,下面两种表达式有不同结果:

10 + 10;    // 20
10 + '10';  // '1010'

setDate 又会将超出当前月份的日数正规化到后续月份或年份,因此这次错误并不一定产生无效日期。最终年月还受起始日期和本地时区影响,不能把某个环境中的输出年份当成所有输入的固定结果。

addDays 的 amount 参数声明是 number。库中对 NaN 的检查,不等于替任意字符串做了严格数字转换;'10' 也不会因为通过某一步数值判断就变成 number。

在数据进入日期运算前转换

如果接口允许整数天数,可以在读取输入的边界统一校验。下面示例允许 number 或非空的数字字符串,拒绝其他类型,具体范围还应按业务合同收紧:

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

function addCalendarDays(date, rawAmount) {
  if (!isValid(date)) throw new RangeError('起始日期无效');
  if (typeof rawAmount !== 'number'
      && (typeof rawAmount !== 'string' || rawAmount.trim() === '')) {
    throw new TypeError('天数不能为空或使用其他类型');
  }
  const amount = Number(rawAmount);
  if (!Number.isSafeInteger(amount)) {
    throw new RangeError('天数必须是安全整数');
  }
  const result = addDays(date, amount);
  if (!isValid(result)) throw new RangeError('结果日期超出有效范围');
  return result;
}

这里的 date 约定为应用已经取得的 Date。若最初拿到的是日期文本,应先按接口规定的格式和时区解析,不在这个函数里猜测文本含义。

先拒绝空字符串,是为了避免 Number('') 或只含空白的文本变成 0;也不接受 null、布尔值或对象参与隐式转换。安全整数检查仍不是业务范围校验,例如预约最多允许多少天,需要另外限制。若接口规定只能用十进制整数文本,还应在转换前检查文本格式,因为 Number 也接受其他数字表示法。

不要用 parseInt() 掩盖带尾部垃圾的输入。把 '10days' 截成 10,看似解决了日期偏移,却可能让无效数据被悄悄接受。

整数天与固定时长不要混用

addDays 表示日历天运算。跨夏令时变化时,它不保证每次都经过恰好 24 小时;把它替换成时间戳加 86400000,会改变原来的日期规则,不能作为字符串问题的默认修复。

验收时检查数字、数字字符串、空白、非法文本、零和负整数,确认不合法输入被明确拒绝。还要用业务允许范围的边界值检查结果,避免修正字符串拼接后,又把超长日期区间当成正常请求。