Skip to content

Time Dimensions

Time dimensions are specialized dimensions that represent dates, timestamps, and time-based data in your analytics cubes. They enable powerful time-series analysis, trend visualization, and date-based filtering with granular control over time periods and date ranges.

Time dimensions in Drizzle Cube provide:

  • Granular Time Analysis: Break down data by year, quarter, month, week, day, or hour
  • Flexible Date Ranges: Support for relative dates (“last 30 days”) and absolute ranges
  • Type-Safe Queries: Full TypeScript support with Drizzle schema integration
  • Time Zone Handling: Consistent UTC-based calculations
  • Performance Optimization: Efficient query generation for time-based filters
dimensions: {
createdAt: {
name: "createdAt",
title: "Created Date",
type: "time",
sql: employees.createdAt
},
date: {
name: "date",
title: "Activity Date",
type: "time",
sql: productivity.date
}
}

Time dimensions are used in the timeDimensions array of queries, not in the regular dimensions array:

const query = {
measures: ["Employees.count"],
dimensions: ["Employees.departmentName"],
timeDimensions: [{
dimension: "Employees.createdAt",
granularity: "month",
dateRange: ["2023-01-01", "2023-12-31"]
}]
}

Time dimensions support various granularity levels for grouping data:

GranularityDescriptionFormat ExampleUse Case
yearAnnual grouping2023Year-over-year trends
quarterQuarterly grouping2023-Q1Quarterly reports
monthMonthly grouping2023-01Monthly analysis
weekWeekly grouping2023-W01Weekly trends
dayDaily grouping2023-01-15Daily metrics
hourHourly grouping2023-01-15 14:00Hourly patterns
// Monthly sales trends
const monthlyTrends = {
measures: ["Sales.totalRevenue"],
timeDimensions: [{
dimension: "Sales.date",
granularity: "month",
dateRange: "this year"
}]
}
// Daily activity for the last 30 days
const dailyActivity = {
measures: ["Users.activeCount"],
timeDimensions: [{
dimension: "Users.lastLoginAt",
granularity: "day",
dateRange: "last 30 days"
}]
}
// Hourly patterns for today
const hourlyPatterns = {
measures: ["Orders.count"],
timeDimensions: [{
dimension: "Orders.createdAt",
granularity: "hour",
dateRange: "today"
}]
}

Time dimensions support flexible date range filtering with both relative and absolute date ranges.

Relative date ranges automatically calculate based on the current date. See the Complete Date Range Reference table below for all supported options.

Specify exact start and end dates using arrays:

// Specific date range
dateRange: ["2023-01-01", "2023-12-31"]
// Single date (same start and end)
dateRange: ["2023-06-15", "2023-06-15"]
// Partial dates (will be parsed appropriately)
dateRange: ["2023-01", "2023-12"] // January to December 2023
// Relative date range examples
const examples = [
{
title: "Today"s Activity",
query: {
measures: ["Orders.count"],
timeDimensions: [{
dimension: "Orders.createdAt",
granularity: "hour",
dateRange: "today"
}]
}
},
{
title: "This Month"s Sales",
query: {
measures: ["Sales.totalRevenue"],
timeDimensions: [{
dimension: "Sales.date",
granularity: "day",
dateRange: "this month"
}]
}
},
{
title: "Last 90 Days Trend",
query: {
measures: ["Users.signupCount"],
timeDimensions: [{
dimension: "Users.createdAt",
granularity: "week",
dateRange: "last 90 days"
}]
}
},
{
title: "Quarterly Comparison",
query: {
measures: ["Revenue.total"],
timeDimensions: [{
dimension: "Revenue.date",
granularity: "quarter",
dateRange: "last 4 quarters"
}]
}
},
{
title: "Specific Campaign Period",
query: {
measures: ["Campaigns.conversions"],
timeDimensions: [{
dimension: "Campaigns.startDate",
granularity: "day",
dateRange: ["2023-06-01", "2023-06-30"]
}]
}
}
]
Range StringDescriptionCalculation
todayCurrent day00:00:00 to 23:59:59 today
yesterdayPrevious day00:00:00 to 23:59:59 yesterday
this weekCurrent weekMonday 00:00 to Sunday 23:59
this monthCurrent month1st 00:00 to last day 23:59
this quarterCurrent quarterQuarter start to quarter end
this yearCurrent yearJan 1st 00:00 to Dec 31st 23:59
last weekPrevious weekPrevious Monday to Sunday
last monthPrevious monthPrevious month start to end
last quarterPrevious quarterPrevious quarter start to end
last yearPrevious yearPrevious Jan 1st to Dec 31st
Range StringDescriptionCalculation
last 7 daysLast 7 days7 days ago 00:00 to now
last 30 daysLast 30 days30 days ago 00:00 to now
last 90 daysLast 90 days90 days ago 00:00 to now
last 12 monthsLast 12 months12 months ago to now
PatternExampleDescription
last N dayslast 14 daysLast N days including today
last N weekslast 4 weeksLast N weeks rolling
last N monthslast 6 monthsLast N months rolling
last N quarterslast 2 quartersLast N quarters rolling
last N yearslast 3 yearsLast N years rolling

You can include multiple time dimensions in a single query:

const complexTimeQuery = {
measures: ["Orders.count", "Orders.totalValue"],
dimensions: ["Orders.status"],
timeDimensions: [
{
dimension: "Orders.createdAt",
granularity: "week",
dateRange: "last 12 weeks"
},
{
dimension: "Orders.shippedAt",
granularity: "day",
dateRange: "last 30 days"
}
]
}

Time dimensions can also be used in filters for more complex queries:

const filteredQuery = {
measures: ["Sales.revenue"],
dimensions: ["Products.category"],
timeDimensions: [{
dimension: "Sales.date",
granularity: "month",
dateRange: "this year"
}],
filters: [
{
member: "Sales.createdAt",
operator: "inDateRange",
values: ["2023-Q2"] // Second quarter only
}
]
}
const quarterlyBusiness = {
measures: ["Revenue.total", "Revenue.growth"],
dimensions: ["Revenue.region"],
timeDimensions: [{
dimension: "Revenue.date",
granularity: "quarter",
dateRange: "last 8 quarters" // 2 years of quarters
}]
}
const seasonalAnalysis = {
measures: ["Sales.volume"],
dimensions: ["Products.category"],
timeDimensions: [{
dimension: "Sales.date",
granularity: "month",
dateRange: ["2022-12-01", "2024-02-28"] // Winter seasons
}]
}
const rollingAnalysis = {
measures: ["Users.retention"],
timeDimensions: [{
dimension: "Users.firstLoginAt",
granularity: "month",
dateRange: "last 12 months"
}]
}

Drizzle Cube handles time zones consistently:

  • Server Processing: All calculations use UTC internally
  • Database Queries: Times are normalized to UTC before comparison
  • Client Display: Format times according to user”s timezone in the frontend
  • Date Boundaries: Day, week, month boundaries calculated in UTC
// Time dimension definition with timezone awareness
dimensions: {
orderTime: {
name: "orderTime",
title: "Order Time",
type: "time",
sql: orders.createdAt, // Stored in UTC in database
description: "Order creation time (UTC)"
}
}

Ensure time dimension columns are indexed in your database:

-- PostgreSQL example
CREATE INDEX idx_orders_created_at ON orders(created_at);
CREATE INDEX idx_employees_hire_date ON employees(created_at);
// Good: Use appropriate granularity
const efficientQuery = {
measures: ["Sales.revenue"],
timeDimensions: [{
dimension: "Sales.date",
granularity: "month", // Don"t use "day" for yearly analysis
dateRange: "this year"
}]
}
// Avoid: Over-granular queries for large time ranges
const inefficientQuery = {
measures: ["Sales.revenue"],
timeDimensions: [{
dimension: "Sales.date",
granularity: "hour", // Too granular for 2-year analysis
dateRange: "last 2 years"
}]
}
// Employee hire dates
createdAt: {
name: "createdAt",
title: "Hire Date",
type: "time",
sql: employees.createdAt
}
// Activity dates
activityDate: {
name: "activityDate",
title: "Activity Date",
type: "time",
sql: activities.date
}
// Update timestamps
updatedAt: {
name: "updatedAt",
title: "Last Modified",
type: "time",
sql: records.updatedAt
}
// Business day flag
businessDay: {
name: "businessDay",
title: "Business Day",
type: "boolean",
sql: sql`EXTRACT(DOW FROM ${activities.date}) BETWEEN 1 AND 5`
}
// Time period categories
timePeriod: {
name: "timePeriod",
title: "Time Period",
type: "string",
sql: sql`
CASE
WHEN ${activities.date} >= CURRENT_DATE - INTERVAL "7 days" THEN "This Week"
WHEN ${activities.date} >= CURRENT_DATE - INTERVAL "30 days" THEN "This Month"
WHEN ${activities.date} >= CURRENT_DATE - INTERVAL "90 days" THEN "This Quarter"
ELSE "Earlier"
END
`
}
import { describe, it, expect } from "vitest"
describe("Time Dimensions", () => {
it("should handle relative date ranges", async () => {
const query = {
measures: ["Sales.count"],
timeDimensions: [{
dimension: "Sales.date",
granularity: "day",
dateRange: "last 7 days"
}]
}
const result = await semanticLayer.load(query, {
organisationId: "test-org"
})
const data = result.rawData()
expect(data).toHaveLength(7) // Should have 7 days
expect(data[0]["Sales.date"]).toBeDefined()
})
it("should handle absolute date ranges", async () => {
const query = {
measures: ["Users.signupCount"],
timeDimensions: [{
dimension: "Users.createdAt",
granularity: "month",
dateRange: ["2023-01-01", "2023-12-31"]
}]
}
const result = await semanticLayer.load(query, {
organisationId: "test-org"
})
const data = result.rawData()
expect(data).toHaveLength(12) // 12 months
expect(data[0]["Users.createdAt"]).toMatch(/2023-/)
})
it("should handle multiple granularities", async () => {
const weeklyQuery = {
measures: ["Orders.count"],
timeDimensions: [{
dimension: "Orders.createdAt",
granularity: "week",
dateRange: "last 4 weeks"
}]
}
const monthlyQuery = {
measures: ["Orders.count"],
timeDimensions: [{
dimension: "Orders.createdAt",
granularity: "month",
dateRange: "last 4 months"
}]
}
const weeklyResult = await semanticLayer.load(weeklyQuery, { organisationId: "test" })
const monthlyResult = await semanticLayer.load(monthlyQuery, { organisationId: "test" })
expect(weeklyResult.rawData()).toHaveLength(4)
expect(monthlyResult.rawData()).toHaveLength(4)
})
})
  1. Use Appropriate Granularity: Match granularity to your analysis needs
  2. Index Time Columns: Ensure database performance with proper indexes
  3. Relative Ranges: Prefer relative ranges for dynamic dashboards
  4. UTC Consistency: Store all times in UTC in your database
  5. Boundary Awareness: Understand how date boundaries are calculated
  6. Performance Testing: Test queries with large date ranges
  7. Business Logic: Use computed dimensions for business-specific time logic
// Dynamic dashboard with user-selectable time ranges
const dashboardQuery = (timeRange: string) => ({
measures: ["Metrics.value"],
dimensions: ["Metrics.category"],
timeDimensions: [{
dimension: "Metrics.date",
granularity: "day",
dateRange: timeRange // "last 30 days", "this month", etc.
}]
})
// Year-over-year comparison
const yearOverYear = {
measures: ["Sales.revenue"],
timeDimensions: [{
dimension: "Sales.date",
granularity: "month",
dateRange: "last 24 months"
}]
}
// User cohorts by signup month
const userCohorts = {
measures: ["Users.count", "Users.retainedCount"],
dimensions: ["Users.cohortMonth"],
timeDimensions: [{
dimension: "Users.createdAt",
granularity: "month",
dateRange: "last 12 months"
}]
}