Skip to content

fix(core): compareTo on an already-past date range compares with the period before it, and previousYear from 29 February stays in February - #30246

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-6kk1x6
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-6kk1x6

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread

Fixes #30197

维护者速读

  • 改了什么:指标块开启「与上一周期对比」时,「昨天」「上周」「上月」「上季度」「去年」不再拿窗口和自己比,改为对比再往前一期(前天、上上周、上上月……);「最近 7/30/90 天」改为对比之前等长的那段日子;「与去年同期对比」在 2 月 29 日不再跑到 3 月。这些对比的文字统一为「较上期」。
  • 为什么改:fix(core): compareTo on a forward date range (next month, next quarter, next 30 days) compares with the period just before it, not with itself #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 fix(core): compareTo on a forward date range (next month, next quarter, next 30 days) compares with the period just before it, not with itself #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 fix(core): compareTo on a forward date range (next month, next quarter, next 30 days) compares with the period just before it, not with itself #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 fix(core): compareTo on a forward date range (next month, next quarter, next 30 days) compares with the period just before it, not with itself #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 Dashboard compareTo on a forward date preset (下月, 下季度, 未来 30 天) compares the window with itself #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

Suites, type-check, lint, ablations
  • 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 Inline compareTo previousPeriod: backward presets (上月, 上季度, 昨天, 上周, 去年) compare a window with itself, last_N_days compares N+1 days with N #30197.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; lint passes on the touched files.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: n/a.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the 维护者速读 section excepted).

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGufzijmF7yUSk5ga9jRkh

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

1 participant