--- title: "time_bucket" slug: "dev-database-reference-sql-functions-date-time-time-bucket" updated: 2026-10-30T18:19:01Z published: 2026-10-30T18:19:01Z canonical: "docs.vastdata.com/dev-database-reference-sql-functions-date-time-time-bucket" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.vastdata.com/llms.txt > Use this file to discover all available pages before exploring further. # time_bucket *Applies to VAST Cluster 5.5.* *[Functions](/develop/docs/dev-database-reference-sql-functions-index) › [Date and time functions](/develop/docs/dev-database-reference-sql-functions-date-time) › Truncating and bucketing.* Returns the start of the fixed-width bucket a date or timestamp falls in, for example the six-hour period or the seven-day week that contains it. An origin or an offset moves where the buckets start. ## Syntax ``` time_bucket(width, value) time_bucket(width, value, origin) time_bucket(width, value, offset) ``` | Argument | Type | Description | | --- | --- | --- | | `width` | `INTERVAL` | The width of each bucket, greater than zero. Either months and years (`INTERVAL 1 MONTH`, `INTERVAL 10 YEAR`) or days and smaller units (`INTERVAL 7 DAY`, `INTERVAL 15 MINUTE`), not both. | | `value` | `DATE` or `TIMESTAMP` (also `TIMESTAMP_NS` and `TIMESTAMP_MS`) | The value to place in a bucket. | | `origin` | `DATE` or `TIMESTAMP` | Optional. A moment where a bucket starts; every bucket start is a whole number of widths from it. It can be before or after `value`. | | `offset` | `INTERVAL` | Optional. Moves every bucket start by this interval; it can be negative. | **Returns:** the type of `value`: `DATE` for a `DATE`, `TIMESTAMP` for a `TIMESTAMP` or another timestamp type. A `DATE` with a `TIMESTAMP` origin returns a `TIMESTAMP`. ## Set up the examples The examples connect with the ADBC driver, as in [VAST Query Engine (ADBC)](/develop/docs/dev-database-integrations-adbc), and work in a schema called `docs_sql_fn_date_time_time_bucket`. Set `QE_ENDPOINT`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `VASTDB_BUCKET` ([SQL reference](/develop/docs/dev-database-reference-sql-index#example-connect-and-use-a-transaction)), and run the blocks in order in one Python session. Below each block is the output you should see. This block connects, defines the `run()` and `plan()` helpers, and creates a table of five events, each with a date (`d`) and a timestamp (`ts`). Row 4 has no values: ``` import os import re import adbc_driver_manager import adbc_driver_manager.dbapi as dbapi import adbc_driver_vastdb BUCKET = os.environ["VASTDB_BUCKET"] SCHEMA = f'"{BUCKET}/docs_sql_fn_date_time_time_bucket"' conn = dbapi.connect( driver=adbc_driver_vastdb.get_driver_path(), db_kwargs={ "vast.db.endpoint": os.environ["QE_ENDPOINT"], "vast.db.access_key": os.environ["AWS_ACCESS_KEY_ID"], "vast.db.secret_key": os.environ["AWS_SECRET_ACCESS_KEY"], }, autocommit=True, ) def server_message(e): """The server's message in a driver error, without the request ID or the echoed statement.""" text = re.sub(r"\[req=0x[0-9a-f]+\]: ", "", str(e)) found = (re.search(r'message: "(.*?)", request_id', text, re.S) or re.search(r"description: (.*)\)", text, re.S)) message = found.group(1) if found else text.strip().splitlines()[-1] message = re.sub(r"^Plan query: '.*?' failed with: ", "", message, flags=re.S) message = re.split(r"\\n|\n", message)[0].replace('\\"', '"') return message.replace(BUCKET, "") def run(sql): """Run one statement. Print the rows it returns, or the error.""" try: with conn.cursor() as cur: cur.execute(sql) rows = cur.fetchall() if cur.description else [] except (adbc_driver_manager.Error, OSError) as e: print(f"{type(e).__name__}: {server_message(e)}") else: print(rows) def plan(sql): """Print the plan without its ~N Rows lines: the table's row count, which can still be 0 just after an insert.""" with conn.cursor() as cur: cur.execute("EXPLAIN " + sql) steps = cur.fetchall()[0][1] print("\n".join(line for line in steps.splitlines() if "Rows" not in line)) TABLE = f"{SCHEMA}.events" run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}") run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, d DATE, ts TIMESTAMP)") run(f"INSERT INTO {TABLE} VALUES " "(1, DATE '2026-02-28', TIMESTAMP '2026-02-28 23:15:00'), " "(2, DATE '2026-03-01', TIMESTAMP '2026-03-01 08:30:45.123456'), " "(3, DATE '2024-12-30', TIMESTAMP '2024-12-30 12:00:00'), " "(4, NULL, NULL), " "(5, DATE '2024-02-29', TIMESTAMP '2024-02-29 00:00:00')") ``` ``` [] [] [(5,)] ``` ## What it does `time_bucket` divides the time line into buckets of `width` and returns the start of the one that contains `value`. It always rounds down, also before 1970: `TIMESTAMP '1969-12-31 23:59:59.999999'` in one-day buckets is `1969-12-31 00:00:00`. Without an origin or an offset, the buckets are aligned like this: | Width | Bucket starts | Example | Result | | --- | --- | --- | --- | | Hours, minutes, seconds | Multiples of the width from midnight | `INTERVAL 15 MINUTE` on `08:37:12`; `INTERVAL 6 HOUR` on `2026-02-28 23:15` | `08:30:00`; `2026-02-28 18:00` | | `INTERVAL 1 DAY` | Midnight | `TIMESTAMP '1969-12-31 23:59:59.999999'` | `1969-12-31 00:00:00` | | `INTERVAL 7 DAY`, `INTERVAL 1 WEEK` | Monday | `DATE '2026-03-04'` | `2026-03-02` (a Monday) | | Months | The first of a month; three-month buckets start in January, April, July and October | `INTERVAL 3 MONTH` on `2026-05-15` | `2026-04-01` | | Years | 1 January; ten-year buckets start in a year that ends in 0 | `INTERVAL 10 YEAR` on `2026-03-15` | `2020-01-01` | **Origin.** With an origin, every bucket starts a whole number of widths from it. One-day buckets with origin `TIMESTAMP '2026-01-01 06:00:00'` run from 06:00 to 06:00, and weekly buckets with a Sunday as origin start on Sundays. The origin can be after the value. **Offset.** An offset moves every default bucket start: `time_bucket(INTERVAL 1 DAY, ts, INTERVAL 6 HOUR)` gives days that start at 06:00, and `INTERVAL '-6 hours'` days that start at 18:00 the evening before. With a month width, `INTERVAL 9 DAY` gives months that start on the 10th. The width, the origin and the offset can come from columns, so each row can use a different bucket. ## NULLs and edge cases | Input | Result | | --- | --- | | A NULL value (row 4) | `NULL` | | A NULL width, origin or offset | `NULL` | | An untyped `NULL` value: `time_bucket(INTERVAL 6 HOUR, NULL)` | `NULL` | | `INTERVAL 1 MICROSECOND` | The value unchanged | | A `DATE` with a width under a day: `time_bucket(INTERVAL 1 HOUR, DATE '2026-03-01')` | `2026-03-01`, still a `DATE` | | A `TIMESTAMP_NS` value | A `TIMESTAMP`: the nanoseconds are dropped | | `DATE 'infinity'` | `infinity` | | The width as a string: `time_bucket('1 day', ts)` | Works: the string is read as an interval | | A zero or negative width | Error: `Period must be greater than 0` | | A width with months and days: `INTERVAL '1 month 1 day'` | Error: `Month intervals cannot have day or time component` | | A `TIME` or `INTERVAL` value | Error: `No function matches the given name and argument types 'time_bucket(INTERVAL, TIME)'` | | A string value, such as `'abc'` | Error: `Could not choose a best candidate function for the function call "time_bucket(INTERVAL, STRING_LITERAL)"`; cast it to `TIMESTAMP` | | A number, boolean, `BLOB` or list | Error: `No function matches the given name and argument types`, for example `'time_bucket(INTERVAL, INTEGER_LITERAL)'` | The same cases, as the cluster returns them. They use the table and helpers from [Set up the examples](/develop/docs/dev-database-reference-sql-functions-date-time-time-bucket#set-up-the-examples): ``` run(f"SELECT id, time_bucket(INTERVAL 6 HOUR, ts) FROM {TABLE} WHERE id = 4") run("SELECT time_bucket(NULL, TIMESTAMP '2026-03-01 08:30:00'), " "time_bucket(INTERVAL 1 DAY, TIMESTAMP '2026-03-01 08:30:00', NULL::TIMESTAMP), " "time_bucket(INTERVAL 1 DAY, TIMESTAMP '2026-03-01 08:30:00', NULL::INTERVAL), time_bucket(INTERVAL 6 HOUR, NULL)") run("SELECT time_bucket(INTERVAL 1 MICROSECOND, TIMESTAMP '2026-03-01 08:37:12.123456'), " "time_bucket(INTERVAL 1 HOUR, DATE '2026-03-01'), " "CAST(time_bucket(INTERVAL 1 SECOND, TIMESTAMP_NS '2026-03-01 08:30:00.123456789') AS VARCHAR), " "CAST(time_bucket(INTERVAL 1 DAY, DATE 'infinity') AS VARCHAR), time_bucket('1 day', TIMESTAMP '2026-03-01 08:30:00')") run("SELECT time_bucket(INTERVAL 0 DAY, DATE '2026-03-01')") run("SELECT time_bucket(INTERVAL '1 month 1 day', DATE '2026-03-01')") run("SELECT time_bucket(INTERVAL 1 HOUR, TIME '08:30:00')") run("SELECT time_bucket(INTERVAL 6 HOUR, 'abc')") run("SELECT time_bucket(INTERVAL 6 HOUR, 123)") ``` ``` [(4, None)] [(None, None, None, None)] [(datetime.datetime(2026, 3, 1, 8, 37, 12, 123456), datetime.date(2026, 3, 1), '2026-03-01 08:30:00', 'infinity', datetime.datetime(2026, 3, 1, 0, 0))] OSError: Period must be greater than 0 OSError: Month intervals cannot have day or time component InternalError: Binder Error: No function matches the given name and argument types 'time_bucket(INTERVAL, TIME)'. You might need to add explicit type casts. InternalError: Binder Error: Could not choose a best candidate function for the function call "time_bucket(INTERVAL, STRING_LITERAL)". In order to select one, please add explicit type casts. InternalError: Binder Error: No function matches the given name and argument types 'time_bucket(INTERVAL, INTEGER_LITERAL)'. You might need to add explicit type casts. ``` The default alignment, an origin and an offset, before and after 1970: ``` run("SELECT time_bucket(INTERVAL 15 MINUTE, TIMESTAMP '2026-03-01 08:37:12'), " "time_bucket(INTERVAL 7 DAY, DATE '2026-03-04'), dayname(time_bucket(INTERVAL 7 DAY, DATE '2026-03-04')), " "time_bucket(INTERVAL 3 MONTH, TIMESTAMP '2026-05-15 08:30:00'), time_bucket(INTERVAL 10 YEAR, DATE '2026-03-15')") run("SELECT time_bucket(INTERVAL 1 DAY, TIMESTAMP '2026-03-01 08:30:00', TIMESTAMP '2026-01-01 06:00:00'), " "time_bucket(INTERVAL 1 WEEK, DATE '2026-03-04', DATE '2026-03-01'), " "time_bucket(INTERVAL 1 DAY, TIMESTAMP '2026-03-01 08:30:00', INTERVAL '-6 hours'), " "time_bucket(INTERVAL 1 MONTH, DATE '2026-03-15', INTERVAL 9 DAY)") run("SELECT time_bucket(INTERVAL 1 DAY, TIMESTAMP '1969-12-31 23:59:59.999999'), time_bucket(INTERVAL 7 DAY, DATE '1969-12-31')") ``` ``` [(datetime.datetime(2026, 3, 1, 8, 30), datetime.date(2026, 3, 2), 'Monday', datetime.datetime(2026, 4, 1, 0, 0), datetime.date(2020, 1, 1))] [(datetime.datetime(2026, 3, 1, 6, 0), datetime.date(2026, 3, 1), datetime.datetime(2026, 2, 28, 18, 0), datetime.date(2026, 3, 10))] [(datetime.datetime(1969, 12, 31, 0, 0), datetime.date(1969, 12, 29))] ``` ## Examples Each event in six-hour buckets, in weeks and in months: ``` run(f"SELECT id, time_bucket(INTERVAL 6 HOUR, ts), time_bucket(INTERVAL 7 DAY, d), time_bucket(INTERVAL 1 MONTH, d) " f"FROM {TABLE} ORDER BY id LIMIT 10") ``` ``` [(1, datetime.datetime(2026, 2, 28, 18, 0), datetime.date(2026, 2, 23), datetime.date(2026, 2, 1)), (2, datetime.datetime(2026, 3, 1, 6, 0), datetime.date(2026, 2, 23), datetime.date(2026, 3, 1)), (3, datetime.datetime(2024, 12, 30, 12, 0), datetime.date(2024, 12, 30), datetime.date(2024, 12, 1)), (4, None, None, None), (5, datetime.datetime(2024, 2, 29, 0, 0), datetime.date(2024, 2, 26), datetime.date(2024, 2, 1))] ``` Days that start at 06:00, written with an origin and with an offset; both give the same buckets: ``` run(f"SELECT id, time_bucket(INTERVAL 1 DAY, ts, TIMESTAMP '2026-01-01 06:00:00'), " f"time_bucket(INTERVAL 1 DAY, ts, INTERVAL 6 HOUR) FROM {TABLE} ORDER BY id LIMIT 10") ``` ``` [(1, datetime.datetime(2026, 2, 28, 6, 0), datetime.datetime(2026, 2, 28, 6, 0)), (2, datetime.datetime(2026, 3, 1, 6, 0), datetime.datetime(2026, 3, 1, 6, 0)), (3, datetime.datetime(2024, 12, 30, 6, 0), datetime.datetime(2024, 12, 30, 6, 0)), (4, None, None), (5, datetime.datetime(2024, 2, 28, 6, 0), datetime.datetime(2024, 2, 28, 6, 0))] ``` A bucket width that comes from a column: here, `id` days: ``` run(f"SELECT id, time_bucket(to_days(CAST(id AS INTEGER)), d) FROM {TABLE} ORDER BY id LIMIT 10") ``` ``` [(1, datetime.date(2026, 2, 28)), (2, datetime.date(2026, 3, 1)), (3, datetime.date(2024, 12, 28)), (4, None), (5, datetime.date(2024, 2, 26))] ``` Keep only the events in the bucket that starts on 1 March 2026: ``` run(f"SELECT id FROM {TABLE} WHERE time_bucket(INTERVAL 1 DAY, ts) = TIMESTAMP '2026-03-01 00:00:00' ORDER BY id LIMIT 10") ``` ``` [(2,)] ``` ## How it runs in VAST The Query Engine runs on the cluster's CNodes, and a query on a table starts with a scan in VAST DataBase (`VAST_SCAN`). VAST DataBase can evaluate a comparison of a column with a constant, but not a function of a column, so `time_bucket()` is always computed by the Query Engine: - **In the `SELECT` list,** the scan reads the column and the Query Engine computes `time_bucket()` for each row it returns. - **In `WHERE`,** the scan sends the column for every row, and a `FILTER` step in the Query Engine computes `time_bucket()` and keeps the rows that match. No rows are skipped in storage. The plan of the `WHERE` example shows the `FILTER` step above the scan: ``` plan(f"SELECT id FROM {TABLE} WHERE time_bucket(INTERVAL 1 DAY, ts) = TIMESTAMP '2026-03-01 00:00:00'") ``` ``` ┌───────────────────────────┐ │ PROJECTION │ │ ──────────────────── │ │ #1 │ │ │ └─────────────┬─────────────┘ ┌─────────────┴─────────────┐ │ FILTER │ │ ──────────────────── │ │ (time_bucket('1 day': │ │ :INTERVAL, ts) = '2026-03 │ │ -01 00:00:00'::TIMESTAMP) │ │ │ └─────────────┬─────────────┘ ┌─────────────┴─────────────┐ │ VAST_SCAN │ │ ──────────────────── │ │ Function: VAST_SCAN │ │ │ │ Projections: │ │ ts │ │ id │ │ │ └───────────────────────────┘ ``` ## Make it faster - **To select one bucket, filter on a range of the raw column.** `ts >= TIMESTAMP '2026-03-01 00:00:00' AND ts < TIMESTAMP '2026-03-02 00:00:00'` keeps the same rows as `time_bucket(INTERVAL 1 DAY, ts) = TIMESTAMP '2026-03-01 00:00:00'`, and it's pushed down to VAST DataBase, so rows that don't match never leave storage. Its plan has no `FILTER` step: ``` run(f"SELECT id FROM {TABLE} WHERE ts >= TIMESTAMP '2026-03-01 00:00:00' AND ts < TIMESTAMP '2026-03-02 00:00:00' " f"ORDER BY id LIMIT 10") plan(f"SELECT id FROM {TABLE} WHERE ts >= TIMESTAMP '2026-03-01 00:00:00' AND ts < TIMESTAMP '2026-03-02 00:00:00'") ``` ``` [(2,)] ┌───────────────────────────┐ │ VAST_SCAN │ │ ──────────────────── │ │ Function: VAST_SCAN │ │ Projections: id │ │ │ └───────────────────────────┘ ``` - **Store the bucket if you filter on it often.** Compute it when you write the rows and keep it in its own column: `day_bucket = TIMESTAMP '2026-03-01 00:00:00'` is pushed down, `time_bucket(INTERVAL 1 DAY, ts) = ...` isn't. ## Limitations and errors - **No `GROUP BY`.** Grouping by a bucket, as in `GROUP BY time_bucket(...)`, fails with `The operator 'HASH_GROUP_BY' type=9 is currently not supported.` Return the bucket for each row and aggregate in your client, or count one bucket at a time with a range filter, as in [Make it faster](/develop/docs/dev-database-reference-sql-functions-date-time-time-bucket#make-it-faster). - **No time-zone support.** On a `TIMESTAMP WITH TIME ZONE` value, `time_bucket` fails with an `Extension Autoloading Error`, and the form that takes a time-zone name as its third argument isn't available. Cast the value to `TIMESTAMP` first; see [time zones](/develop/docs/dev-database-reference-sql-functions-date-time-time-zones). - **Months and days don't mix in one width.** Use `INTERVAL 1 MONTH` with an offset such as `INTERVAL 9 DAY` instead of `INTERVAL '1 month 9 days'`. - **No `TIME` values.** `time_bucket` takes a `DATE` or a timestamp, not a `TIME`. ``` run(f"SELECT time_bucket(INTERVAL 1 MONTH, d) AS m, count(*) FROM {TABLE} GROUP BY m ORDER BY m LIMIT 10") run("SELECT time_bucket(INTERVAL 1 DAY, TIMESTAMP '2026-03-01 08:30:00', 'UTC')") ``` ``` InternalError: The operator 'HASH_GROUP_BY' type=9 is currently not supported. InternalError: Binder Error: Could not choose a best candidate function for the function call "time_bucket(INTERVAL, TIMESTAMP, STRING_LITERAL)". In order to select one, please add explicit type casts. ``` ## Similar functions | Function | Returns | Result for `TIMESTAMP '2024-12-30 13:45:30.123456'` | | --- | --- | --- | | `time_bucket(INTERVAL 1 MONTH, ...)` | The start of the bucket, as a `TIMESTAMP` | `2024-12-01 00:00:00` | | [date_trunc](/develop/docs/dev-database-reference-sql-functions-date-time-date-trunc) `('month', ...)` | The start of the calendar part; a `DATE` for `'day'` and coarser parts | `2024-12-01` | | `time_bucket(INTERVAL 1 HOUR, ...)` | Same as `date_trunc('hour', ...)` | `2024-12-30 13:00:00` | | `time_bucket(INTERVAL 6 HOUR, ...)` | Buckets `date_trunc` has no part for | `2024-12-30 12:00:00` | | [strftime](/develop/docs/dev-database-reference-sql-functions-date-time-strftime) | The value as text, in a format you choose | `strftime(..., '%Y-%m-01')` is `'2024-12-01'` | `date_trunc` works only with calendar parts (one month, one hour); `time_bucket` takes any width, such as six hours or ten years, and an origin. ``` run("SELECT time_bucket(INTERVAL 1 MONTH, TIMESTAMP '2024-12-30 13:45:30.123456'), " "date_trunc('month', TIMESTAMP '2024-12-30 13:45:30.123456'), " "time_bucket(INTERVAL 1 HOUR, TIMESTAMP '2024-12-30 13:45:30.123456'), " "date_trunc('hour', TIMESTAMP '2024-12-30 13:45:30.123456'), " "time_bucket(INTERVAL 6 HOUR, TIMESTAMP '2024-12-30 13:45:30.123456'), " "strftime(TIMESTAMP '2024-12-30 13:45:30.123456', '%Y-%m-01')") ``` ``` [(datetime.datetime(2024, 12, 1, 0, 0), datetime.date(2024, 12, 1), datetime.datetime(2024, 12, 30, 13, 0), datetime.datetime(2024, 12, 30, 13, 0), datetime.datetime(2024, 12, 30, 12, 0), '2024-12-01')] ``` Clean up: ``` run(f"DROP TABLE IF EXISTS {TABLE}") run(f"DROP SCHEMA IF EXISTS {SCHEMA}") conn.close() ``` ``` [] [] ``` ## See also - [Date and time functions](/develop/docs/dev-database-reference-sql-functions-date-time): all the date and time functions. - [Where filters run](/develop/docs/dev-database-reference-sql-select#where-filters-run) and [pushdowns](/develop/docs/dev-database-reference-pushdowns). - [EXPLAIN](/develop/docs/dev-database-reference-sql-explain): how to read a plan.