Applies to VAST Cluster 5.5.
Functions › Date and time functions › Epoch values. Returns the number of whole milliseconds since 1970-01-01 00:00:00 of a date or timestamp, as a BIGINT. It also works the other way: given a BIGINT count of milliseconds, it returns the TIMESTAMP.
Syntax
epoch_ms(value)
epoch_ms(milliseconds)
| Argument | Type | Description |
|---|---|---|
value |
DATE, TIMESTAMP (also TIMESTAMP_NS, TIMESTAMP_MS, TIMESTAMP_S), TIMESTAMP WITH TIME ZONE, TIME, TIME WITH TIME ZONE or INTERVAL |
The value to convert to milliseconds. |
milliseconds |
BIGINT (or a smaller integer type) |
Milliseconds since 1970-01-01 00:00:00, to turn into a timestamp. |
Returns: BIGINT; TIMESTAMP for epoch_ms(milliseconds).
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_epoch_ms. 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 five events. Each has a date (d), a timestamp (ts), a time of day (t), a timestamp with nanoseconds (tns) and a Unix time in milliseconds (ms). Row 4 has no values, and row 5 is the last moment of 1969:
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_epoch_ms"'
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, t TIME, tns TIMESTAMP_NS, ms BIGINT)")
run(f"INSERT INTO {TABLE} VALUES "
"(1, DATE '2026-02-28', TIMESTAMP '2026-02-28 23:15:00', TIME '23:15:00', "
"CAST('2026-02-28 23:15:00.123456789' AS TIMESTAMP_NS), 1772320500000), "
"(2, DATE '2026-03-01', TIMESTAMP '2026-03-01 08:30:45.123456', TIME '08:30:45.123456', "
"CAST('2026-03-01 08:30:45.123456' AS TIMESTAMP_NS), 1772353845123), "
"(3, DATE '2024-12-30', TIMESTAMP '2024-12-30 12:00:00', TIME '12:00:00', "
"CAST('2024-12-30 12:00:00' AS TIMESTAMP_NS), 0), "
"(4, NULL, NULL, NULL, NULL, NULL), "
"(5, DATE '1969-12-31', TIMESTAMP '1969-12-31 23:59:59.999999', TIME '23:59:59.999999', "
"CAST('1969-12-31 23:59:59.999999999' AS TIMESTAMP_NS), -1)")
[]
[]
[(5,)]
What it does
epoch_ms has two forms:
- From a date or time value to milliseconds. On a
TIMESTAMPorDATE, the milliseconds since 1970-01-01 00:00:00; on aTIME, the milliseconds since midnight; on anINTERVAL, its length in milliseconds. The digits finer than a millisecond are dropped:epoch_ms(TIMESTAMP '2024-02-29 13:45:30.123456')is1709214330123. - From milliseconds to a timestamp. On a
BIGINT, theTIMESTAMPthat many milliseconds after 1970-01-01 00:00:00:epoch_ms(1772353845123)is2026-03-01 08:30:45.123, andepoch_ms(-1)is1969-12-31 23:59:59.999. Use it to read Unix times in milliseconds, as many systems write them.
On a TIMESTAMP WITH TIME ZONE, the milliseconds count from 1970-01-01 00:00:00 UTC, so the offset is applied: epoch_ms(TIMESTAMPTZ '2024-02-29 13:45:30.123456+02') is 1709207130123. On a TIME WITH TIME ZONE, the offset isn't applied: epoch_ms(TIMETZ '13:45:30.123456+02:30') is 49530123.
On an INTERVAL, epoch_ms counts a month as 30 days and a year as 12 months: epoch_ms(INTERVAL '1 year') is 31104000000 (360 days). epoch counts a year as 365.25 days instead.
NULLs and edge cases
| Input | Result |
|---|---|
NULL, or a NULL value (row 4) |
NULL |
DATE 'infinity', TIMESTAMP '-infinity' |
NULL |
One microsecond before 1970 (row 5's ts) |
0: the fraction isn't rounded down to -1 |
TIMESTAMP '1969-12-31 23:59:59.9995' |
0 |
DATE '0001-01-01' |
-62135596800000 |
INTERVAL '1 month' |
2592000000 |
INTERVAL '1 year 2 months 3 days 04:05:06.789' |
36561906789 |
epoch_ms(0) |
1970-01-01 00:00:00 |
epoch_ms(9223372036854) |
2262-04-11 23:47:16.854 |
epoch_ms(9223372036854775807) |
Error: Could not convert Timestamp(MS) to Timestamp(US |
A DOUBLE or DECIMAL, such as epoch_ms(1.5e12) |
Error: No function matches the given name and argument types 'epoch_ms(DOUBLE)'; cast it to BIGINT first |
| A string | Error: Could not choose a best candidate function |
A boolean, BLOB or list |
Error: No function matches the given name and argument types, for example 'epoch_ms(BOOLEAN)' |
The same cases, as the cluster returns them. They use the table and helpers from Set up the examples:
run(f"SELECT id, epoch_ms(ts), epoch_ms(ms) FROM {TABLE} WHERE id = 4")
run("SELECT epoch_ms(NULL), epoch_ms(DATE 'infinity'), epoch_ms(TIMESTAMP '-infinity'), "
"epoch_ms(TIMESTAMP '1969-12-31 23:59:59.999999'), epoch_ms(TIMESTAMP '1969-12-31 23:59:59.9995')")
run("SELECT epoch_ms(DATE '0001-01-01'), epoch_ms(INTERVAL '1 month'), epoch_ms(INTERVAL '1 year 2 months 3 days 04:05:06.789')")
run("SELECT epoch_ms(0), CAST(epoch_ms(9223372036854) AS VARCHAR)")
run("SELECT epoch_ms(9223372036854775807)")
run("SELECT epoch_ms(1.5e12)")
run("SELECT epoch_ms('1772320500000')")
run("SELECT epoch_ms(true)")
[(4, None, None)]
[(None, None, None, 0, 0)]
[(-62135596800000, 2592000000, 36561906789)]
[(datetime.datetime(1970, 1, 1, 0, 0), '2262-04-11 23:47:16.854')]
OSError: Could not convert Timestamp(MS) to Timestamp(US)
InternalError: Binder Error: No function matches the given name and argument types 'epoch_ms(DOUBLE)'. You might need to add explicit type casts.
InternalError: Binder Error: Could not choose a best candidate function for the function call "epoch_ms(STRING_LITERAL)". In order to select one, please add explicit type casts.
InternalError: Binder Error: No function matches the given name and argument types 'epoch_ms(BOOLEAN)'. You might need to add explicit type casts.
Examples
The milliseconds since 1970 of each event's date and timestamp, and the milliseconds since midnight of its time:
run(f"SELECT id, epoch_ms(d), epoch_ms(ts), epoch_ms(t) FROM {TABLE} ORDER BY id LIMIT 10")
[(1, 1772236800000, 1772320500000, 83700000), (2, 1772323200000, 1772353845123, 30645123), (3, 1735516800000, 1735560000000, 43200000), (4, None, None, None), (5, -86400000, 0, 86399999)]
Turn the Unix times in milliseconds into timestamps:
run(f"SELECT id, ms, epoch_ms(ms) FROM {TABLE} ORDER BY id LIMIT 10")
[(1, 1772320500000, datetime.datetime(2026, 2, 28, 23, 15)), (2, 1772353845123, datetime.datetime(2026, 3, 1, 8, 30, 45, 123000)), (3, 0, datetime.datetime(1970, 1, 1, 0, 0)), (4, None, None), (5, -1, datetime.datetime(1969, 12, 31, 23, 59, 59, 999000))]
Keep only the events after a Unix time in milliseconds. This works, but Make it faster has a faster way to write it:
run(f"SELECT id FROM {TABLE} WHERE epoch_ms(ts) > 1772300000000 ORDER BY id LIMIT 10")
[(1,), (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 epoch_ms() of a column is always computed by the Query Engine:
- In the
SELECTlist, the scan reads the column and the Query Engine computesepoch_ms()for each row it returns. - In
WHERE, the scan sends the column for every row, and aFILTERstep in the Query Engine computesepoch_ms()and keeps the rows that match. No rows are skipped in storage. This is the same for both forms.
The plan of the WHERE example shows the FILTER step above the scan:
plan(f"SELECT id FROM {TABLE} WHERE epoch_ms(ts) > 1772300000000")
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #1 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ (epoch_ms(ts) > │
│ 1772300000000) │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ ts │
│ id │
│ │
└───────────────────────────┘
Make it faster
- Apply
epoch_msto the constant, not to the column.ts > epoch_ms(1772300000000)returns the same rows asepoch_ms(ts) > 1772300000000. The Query Engine computes the constant once, so the comparison of the raw column is pushed down to VAST DataBase, and the plan has noFILTERstep:
plan(f"SELECT id FROM {TABLE} WHERE ts > epoch_ms(1772300000000)")
run(f"SELECT id FROM {TABLE} WHERE ts > epoch_ms(1772300000000) ORDER BY id LIMIT 10")
┌───────────────────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ Projections: id │
│ │
└───────────────────────────┘
[(1,), (2,)]
- Store Unix times as
TIMESTAMP. If you load milliseconds, convert them once withepoch_ms(ms)when you write the rows. ATIMESTAMPcolumn can be filtered with pushed-down comparisons and read with every date and time function.
Limitations and errors
- Milliseconds only. The reverse form loses the microseconds:
epoch_ms(epoch_ms(TIMESTAMP '2026-03-01 08:30:45.123456'))is2026-03-01 08:30:45.123. For microseconds, use epoch_us and make_timestamp. - Only
epoch_mshas the reverse form.epoch_us(1772353845123456),epoch_ns(...)andepoch(...)of a number fail withNo function matches the given name and argument types. Use make_timestamp for microseconds, make_timestamp_ns for nanoseconds and to_timestamp for seconds. - Whole milliseconds as
BIGINT. ADOUBLEorDECIMALcount fails; cast it toBIGINTfirst. - One argument.
epoch_ms(ts, ts)fails withNo function matches the given name and argument types 'epoch_ms(TIMESTAMP, TIMESTAMP)'.
run("SELECT epoch_ms(epoch_ms(TIMESTAMP '2026-03-01 08:30:45.123456'))")
run("SELECT epoch_us(1772353845123456)")
run("SELECT epoch_ms(CAST(1.5e12 AS BIGINT))")
run(f"SELECT epoch_ms(ts, ts) FROM {TABLE} LIMIT 1")
[(datetime.datetime(2026, 3, 1, 8, 30, 45, 123000),)]
InternalError: Binder Error: No function matches the given name and argument types 'epoch_us(INTEGER_LITERAL)'. You might need to add explicit type casts.
[(datetime.datetime(2017, 7, 14, 2, 40),)]
InternalError: Binder Error: No function matches the given name and argument types 'epoch_ms(TIMESTAMP, TIMESTAMP)'. You might need to add explicit type casts.
Similar functions
| Function | Returns | Result for TIMESTAMP '2024-12-30 13:45:30.123456' (epoch_ms gives 1735566330123) |
|---|---|---|
| epoch | Seconds, as a DOUBLE with the fraction |
1735566330.123456 |
| epoch_us | Whole microseconds | 1735566330123456 |
| epoch_ns | Whole nanoseconds | 1735566330123456000 |
| make_timestamp with one argument | From microseconds to a TIMESTAMP: the reverse of epoch_us |
make_timestamp(1735566330123456) gives 2024-12-30 13:45:30.123456 |
| to_timestamp | From seconds to a TIMESTAMP WITH TIME ZONE: the reverse of epoch |
(cast it to TIMESTAMP) |
run("SELECT epoch_ms(TIMESTAMP '2024-12-30 13:45:30.123456'), epoch(TIMESTAMP '2024-12-30 13:45:30.123456'), "
"epoch_us(TIMESTAMP '2024-12-30 13:45:30.123456'), epoch_ns(TIMESTAMP '2024-12-30 13:45:30.123456')")
run("SELECT epoch_ms(1735566330123), make_timestamp(1735566330123456), CAST(to_timestamp(1735566330.123456) AS TIMESTAMP)")
[(1735566330123, 1735566330.123456, 1735566330123456, 1735566330123456000)]
[(datetime.datetime(2024, 12, 30, 13, 45, 30, 123000), datetime.datetime(2024, 12, 30, 13, 45, 30, 123456), datetime.datetime(2024, 12, 30, 13, 45, 30, 123456))]
Clean up:
run(f"DROP TABLE IF EXISTS {TABLE}")
run(f"DROP SCHEMA IF EXISTS {SCHEMA}")
conn.close()
[]
[]
See also
- epoch, epoch_us and epoch_ns: seconds, microseconds and nanoseconds.
- Date and time functions: all the date and time functions.
- Where filters run and pushdowns.
- EXPLAIN: how to read a plan.