Skip to content

Read the GA4 day on the property's clock, not on UTC - #3

Merged
TadelUnso merged 1 commit into
mainfrom
fix/ga4-property-timezone
Aug 7, 2026
Merged

Read the GA4 day on the property's clock, not on UTC#3
TadelUnso merged 1 commit into
mainfrom
fix/ga4-property-timezone

Conversation

@TadelUnso

Copy link
Copy Markdown
Owner

The defect

GA4 dates every row by the reporting timezone configured on the property. This property is on Etc/GMT-3 — UTC+3 — which the live API confirms in metadata.timeZone. GoogleAnalyticsInstallsService.supplement worked out "today" with a UTC calendar instead.

Between 21:00 UTC and midnight the property has already begun a day UTC has not reached. GA4 files those events under tomorrow's label, the UTC-based filter does not match it, and the panel shows the previous day's finished count as though it were today's movement — while dating the figures a day behind.

West of UTC it is worse and lasts longer. A Los Angeles property spends every evening presenting a day that is still filling in as if it were settled.

The fix

metadata.timeZone already comes back with every runReport, so nothing new is fetched. The client returns it alongside the rows and the supplement reads the current day on that clock.

Row dates stay UTC-anchored labels — that is the one calendar the whole panel dates figures in (PlayInstallsReport, Fmt.day). Only "which label is today" ever needed the property's zone.

endDate becomes GA4's own today keyword rather than a date computed locally, since a UTC-derived end can cut the property's current day short. That leaves the end parameter meaningless to callers, so it is gone from the protocol:

func firstOpens(since start: Date) async throws -> GoogleAnalyticsFirstOpens

startDate stays UTC-formatted on purpose, and now says why in a comment: it is Play's newest report day plus one, and Play's report days are parsed as UTC labels.

A response with no readable timezone falls back to UTC rather than throwing — the counts are still right, only the today/not-today split degrades to the old behaviour.

Verification

Both new tests were confirmed to fail against the previous logic before the fix landed:

UTC+3 property at 22:00 UTC:       today → 2 (expected 5),  asOf → 6 Aug (expected 7 Aug)
Los Angeles property at 03:00 UTC: today → 7 (expected 4),  asOf → 6 Aug (expected 5 Aug)
  • make test — 144 tests, all passing (140 before, +4)
  • swift build -c release — clean
  • Both API-facing changes confirmed against the live GA4 API beforehand: endDate: "today" returns HTTP 200, and metadata.timeZone returns Etc/GMT-3 for this property.

Not included

A README note about the property's reporting timezone. #2 already edits README.md; a second independent edit across two open PRs buys a merge conflict and no behaviour. Worth adding once both land.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EZ2Y7A3ZjdPCNSRzWDYVha

GA4 dates every row by the reporting timezone configured on the property, and
this one is on UTC+3. The supplement worked out "today" with a UTC calendar,
so for the three hours between 21:00 UTC and midnight the property had already
started a day that UTC had not reached — the panel then showed the previous
day's finished count as though it were today's movement, and dated the figures
a day behind. West of UTC the error runs the other way and lasts longer: a Los
Angeles property would spend every evening showing a day that is still filling
in as if it were settled.

The response already carries the answer. `metadata.timeZone` comes back with
every runReport, so the client now hands it to the caller alongside the rows,
and the supplement reads the current day on that clock. Row dates stay
UTC-anchored labels, which is the one calendar the whole panel dates figures
in; only the question of which label is today needed the property's zone.

`endDate` is now GA4's own `today` keyword rather than a date computed here,
for the same reason: a UTC-derived end date can cut the property's current day
short. That also makes the end parameter meaningless to a caller, so it is
gone from the protocol.

A response with no readable timezone falls back to UTC rather than failing.
The counts are still correct; only the today/not-today split degrades to the
old behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ2Y7A3ZjdPCNSRzWDYVha
@TadelUnso
TadelUnso merged commit 16f4cba into main Aug 7, 2026
2 of 4 checks passed
@TadelUnso
TadelUnso deleted the fix/ga4-property-timezone branch August 7, 2026 02:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant