fix(gsc): make convenience date ranges span the days they name - #173
Open
mlava wants to merge 1 commit into
Open
fix(gsc): make convenience date ranges span the days they name#173mlava wants to merge 1 commit into
mlava wants to merge 1 commit into
Conversation
GSC treats startDate and endDate as inclusive, but subtractRange took a full N days off the lagged end, so every convenience range covered one day too many: last_7_days spanned 8 days, last_28_days spanned 29, and the calendar-month ranges spanned n months plus a day. Reported clicks and impressions were correspondingly inflated (~3.6% on the 28-day default) and did not reconcile with the same range in the GSC UI. Subtract N-1 days for the day ranges, and start the month ranges the day after the month subtraction. sixteenMonthFloor keeps subtracting whole months — it is a data-availability clamp, not a range endpoint, so it does not take the same adjustment. Period-over-period comparison is unaffected: previousPeriod derives its length from the resolved window, so it tracked the old behaviour and tracks the new one. Tests now assert the inclusive day count rather than only hard-coded dates, so the off-by-one cannot return under a passing test, and last_7_days gains coverage it did not have. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mlava
added a commit
to mlava/open-seo
that referenced
this pull request
Jul 30, 2026
HasimEmre
pushed a commit
to HasimEmre/open-seo
that referenced
this pull request
Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Every GSC convenience date range currently covers one day more than its name promises.
last_28_daysasks Google for 29 days of data,last_7_daysfor 8, and the calendar-month ranges for n months plus a day.The cause is an inclusive/exclusive mismatch. Google's Search Analytics API documents both bounds as inclusive — for
startDateandendDatealike, "This value is included in the range." ButsubtractRangeinsrc/server/features/gsc/searchAnalytics.tssubtracted a full N days from the lagged end date, which produces an N+1 day window once both endpoints are counted.Why this matters
Absolute metrics are inflated by the extra day's traffic:
last_7_dayslast_28_days(the default)last_3_monthslast_6/12/16_monthslast_28_daysis the default for both the Search Performance page and theget_search_console_performanceMCP tool, so the 3.6% figure is the one most users see.The practical failure is reconciliation. A user comparing OpenSEO's "Last 28 days" clicks against the identically-labelled range in the Google Search Console UI gets two different numbers, with no visible reason for the gap. For a tool whose value rests on being a faithful view of first-party data, silently disagreeing with the source is a worse failure than the size of the discrepancy suggests. The same applies to anything downstream that divides by the window — per-day averages and the striking-distance calculations are all computed over a denominator that is one day too wide.
What is not affected
Period-over-period comparison is correct, before and after.
previousPeriodinsearchPerformanceReport.tsderives its length fromendDate - startDaterather than from the range name, so the prior window was also 29 days and abutted the current one with no gap or overlap. The deltas were apples-to-apples under the bug and remain so under the fix. Only absolute totals move.The 16-month floor is unchanged.
sixteenMonthFloorstill subtracts whole calendar months. It is a data-availability clamp expressing how far back GSC retains data, not a range endpoint, so it does not take the inclusive-bounds adjustment. This is why the fix adds a separatenextUtcDayhelper instead of changingsubtractUtcMonths, which both call sites share.The change
N - 1days (-6and-27).nextUtcDayhelper. Short-month clamping insubtractUtcMonthsis preserved: for an end date of 31 May, the 3-month window is now 1 Mar – 31 May (three whole months) rather than 28 Feb – 31 May.Resolved windows for
TODAY = 2026-05-28, end date2026-05-25after the existing 3-day data-lag offset:last_7_days2026-05-18(8 days)2026-05-19(7 days)last_28_days2026-04-27(29 days)2026-04-28(28 days)last_3_months2026-02-282026-03-01Tests
The existing suite asserted the bug outright — a case titled "computes a 28-day window from the lagged end" expected
2026-04-27→2026-05-25, which is 29 inclusive days. A hard-coded date pair cannot tell you whether the window is the length it claims to be, which is how this survived.The day-range cases now assert the inclusive day count through a small
inclusiveDayshelper alongside the dates, so the off-by-one cannot reappear under a green test.last_7_daysgains coverage it did not previously have.The two 16-month clamp tests pass unchanged, though one now exercises the clamp slightly harder: the raw start it produces has moved a day later and lands exactly on the floor rather than below it.
Verification
searchAnalytics,searchPerformanceReport,GscService,gscClient,search-console-tools.prettier --checkandoxlint --type-awareclean on the changed files.Branched from
upstream/mainand verified there rather than on a fork base. Two pre-existing failures innpm run ci:checkare unrelated to this change and reproduce identically on a cleanupstream/maincheckout with the change stashed:tsc --noEmiterrors inalchemy.run.tsfrom unresolved@distilled.cloud/cloudflare/*modules, and a largeknipunused-file count. Neither touchessrc/server/features/gsc/.Note for reviewers
Expect reported clicks and impressions to tick down slightly after this lands. That is the fix taking effect, not a regression — the numbers were previously counting a day the label did not include.