Repository navigation
fix(service-analytics): a preset dateRange on a date field counts the reference zone's calendar days, east of UTC too - #27271
Conversation
…he zone's calendar days Claude-Session: https://claude.ai/code/session_01Utp6Caqg6E9ggEvoUcYsbq Co-authored-by: Claude <noreply@anthropic.com>
…te field Claude-Session: https://claude.ai/code/session_01Utp6Caqg6E9ggEvoUcYsbq Co-authored-by: Claude <noreply@anthropic.com>
…date field Claude-Session: https://claude.ai/code/session_01Utp6Caqg6E9ggEvoUcYsbq Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 2 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 27036e4d9f83feea3bb82c41561689a64beb9ef4 && git checkout 27036e4d9f83feea3bb82c41561689a64beb9ef4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 39ea1d67e5c272b41871cfeada3630971d10b81b c9135bdbf62f41190fde5d3f7a99f2f1bfeb76cd && git checkout -B drift-repro 39ea1d67e5c272b41871cfeada3630971d10b81b && git merge --no-ff c9135bdbf62f41190fde5d3f7a99f2f1bfeb76cd
node scripts/docs-audit/affected-docs.mjs --json 39ea1d67e5c272b41871cfeada3630971d10b81b
|
席位核对 — ACCEPT ·
|
Requested by Bill · project thread
Fixes #27172
Clause-②: no
Metadata-protocol: none
维护者速读
date类型字段上时,按参考时区的日历日计数;datetime字段仍按时刻比较。两条查询策略、钻取范围、草稿预览共用同一处取界。packages/services/service-analytics/src/dataset-executor.ts(新增dateRangeWindowBounds,复用既有inclusiveCalendarDayWindow)、strategies/native-sql-strategy.ts与strategies/objectql-strategy.ts的预设分支、drill-window.ts。pnpm --filter @objectstack/service-analytics exec vitest run src/__tests__/dataset-drill-window.test.ts,预期 26 条全过,其中上海 date 字段 this_quarter 两条策略都只列 7/1–9/30 的记录。What changes
A preset
dateRange(this_quarter,last_30_days, …) resolves to the reference zone's midnight instants. On adatecolumn those instants were compared as they are, and the column reads an instant by its leading UTC day (temporalStorageForm), so east of UTC the window slid one day early: inAsia/Shanghai,this_quartercounted 2026-06-30 and dropped 2026-09-30, on both strategies (measured red below). West of UTC the leading UTC day happens to be the zone's own day.The triage rule (6465987107): a
datefield compares calendar days in the reference zone (ADR-0053), adatetimecompares instants, and the executor's existing whole-day rule is used, not a new resolver.inclusiveCalendarDayWindowinpackages/services/service-analytics/src/dataset-executor.tsalready projects a lowered window onto the calendar days it covers in the reference zone (throughbucketDateKey, the exact inverse of thezonedDateStartToUtcMsthe preset resolver renders bounds with);compareToshifts exactly that projection. No new resolver and no new calendar arithmetic.dateRangeWindowBounds(window, timezone, dateColumn)beside it: a resolved preset on adatecolumn becomes the inclusive day pair{ $gte: first, $lte: last }; on any other column it stays{ $gte, $lt }on the instants; an explicit[a, b]stays{ $gte, $lte }bound for bound. It is called byNativeSQLStrategy(compile),ObjectQLStrategy(execute and the SQL echo),drillWindowBounds(so drill and count stay on one lowering, as fix(service-analytics): a windowed dataset answer reports its window in drillRanges #27169 set up) and the draft preview'sevaluateAnalyticsQueryOverRows.date. NewdeclaredDateColumn(ctx, target)instrategies/filter-normalizer.ts, asked of the samedeclaredFieldTypehook and the same storage target asdeclaredDatetimeLowering. A column whose type the host cannot name answersfalseand keeps the instants it was filtered by before.[gte, lt)drill bound for adatewindow now reads2026-07-01..2026-10-01in Shanghai, the same days the count uses.Bounded in-place fix, declared: the draft preview face (
preview-evaluator.ts, inside the claim's file surface) had the same defect, measured red on4d3a077f(Shanghai counted Jun 30 and dropped Sep 30); it takes the same helper, pinned by its own test.Tests
dataset-drill-window.test.ts: the real-engine case "stage × this_quarter on the date field, Shanghai" now asserts the ids (won: d2 d3 d6 d8,lost: d10 d4) instead of parity only, on both strategies; added Los Angeles ×date(west, unchanged days) and Los Angeles ×datetime(instants: d2 out, d5 in), each also checking the drill lists exactly what the row counted. A unit case pins the engine bounds ({ $gte: '2026-07-01', $lte: '2026-09-30' }) and the drill range for Shanghai and Los Angeles.preview-preset-date-field.test.ts: the draft preview on adatefield selects Jul 1 through Sep 30 in Shanghai, Los Angeles and UTC; adatetimefield keeps the zone's instants.Acceptance notes
driver-memory's cube face (MemoryAnalyticsService) is outside this claim's file surface and has the mirror-image defect: measured in-process on this branch,this_quarteron adatefield (declared or not) answers Jul 1 and Sep 30 in Shanghai (right, by text-prefix ordering of a bare day against the instant) but Sep 30 and Oct 1 in Los Angeles (drops Jul 1, counts Oct 1). Reported to the seat for its own card; not touched here.[a, b]window keeps the engine's UTC calendar-day reading on adatetimecolumn (ADR-0053 D-D), unchanged and still pinned.Verification
All at head
c9135bdbon this branch, baseorigin/main4d3a077f.Red first, on base sources (fix restored to origin/main 4d3a077, tests from this branch)
Green at c9135bd
Ablation, once (scripts/ablation-replace.mjs; restored to the HEAD blob 41872f43db26, git diff HEAD empty)
Mutation in
dateRangeWindowBounds:if (!dateColumn) return { $gte: window.start, $lt: window.end };becameif (dateColumn || !dateColumn) …(the date arm never taken). Anchor 1 to 0, blob 41872f43db26 to 6b76ccd50391.The Los Angeles date and datetime cases stay green under the mutation, as expected: west of UTC the leading UTC day was already the zone's day.
Derived gates (dispatch-gates --commands, 60 families) at c9135bd
check:dual-build-cjs-loadsexit 3 is PREREQUISITE NOT MET (it reads every package's dist; only this package's dependency closure was built): NOT MEASURED, CI's.check-plugin-teardown-shape --self-testfirst exited 3 on the shallow clone (its pinned fixture commit was unreachable); after fetching that commit it exits 0, as recorded. Reconciliation,dispatch-gates --ran: 60 derived, 59 run, 1 NOT-MEASURED (derived from the recorded exit 3), 0 UNRUN.Generated by Claude Code