|
|
6757644756
|
fix(判据): 农历推进的起点必须在未来 —— 否则 advanceToFuture 跳过过期月份必假红
## 现象
部署被测试闸拦下(这正是纪律该做的事):`TestAdvanceRecurrenceLunar` 报
「间隔 58 天不像一个农历月」。已确认与本次 SSE 改动无关
(把我的改动全部 stash 后它同样红)。
## 根因(实测复算,不是推测)
`AdvanceRecurrence` 走 `advanceToFuture`,职责是「推进到**未来**」:
已经过去的农历月会被跳过(`if cur.After(now) { return }` 那个循环)。
原起点写死 `2026-09-03`,而今天已是 10-02 ⇒ 那个农历月(10-02)已过去
⇒ 循环再推一个月。探针实测:
起点 2026-09-03 ⇒ 落点 2026-10-31 间隔 58 天 ← 旧起点,今天跑必红
起点 2026-10-03 ⇒ 落点 2026-11-01 间隔 29 天 ← 明天,正确
农历库本身是对的:直接调 `AddMonths(1).ToSolar()` 得到的落点恰好 29 天。
所以**不是代码缺陷,是判据的期望依赖了「今天离起点不到一个月」**这个
随日期漂移的前提。
## 修法
起点改为「明天」起算(不写死具体日期 ⇒ 明年跑也成立),
农历日的期望也跟着起点走。
★ 不用 `time.Now().AddDate(0,1,0)` 那种相对写法:农历月 29/30 天不定,
起点落在月末时下一个同农历日可能被夹(commit 3459605 记的同族假红)。
明天起算同时满足「确保在未来」与「落点就是下一个农历月」。
## ★★ 修判据时差点削弱了它(变异验证抓出来的)
把起点改成明天后判据绿了,但我立刻做变异验证:
变异:advanceToFuture 不跳过已过期月份 ⇒ TestAdvanceRecurrenceLunar **全绿**
因为起点在未来,第一个落点本来就在未来,循环与单步没有区别 ——
**我修好了假红,却顺手删掉了「跳过过期」这个真行为的判别力。**
补了一格:用**已过期 70 天**的起点单独钉它,落点必须在未来,
否则「每次扫描都重复触发同一封提醒」。变异重测 ⇒ 红 1 格。
这是同一天内第二次判据自身缺陷(第一次是 SSE 那格只查文本不查控制流)。
**改判据后必须变异验证判别力还在**,否则就是用改测试掩盖问题。
|
2026-10-02 10:57:59 +08:00 |
|
|
|
3459605dc0
|
修复: TestStaleLunarRecurringDoesNotFlood 是**日期相关的假红** —— 它要求一个不存在的日期
这条红不是"既有代码缺陷",是**测试的期望写错了**。`go test ./...` 现已 **rc=0 全绿**。
## 形状:断言要求「农历日原样保持」,而目标月可能根本没那一天
if d := lunar.FromSolar(after.EventTime.In(time.Local)); d.Day != lunar.FromSolar(e.EventTime).Day
起点是 `time.Now().AddDate(-1,0,0)`,推进落到哪个农历月**随日期浮动**。
农历月 29/30 天不定 ⇒ 落到 29 天的月份时,"30 日"**不存在**,夹到 29 是
`ToSolar` 明确设计的行为(它连 `clamped` 都返回了)。旧断言却要求 30 ⇒ 必红。
实测(2026-09-21,起点 = 农历七月三十):
落点 = 2026 年农历**八月廿九**,而该月只有 **29** 天
⇒ 正确结果就是 29;旧断言要 30 ⇒ 无论代码对不对都红。
**所以它是一条"日期相关"的假红**:每年那几天必红,与 `git bisect` 的结果无关。
pi 独立复核过它在**上次部署时的 HEAD `e8b260d`** 上同样 FAIL —— 与我一致。
## 我不是靠"看不见"修的:先排除了另外两种解释
- **不是时区/基准不一致**(我第一版猜这个):探针打印了两个基准,
`e.EventTime` 未转本地 / 转本地,**农历日都是 30**,而左侧是 29 ⇒ 基准不是原因。
- 也不是推进逻辑多走了一步:`AddMonths(1)` 对 7月30 得 8月30(`Date` 只加月份),
是**后面 `ToSolar` 往 29 天的月里落时才夹**。
## 修法:期望取 `min(原日, 目标月天数)`
days, err := lunar.DaysInMonth(got.Year, got.Month) // 读不到就 Fatal,不静默
if want > days { want = days }
**没有放松真正的约束**:落到**长月**却少一天,仍然红。
变异验证:把 `RecurLunarMonthly` 的 `AddMonths(1)` 改成 `AddMonths(2)` ⇒
`农历日从 30 变成 29(目标月 30 天,期望 30)` **红**(在最终代码上重跑确认)。
## 由此**新发现**一条真缺陷(本次**不修**,因为要改产品语义)
顺着"夹取"往下量,发现 `addSolarMonthClamped` / 农历 `AddMonths` 的夹取是**粘的**:
公历 每月31日: 1-31 → 2-28 → 3-28 → 4-28 … (3 月有 31 天却停在 28)
农历 每月30日: 7月30 → 8月29 → 9月29 → 10月29 …(9 月有 30 天却停在 29)
一旦被夹过一次,**此后再也回不到原始日**。而 `calendar.go:401-405` 的注释恰恰把
"一次溢出永久改变规则"称作**要避免的** bug —— **注释说的和实现对不上**。
根因:落点被写回 `event_time`,而**没有一列保存原始锚点日**(`calendar_events` 无此列)。
要真修得加锚点列 + 迁移,改的是**产品语义**,不是本次部署能顺带做的 ⇒ 已如实报给 pi。
|
2026-09-21 05:06:53 +08:00 |
|