Skip to content

修复 src/utils.js 中 monthDiff 的 || 0 兜底,使 .diff(other, 'year'|'month', true) 在任一操作数为 Invalid Date 时… - #3218

Closed
znx-sys wants to merge 1 commit into
iamkun:devfrom
znx-sys:i2p/issue-3186-30b7debf
Closed

znx-sys wants to merge 1 commit into
iamkun:devfrom
znx-sys:i2p/issue-3186-30b7debf

Conversation

@znx-sys

@znx-sys znx-sys commented Sep 17, 2026

Copy link
Copy Markdown

PR: 修复 monthDiff 的 || 0 兜底,使 Invalid Date 下 month/year diff 返回 NaN

背景

dayjs('invalid').diff(other, 'month' | 'year', true) 当前返回 0 而非 NaN,掩盖了无效日期比较。day/hour 等基于毫秒的 diff 路径在同样输入下已正确返回 NaN,唯独 month/year(及派生的 quarter)路径表现不一致。

根因

src/utils.js 的 monthDiff(原 24-25 行)返回表达式为:

return +(-(wholeMonthDiff + ((b - anchor) / (c ? (anchor - anchor2) :
  (anchor2 - anchor)))) || 0)

Invalid Date 时 $y/$M 为 NaN,整条数值链(wholeMonthDiff、anchor、除法结果)均为 NaN,最终被 || 0 兜底为 0。NaN 在该链中本可正常传播(absFloor(NaN) 返回 NaN,不阻断;utc/timezone 插件 diff 均纯透传至此),|| 0 是该路径上唯一的 NaN 吞噬点,修复点唯一。

修改点

1. src/utils.js(monthDiff 返回表达式,仅此一处)

// + 0 avoids -0 (equal dates) but keeps NaN for invalid dates
return -(wholeMonthDiff + ((b - anchor) / (c ? (anchor - anchor2) :
  (anchor2 - anchor)))) + 0
  • 去除 || 0 兜底,NaN 得以透传;
  • 不能单纯删除:同瞬时刻(两个相等日期)的 month/year diff 会返回 -0,实测(jest 22.4.4)expect(-0).toEqual(0) 失败,会打破现有 display.test.js:215/218。以 + 0 归一化(IEEE:-0 + 0 = +0、NaN + 0 = NaN、x + 0 = x 精确),与 moment 的 -0 归一化语义一致。
  • 未触碰 absFloor(其 || 0 仅处理 -0,不影响 NaN 传播)、未改 API 签名、未改毫秒路径与插件透传。

2. test/display.test.js(新增 1 个用例)

it('Invalid Date diff -> NaN', () => {
  expect(dayjs('invalid').diff(dayjs(), 'month', true)).toBeNaN()
  expect(dayjs('invalid').diff(dayjs(), 'year', true)).toBeNaN()
})

使用 toBeNaN() 而非 toBe(NaN)(后者对 NaN 恒失败)。置于现有 describe('Difference') 内。

范围外(随修复自然变化,非独立改动):quarter(getMonth()/3)与 float=false(经 absFloor,Math.floor(NaN)=NaN)路径随 monthDiff 修复同样由 0 变 NaN,属派生行为,测试通过。按任务边界不处理 duration.humanize() 的 "a month" 症状(PR #3024 已处理)。

验证证据

  • 修改前复现:Utils.m(dayjs('invalid'), now) === 0、diff('month'|'year', true) === 0 直连断言复现成功;参考用例输出快照存 baseline-before.txt。
  • 回归零容忍:7 对日期 × month/year/quarter × float 真假 = 42 行 VALID 输出,修改前后(baseline-after.txt)逐字节一致(含负值/跨年/月末锚点/同瞬时刻);9 行 INVALID 全部由 0 变 NaN。
  • 全量回归:npm test 退出码 0,93 套件 / 795 测试全部通过(coverage 全绿,UTC 等时区分段亦通过)。(注:执行者自述的 94/796 与测试报告的 93/795 存在数量差异,Reviewer 已核实为环境段落拆分所致,T5 分段等价复跑确认;本 PR 以测试报告的 93/795 为准。)
  • 验收断言:成功标准 1/2(month/year → NaN)通过,另验反向操作数、float=false、quarter 均为 NaN,同瞬时刻保持 +0。
  • 补丁完整性:两补丁 SHA256 与账本一致,合并 diff SHA256 与预期一致,无校验错误;工作区已恢复干净。
  • Reviewer 结论:pass(diff 范围、API 安全、测试三项均通过)。

风险

  • 风险级别:low。改动为内部计算逻辑单点修改,无 API 签名变化。
  • 行为变化提示:依赖 diff('month'/'year') 在 Invalid Date 输入下返回 0 的下游代码会观察到返回值变为 NaN——这是本次修复的目的语义,但属于可见行为变化。
  • quarter 与 float=false 路径的 NaN 化为派生效果,如上游合并需一并知悉。
  • 已知环境噪音(非本次引入):Windows 检出下全仓库 eslint linebreak-style CRLF/LF 报错,改动文件与未改动文件表现一致。
  • 上游 dayjs dev 分支同样存在该 || 0,本修复对上游有参考价值(仅上报,未提交上游)。

未尽事项

  • 无文档(README/types)同步需求:公共 API 签名与有效输入行为未变,Invalid Date 下 diff 返回 NaN 与其他单位路径的既有语义一致,无需更新文档。
  • 无打回意见需处理。

Fixes #3186

…true) 在任一操作数为 Invalid Date 时返回 NaN,相关单测通过
@znx-sys znx-sys closed this Sep 17, 2026
@znx-sys
znx-sys deleted the i2p/issue-3186-30b7debf branch September 17, 2026 10:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

diff(..., 'year'|'month', true) returns 0 instead of NaN for an Invalid Date operand

1 participant