Welcome to the new DOCS.vastdata.com. We've added new menus and a version selector, plus new persona and resource pages to help customers get started. Looking for a previous article? Use the docs search.

era

Prev Next

Applies to VAST Cluster 5.5.

Functions › Date and time functions › Parts of a date or time. Returns the era of a date or timestamp: 1 for year 1 and later (AD, or CE), 0 for the years before it (BC, or BCE).

Syntax

era(value)
Argument Type Description
value DATE or TIMESTAMP (also TIMESTAMP_NS, TIMESTAMP_MS, TIMESTAMP_S) The value to take the era of.

Returns: BIGINT.

Same as: date_part('era', value) and extract(era FROM value); see date_part.

Set up the examples

The examples connect with the ADBC driver, as in VAST Query Engine (ADBC), and work in a schema called docs_sql_fn_date_time_era. Set QE_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and VASTDB_BUCKET (SQL reference), 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 four events. Each has a date (d) and a timestamp (ts). Row 2 is the first day of year 1, row 3 the last day of year 9999, and 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_era"'

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, "<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 '0001-01-01', TIMESTAMP '0001-01-01 00:00:00'), "
    "(3, DATE '9999-12-31', TIMESTAMP '9999-12-31 23:59:59'), "
    "(4, NULL, NULL)")
[]
[]
[(4,)]

What it does

era returns 1 for a date or timestamp in year 1 or later, and 0 for one before year 1: DATE '0001-01-01' gives 1, and CAST('0001-01-01 (BC)' AS DATE) (1 BC) gives 0. year counts the years before year 1 down from 0, so 44 BC is year -43, era 0.

Use era to label or separate BC dates; for every date after year 1 it's 1. It works on every timestamp precision.

era has no meaning for a time of day or an interval: a TIME fails to bind, and an INTERVAL fails with interval units "era" not recognized.

NULLs and edge cases

Input Result
A NULL value (row 4) NULL
DATE 'infinity', TIMESTAMP '-infinity' NULL
DATE '0001-01-01' 1
DATE '9999-12-31' 1
1 BC: CAST('0001-01-01 (BC)' AS DATE) 0
44 BC: CAST('0044-03-15 (BC)' AS DATE) 0
An untyped NULL: era(NULL) Error: Could not choose a best candidate function for the function call "era("NULL")"
An INTERVAL Error: interval units "era" not recognized
A TIME Error: No function matches the given name and argument types 'era(TIME)'
A string Error: Could not choose a best candidate function; cast it to DATE first
A number, boolean, BLOB or list Error: No function matches the given name and argument types, for example 'era(INTEGER_LITERAL)'

The same cases, as the cluster returns them. They use the table and helpers from Set up the examples:

run(f"SELECT id, era(d), era(ts) FROM {TABLE} WHERE id = 4")
run("SELECT era(DATE 'infinity'), era(TIMESTAMP '-infinity'), era(DATE '0001-01-01'), era(DATE '9999-12-31'), "
    "era(CAST('0001-01-01 (BC)' AS DATE)), era(CAST('0044-03-15 (BC)' AS DATE))")
run("SELECT era(NULL)")
run("SELECT era(INTERVAL 1 DAY)")
run("SELECT era(TIME '13:45:30')")
run("SELECT era('2024-02-29')")
run("SELECT era(123)")
[(4, None, None)]
[(None, None, 1, 1, 0, 0)]
InternalError: Binder Error: Could not choose a best candidate function for the function call "era("NULL")". In order to select one, please add explicit type casts.
OSError: interval units "era" not recognized
InternalError: Binder Error: No function matches the given name and argument types 'era(TIME)'. You might need to add explicit type casts.
InternalError: Binder Error: Could not choose a best candidate function for the function call "era(STRING_LITERAL)". In order to select one, please add explicit type casts.
InternalError: Binder Error: No function matches the given name and argument types 'era(INTEGER_LITERAL)'. You might need to add explicit type casts.

Examples

The era and year of each event:

run(f"SELECT id, era(d), year(d), era(ts) FROM {TABLE} ORDER BY id LIMIT 10")
[(1, 1, 2026, 1), (2, 1, 1, 1), (3, 1, 9999, 1), (4, None, None, None)]

Label a date with its era. A BC year is 1 - year(d): 44 BC is year -43:

run("SELECT CASE WHEN era(CAST('0044-03-15 (BC)' AS DATE)) = 0 "
    "THEN CAST(1 - year(CAST('0044-03-15 (BC)' AS DATE)) AS VARCHAR) || ' BC' "
    "ELSE CAST(year(CAST('0044-03-15 (BC)' AS DATE)) AS VARCHAR) || ' AD' END")
[('44 BC',)]

Keep only the events in the current era. This works, but Make it faster has a faster way to write it:

run(f"SELECT id FROM {TABLE} WHERE era(d) = 1 ORDER BY id LIMIT 10")
[(1,), (2,), (3,)]

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 era() is always computed by the Query Engine:

  • In the SELECT list, the scan reads the column and the Query Engine computes era() for each row it returns.
  • In WHERE, the scan sends the column for every row, and a FILTER step in the Query Engine computes era() 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 era(d) = 1")
┌───────────────────────────┐
│         PROJECTION        │
│    ────────────────────   │
│             #1            │
│                           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│           FILTER          │
│    ────────────────────   │
│        (era(d) = 1)       │
│                           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│         VAST_SCAN         │
│    ────────────────────   │
│    Function: VAST_SCAN    │
│                           │
│        Projections:       │
│             d             │
│             id            │
│                           │
└───────────────────────────┘

Make it faster

  • Compare the raw column with the first day of year 1, not era(). d >= DATE '0001-01-01' returns the same rows as era(d) = 1, and d < DATE '0001-01-01' the same as era(d) = 0. A comparison of the column with a constant is pushed down to VAST DataBase, so the plan has no FILTER step:
run(f"SELECT id FROM {TABLE} WHERE d >= DATE '0001-01-01' ORDER BY id LIMIT 10")
plan(f"SELECT id FROM {TABLE} WHERE d >= DATE '0001-01-01'")
[(1,), (2,), (3,)]
┌───────────────────────────┐
│         VAST_SCAN         │
│    ────────────────────   │
│    Function: VAST_SCAN    │
│      Projections: id      │
│                           │
└───────────────────────────┘

Limitations and errors

  • Dates and timestamps only. era of an INTERVAL fails with interval units "era" not recognized, although the error for a wrong argument lists era(INTERVAL). era of a TIME fails with No function matches the given name and argument types 'era(TIME)', and extract(era FROM TIME ...) with "time" units "era" not recognized.
  • No time-zone support. On a TIMESTAMP WITH TIME ZONE value, such as now(), era fails with an Extension Autoloading Error. Cast the value to TIMESTAMP first; see time zones.
  • No date_trunc('era', ...). It fails with Specifier type not implemented for DATETRUNC statistics. Compare with DATE '0001-01-01' instead.
run("SELECT extract(era FROM TIME '13:45:30')")
run("SELECT date_trunc('era', DATE '2024-02-29')")
OSError: "time" units "era" not recognized
InternalError: Not implemented Error: Specifier type not implemented for DATETRUNC statistics

Similar functions

Function Returns Result for 44 BC, CAST('0044-03-15 (BC)' AS DATE) (era gives 0)
year The year, counting down from 0 before year 1 -43
century The century, negative before year 1 -1
millennium The millennium, negative before year 1 -1
decade The year divided by 10 -4
run("SELECT era(CAST('0044-03-15 (BC)' AS DATE)), year(CAST('0044-03-15 (BC)' AS DATE)), "
    "century(CAST('0044-03-15 (BC)' AS DATE)), millennium(CAST('0044-03-15 (BC)' AS DATE)), "
    "decade(CAST('0044-03-15 (BC)' AS DATE))")
[(0, -43, -1, -1, -4)]

Clean up:

run(f"DROP TABLE IF EXISTS {TABLE}")
run(f"DROP SCHEMA IF EXISTS {SCHEMA}")
conn.close()
[]
[]

See also