Skip to content

Add timezone_aware option for continuous aggregates#132

Open
jonatas wants to merge 2 commits intotimescale:masterfrom
jonatas:timezone-helpers
Open

Add timezone_aware option for continuous aggregates#132
jonatas wants to merge 2 commits intotimescale:masterfrom
jonatas:timezone-helpers

Conversation

@jonatas
Copy link
Contributor

@jonatas jonatas commented Mar 11, 2026

Hello Timescale friends, I had a need for time zones and I think it fits perfectly on the continuous aggregates helper.

Please, check if this would be useful for the core library.

Summary

  • Adds timezone_aware: true option to continuous_aggregates — injects an in_timezone(tz) scope into every generated cagg class
  • Adds timeframe_intervals: to support custom SQL intervals for symbolic timeframe names (e.g. { halfhour: '30 minutes' }) covering X:30 UTC offsets like IST, ACST, IRT, and NST
  • The scope rebuckets UTC cagg rows into local calendar days using (time_column AT TIME ZONE tz)::date without extra caggs or raw-table scans
  • Wraps the rebucketing in a derived table so chained .where calls work correctly
  • Benchmarking showed this scope approach outperforms permanent SQL VIEWs because the time_column predicate stays in the inner query where the planner can apply chunk exclusion

Why sub-daily granularity matters

The daily cagg buckets by UTC midnight, which always straddles local calendar days for non-UTC zones. Reading from a 30-min cagg and rebucketing by local date gives correct results for every timezone — including X:30 offsets.

# EST user sees the 03:00 UTC row correctly on Jan 15, not Jan 16
PageView::PageViewStatsPerHalfhour
  .in_timezone('America/New_York')
  .where(user_id: 2, local_date: '2024-01-15')
  .sum('total_duration')
# => 2700  (03:00 UTC = 22:00 EST on the 15th ✓)

Test plan

  • 23 new specs covering class generation, SQL correctness, and data correctness with a day-boundary proof
  • Full test suite passes (pre-existing failures unrelated to this change)
  • Runnable example script in examples/all_in_one/timezone_aware_caggs.rb
  • Docs updated in docs/models.md

jonatas and others added 2 commits March 11, 2026 16:36
Introduces `timezone_aware: true` and `timeframe_intervals:` options on
`continuous_aggregates`. When enabled, every generated cagg class gains the
`in_timezone(tz)` scope, which rebuckets UTC cagg rows into local calendar
days without extra caggs or raw-table scans.

The scope wraps the rebucketing in a derived table so chained `.where` calls
work correctly. Benchmarking showed this outperforms permanent SQL VIEWs
because the time_column predicate stays in the inner query where the planner
can apply chunk exclusion.

Also adds `timeframe_intervals:` to support custom SQL intervals for symbolic
timeframe names (e.g. `{ halfhour: '30 minutes' }` covers X:30 UTC offsets
like IST, ACST, IRT, and NST with a single cagg level).

Includes 23 specs, a runnable example script, and docs with a day-boundary
proof showing why sub-daily granularity is required for correct TZ rebucketing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The `t.timestamptz` shorthand was added in Rails 7.0. The scenic Gemfile
pins ActiveRecord to 6.1.x, which does not have this method. Use the
generic `t.column :ts, :timestamptz` form instead, which works in both
6.1 and 7.0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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