Skip to content

Dates & Time

Dates are fundamental to analytics. ASQL provides a clean, intuitive, and portable date syntax that compiles to the right SQL for any dialect.

Date Literals

Use the @ prefix for date literals:

from users
  where signup_date >= @2024-01-01

from orders
  where order_date between @2024-01-01 and @2024-12-31

This compiles to DATE '2024-01-01' in SQL.

For timestamps, include the time:

@2024-01-15T10:30:00

Time Truncation Functions

Truncate dates to a specific unit:

Function Result Example Output
year(date) Truncate to year start 2025-01-01
month(date) Truncate to month start 2025-01-01
week(date) Truncate to week start 2025-01-06
day(date) Truncate to day 2025-01-15
hour(date) Truncate to hour 2025-01-15 14:00:00
quarter(date) Truncate to quarter start 2025-01-01
from orders
  group by month(created_at) (
    sum(amount) as revenue
  )

Natural Language Alternatives

All of these are equivalent:

year(created_at)       -- function style
year created_at        -- space style
year_created_at        -- underscore style
year of created_at     -- "of" style

Date Part Extraction

Extract specific parts (returns integers, not dates):

Expression Returns Example
day of week date 1-7 3 (Wednesday)
day of month date 1-31 15
day of year date 1-366 45
week of year date 1-52 12
month of year date 1-12 6
quarter of year date 1-4 2

Numbering follows the database

Extraction compiles to the database's own function, so numbering can differ by dialect: week of year is the ISO week (1-53) on PostgreSQL and DuckDB, but BigQuery's EXTRACT(WEEK) counts Sunday-start weeks from 0, and Snowflake's follows its WEEK_OF_YEAR_POLICY setting. day of week likewise returns the dialect's native numbering. Use week(date) truncation when you need weeks that line up across dialects. There is no year of ... extraction form; use extract(year from date).

Comparing a truncating unit function with a number, such as year(created_at) = 2024, is a compile error because it compares a date with an integer. Write extract(year from created_at) = 2024 or filter on a date range.

-- Weekend orders
from orders
  where day of week order_date in (6, 7)

-- Sales by day of week
from sales
  group by day of week sale_date (
    sum(amount) as revenue
  )

Truncation vs Extraction

  • Truncation (month(date)): Returns a date, for time series
  • Extraction (month of year date): Returns an integer, for "all Januaries"
month(created_at)           -- → 2025-01-01 (date)
month of year created_at    -- → 1 (integer)

Date Arithmetic

Add or subtract from dates using clean inline syntax:

order_date + 7 days
order_date - 1 month
order_date + 2 weeks
created_at + 24 hours

Singular and plural both work:

order_date + 1 day    -- singular
order_date + 7 days   -- plural

This compiles to dialect-appropriate SQL:

-- PostgreSQL, Snowflake, Redshift
order_date + INTERVAL '7 DAY'

-- BigQuery, MySQL, DuckDB, Databricks, Trino
order_date + INTERVAL '7' DAY

Date Difference

Calculate the difference between dates:

days(end_date - start_date)      -- Integer days
months(end_date - start_date)    -- Integer months
years(end_date - start_date)     -- Integer years
hours(end_date - start_date)     -- Integer hours
weeks(end_date - start_date)     -- Integer weeks

Alternative function syntax:

days_between(start_date, end_date)
months_between(start_date, end_date)

Example:

from orders
  select
    days(shipped_date - order_date) as fulfillment_days,
    months(now() - customer_since) as customer_tenure_months

Relative Dates

Past Dates (ago)

from users
  where last_login >= 7 days ago

from orders
  where created_at >= 30 days ago
  where created_at >= 1 month ago

Future Dates (from now)

from orders
  where estimated_delivery <= 3 days from now

from reminders
  where remind_at <= 1 hour from now

These compile to dialect-appropriate SQL using CURRENT_TIMESTAMP and intervals.

Time Since/Until Functions

Calculate time elapsed since a date, or time remaining until a future date.

Three Equivalent Syntaxes

All of these are equivalent:

-- Function call (recommended)
days_since(created_at)
months_until(due_date)

-- Space notation (natural language)
days since created_at
months until due_date

-- Underscore alias (compact)
days_since_created_at
months_until_due_date

Available Functions

Since (time elapsed) Until (time remaining)
days_since(col) days_until(col)
weeks_since(col) weeks_until(col)
months_since(col) months_until(col)
years_since(col) years_until(col)
hours_since(col) hours_until(col)
minutes_since(col) minutes_until(col)
seconds_since(col) seconds_until(col)

Examples

from users
  select
    name,
    days_since(last_login),           -- Function call
    months since signup_date,          -- Space notation
    years_since_birth_date as age      -- Underscore alias

from tasks
  where days_until(due_date) < 7
  -- or: where days until due_date < 7
  -- or: where days_until_due_date < 7

Week Start Configuration

Default: ISO 8601 standard (Monday = day 1)

week(created_at)           -- Default: ISO (Monday start)
week_monday(created_at)    -- Explicit Monday start
week_sunday(created_at)    -- US-style Sunday start

day of week created_at     -- 1 = Monday, 7 = Sunday (ISO)

Time Bucketing (Grouping)

Time bucketing is simply grouping by a time function:

from orders
  group by month(created_at) (
    sum(amount) as revenue
  )

from sessions
  group by week(start_time) (
    count(distinct user_id) as active_users
  )

Timezone Handling

Use :: syntax for timezone conversion:

-- Short timezone codes
created_at::PST
created_at::UTC
created_at::EST

-- Full IANA timezone names (quoted)
created_at::"America/Los_Angeles"
created_at::"Europe/London"

-- Chained with other operations
month(created_at::PST)
created_at::UTC + 7 days

A short code names its region's zone, daylight saving included: PT/PST/PDT → America/Los_Angeles, MT/MST/MDT → America/Denver, CT/CST/CDT → America/Chicago, ET/EST/EDT → America/New_York, UTC/GMT → UTC. Any other zone needs its quoted IANA name.

This compiles to each warehouse's conversion:

-- PostgreSQL, DuckDB
created_at AT TIME ZONE 'America/Los_Angeles'

-- BigQuery
TIMESTAMP(DATETIME(created_at, 'America/Los_Angeles'))

-- Snowflake, Redshift, Databricks
CONVERT_TIMEZONE('America/Los_Angeles', created_at)

-- MySQL (DATETIME values carry no zone, so they are read as UTC)
CONVERT_TZ(created_at, 'UTC', 'America/Los_Angeles')

Quick Reference

Operation Syntax Example
Date literal @YYYY-MM-DD @2025-01-15
Truncation unit(col) month(created_at)
Extraction unit of period col day of week created_at
Add/subtract date + N unit order_date + 7 days
Difference unit(date1 - date2) days(end - start)
Relative past N unit ago 7 days ago
Relative future N unit from now 3 days from now
Time since unit_since(col) or unit since col days_since(created_at)
Time until unit_until(col) or unit until col days_until(due_date)
Timezone col::TZ created_at::PST

Real-World Examples

Daily Revenue for Last 30 Days

from orders
  where created_at >= 30 days ago
  group by day(created_at) (
    sum(amount) as revenue
  )
  order by day(created_at)

Monthly User Signups

from users
  group by month(created_at) (
    # as signups
  )
  order by month

Orders with Fulfillment Time

from orders
  where shipped_date is not null
  select
    id,
    order_date,
    shipped_date,
    days(shipped_date - order_date) as fulfillment_days

Active Users Last Week

from users
  where last_login >= 7 days ago
  select name, email, days_since_last_login

Next Steps