Repository navigation
Commit 31260ca
fix(core): compareTo on an already-past date range compares with the period before it, and previousYear from 29 February stays in February (#30246)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwB727BBWue3wvnPrGZkDgyYY)_
Fixes #30197
## 维护者速读
-
改了什么:指标块开启「与上一周期对比」时,「昨天」「上周」「上月」「上季度」「去年」不再拿窗口和自己比,改为对比再往前一期(前天、上上周、上上月……);「最近
7/30/90 天」改为对比之前等长的那段日子;「与去年同期对比」在 2 月 29 日不再跑到 3 月。这些对比的文字统一为「较上期」。
- 为什么改:#30196 的验收备注发现的老问题:后向预设的对比涨跌永远是 0%,最近 N 天的对比窗口与本期重叠,闰日的去年同期错月。
- 风险与回滚:只改 `compare-to.ts`
一个文件的逻辑,不动规范、不动服务端。「今天」「本周/本月/本季度/今年」和三个前向预设的环比窗口逐字节不变;变化的只有上述后向预设,以及 2
月 29 日那天的去年同期。回滚即 revert。
- 先看:① `compare-to.ts` 的 `lastPeriodBefore`(规范没有「前两期」的宏,窗口是算出来的);② 同文件
`rollingPair` 的后向分支(N+1 天整体后移);③ `previousYear` 的日期夹取。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/core/src/utils/__tests__/dashboard-filters.presetWindows-29116.test.ts`,预期
16 个预设全部通过,每个预设的上一期都在本期开始前一天结束。
- 需要你做的:无,席位落地。
Clause-②: no
Metadata-protocol: none
## What changes
- **`@object-ui/core` `shiftFilterByCompareTo`, `kind:
'previousPeriod'`.**
- `{yesterday}` and `last_week_*` / `last_month_*` / `last_quarter_*` /
`last_year_*` were never substituted, so the `yesterday`, `last_week`,
`last_month`, `last_quarter` and `last_year` presets compared the window
with itself. Now `{yesterday}` becomes `{2_days_ago}`, and each `last_*`
token becomes the period before last.
- **How "two periods back" is spelled, measured.** `DATE_MACRO_TOKENS`
(`packages/spec/src/data/date-macros.zod.ts`) stops at `last_*`, and the
parameterised grammar (`{N_weeks_ago}`, `{N_months_ago}`) counts from
today, not from a period's first day, so no declared token names the
month before last. `lastPeriodBefore` therefore computes the day: it
resolves the `last_*` START through `resolveDateMacros` (the display
zone, like every other token), moves it back one week / month / quarter
/ year with UTC calendar arithmetic, and takes the day before that
`last_*` start as the end. The result is an ISO day, which
`resolveDateMacros` passes through. No token is invented.
- **Rolling windows.** `rollingPair` (was `forwardRollingPair`) now also
matches `{N_days_ago}` with `{today}`. That window is N + 1 days
(`last_7_days` at 2026-07-15 is 07-08..07-15, measured), and moves back
by N + 1: `{(2N+1)_days_ago}` … `{(N+1)_days_ago}`. It used to become
`{N_days_ago}` … `{yesterday}`, N days overlapping the current window.
The forward branch now writes `{1_day_ago}` instead of `{yesterday}`,
because `{yesterday}` is now itself moved; the resolved window is
unchanged.
- All substitutions still happen in ONE pass (`substituteTokens` now
takes a replacer), so `today` → `yesterday` is never carried on to
`2_days_ago`.
- **`kind: 'previousYear'`** built `new Date(y - 1, 1, 29)`, which is 1
March, so every window seen from 2028-02-29 was re-resolved at
2027-03-01 (`this_month` compared with March 2027). The day is clamped
to the shifted month's length, 28 February.
- **`compareToTrendLabelKey`**: a `{yesterday}` or `last_*` token, and a
backward rolling window, read `vsPreviousPeriod` (they read
`vsYesterday`, `vsLastWeek`, `vsLastMonth`, `vsLastQuarter`,
`vsLastYear`). No existing `dashboard.trend.*` key names "the month
before last", and `vsPreviousPeriod` ("vs previous period", 较上期) is
exact for each, so no new key, no locale or fallback change. The check
runs first, since `last_month_start` also contains `month_start`.
- **Docblock correction in `NEXT_TO_CURRENT_TOKENS`** (my #30196 text):
it said `next_week_*` / `next_year_*` have no `_end` in the macro
vocabulary. The spec declares `next_week_end` and `next_year_end`; it is
this package's resolver (`date-macros.ts`) that lacks them. The comment
now says so; the mapping is unchanged.
## Why
#30197, filed from #30196's Acceptance notes. Governing text: the
`compare-to.ts` docblock, "`kind: 'previousPeriod'` — the equal-length
window immediately before the current one", and `previousYear` = "same
week / month / quarter, one year earlier". No spec change, no new
`compareTo` kind, no executor change.
## Tests
- `dashboard-filters.presetWindows-29116.test.ts`: the compareTo case
pins all 16 presets at 2026-07-15 (both kinds and the label); the five
`last_*` / `yesterday` rows and the three `last_N_days` rows move, the
eight others keep #30196's values. New for every preset on all six
reference dates: the previous window ends the day before the current
window starts, with the same day count for a day, a week or a rolling
window, and from the 1st for a month, quarter or year. New for every
preset: `previousYear` from 2028-02-29 equals resolving the filter at
2027-02-28.
- `compare-to.test.ts`: yesterday, last week, last month (also from
2026-03-31, February then January), last quarter and last year across a
year boundary, `{7_days_ago}`…`{today}` and a `$between`
`{30_days_ago}`…`{today}`, a mixed
`last_month_start`…`current_month_end` window moved bound by bound, the
leap-day `previousYear` case, and the labels (a lone `{today}` still
reads `vsYesterday`).
- `ObjectMetricWidget.compareTo.pastPresets-30197.test.tsx` (rendered,
the registered `object-metric` block, the #30196 harness): `last_month`
(上月), `yesterday` and `last_7_days` each show `20%` against exactly the
expected previous window, under "vs previous period", and not the stale
label.
## Acceptance notes
- **CONTROL, measured outside the tests.** The `tsx` probe of 16 presets
× 8 reference dates × 2 kinds plus labels, `main` `ff12aed3` against
this branch. Unchanged: `this_quarter`, `this_year`, `next_quarter` in
full; `today`, `this_week`, `this_month`, `next_month`, `next_30_days`
in every row except `previousYear` at 2028-02-29 (the leap-day fix; e.g.
`this_month` 2027-03-01..03-31 → 2027-02-01..02-28). Changed as
intended: `yesterday`, `last_week`, `last_month`, `last_quarter`,
`last_year`, `last_7_days`, `last_30_days`, `last_90_days`
(previousPeriod rows and label, plus their 2028-02-29 previousYear row
where it moves).
- **Inline and executor now agree on the rolling windows.**
`last_30_days` at 2026-07-15: this branch 2026-05-15..06-14; the
executor (`shiftRange`, measured on #30140) 2026-05-15..06-14. On
calendar presets the two still differ by design: the executor counts
days, the inline path steps whole periods.
- Out of scope, unchanged: the client resolver lacks `next_week_end` /
`next_year_end`, which the spec declares (see the docblock correction
above).
## Verification
<details><summary>Suites, type-check, lint, ablations</summary>
- Under `os-verify-lock`: `pnpm --dir ui exec vitest run --maxWorkers=3
packages/core/` exit 0 (232 files, 4,321 passed, 27 skipped);
`packages/plugin-dashboard/` exit 0 (202 files, 1,928 passed, 6
skipped).
- `pnpm run type-check` in core and plugin-dashboard: exit 0.
- `npx eslint` on the touched files: 0 errors (warnings only,
`no-explicit-any`, as in the surrounding code).
- Ablations (`scripts/ablation-replace.mjs`, each red then restored to
HEAD):
- past tokens (`lastPeriodBefore` not called): 12 failed, covering the
five past-token pins, the five 29116 rows, and the rendered `last_month`
and `yesterday` rows;
- backward rolling (`ROLLING_DAYS_RE` limited to `from_now`): 6 failed
(the rolling pin, the label pin, the three `last_N_days` rows, the
rendered `last_7_days` row);
- leap day (clamp removed): 12 failed (the leap pin and the 11 presets
whose 2028-02-29 `previousYear` window moves);
- label (past-token check removed): 6 failed.
- Derived gates (`dispatch-gates --commands`, 35): see the report on
#30197.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; lint passes on the touched files.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: n/a.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English (the 维护者速读 section
excepted).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01WGufzijmF7yUSk5ga9jRkh
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 0f3ee58 commit 31260ca
5 files changed
Lines changed: 331 additions & 74 deletions
File tree
- .changeset
- ui/packages
- core/src/utils
- __tests__
- plugin-dashboard/src/__tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
133 | 133 | | |
134 | 134 | | |
135 | 135 | | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
136 | 187 | | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
137 | 196 | | |
138 | 197 | | |
139 | 198 | | |
| |||
224 | 283 | | |
225 | 284 | | |
226 | 285 | | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
227 | 300 | | |
228 | 301 | | |
229 | 302 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
74 | | - | |
75 | | - | |
76 | | - | |
77 | | - | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
78 | 85 | | |
79 | 86 | | |
80 | 87 | | |
81 | 88 | | |
82 | 89 | | |
83 | 90 | | |
84 | 91 | | |
85 | | - | |
| 92 | + | |
86 | 93 | | |
87 | | - | |
| 94 | + | |
88 | 95 | | |
89 | | - | |
| 96 | + | |
90 | 97 | | |
91 | | - | |
| 98 | + | |
92 | 99 | | |
93 | | - | |
94 | | - | |
95 | | - | |
96 | | - | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
97 | 104 | | |
98 | 105 | | |
99 | 106 | | |
| |||
233 | 240 | | |
234 | 241 | | |
235 | 242 | | |
236 | | - | |
237 | 243 | | |
238 | 244 | | |
239 | | - | |
240 | | - | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
241 | 248 | | |
242 | 249 | | |
243 | 250 | | |
| |||
246 | 253 | | |
247 | 254 | | |
248 | 255 | | |
249 | | - | |
250 | | - | |
| 256 | + | |
| 257 | + | |
251 | 258 | | |
252 | 259 | | |
253 | | - | |
| 260 | + | |
254 | 261 | | |
255 | 262 | | |
256 | 263 | | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
257 | 271 | | |
258 | 272 | | |
259 | 273 | | |
| |||
0 commit comments