Difference between two dates
Short answer: in Postgres / DuckDB / Oracle, subtract: end_date - start_date returns days. In MySQL / SQL Server / Snowflake / Redshift, use DATEDIFF (argument order varies, so read your engine's docs). BigQuery uses DATE_DIFF(end, start, DAY). SQLite needs julianday(end) - julianday(start). For months or years: AGE (Postgres), DATEDIFF(MONTH, ...) (SQL Server / Snowflake), MONTHS_BETWEEN (Oracle).
Canonical SQL #
-- Postgres / DuckDB / Oracle: plain subtraction returns days.
SELECT (end_date - start_date) AS days FROM events;
-- SQLite needs julianday().
SELECT julianday(end_date) - julianday(start_date) AS days FROM events;
-- DATEDIFF (and BigQuery's DATE_DIFF). Argument order varies:
SELECT DATEDIFF(end_date, start_date) AS days FROM events; -- MySQL — always days
SELECT DATEDIFF(DAY, start_date, end_date) AS days FROM events; -- SQL Server / Snowflake / Redshift
SELECT DATE_DIFF(end_date, start_date, DAY) AS days FROM events; -- BigQuery (different name)
-- Months or years.
SELECT DATEDIFF(MONTH, start_date, end_date) AS months FROM events; -- SQL Server / Snowflake
SELECT AGE(end_date, start_date) AS interval FROM events; -- Postgres (returns INTERVAL)
SELECT MONTHS_BETWEEN(end_date, start_date) AS months FROM events; -- OracleExample #
Common variants #
-- Days between (works in all major engines via subtraction or DATEDIFF).
SELECT DATEDIFF(DAY, '2026-01-01', '2026-04-26') AS days; -- SQL Server / Snowflake -> 115
SELECT DATE_DIFF('day', DATE '2026-01-01', DATE '2026-04-26') -- DuckDB only
AS days;
SELECT DATE '2026-04-26' - DATE '2026-01-01' AS days; -- Postgres / DuckDB
-- Whole calendar months between (counts month boundaries crossed, not "30-day chunks").
SELECT DATEDIFF(MONTH, '2026-01-31', '2026-02-01') AS months; -- SQL Server -> 1 (one month boundary)
-- Hours / minutes / seconds (TIMESTAMP inputs).
SELECT DATEDIFF(HOUR, started_at, ended_at) AS hrs FROM jobs; -- SQL Server / Snowflake
SELECT DATE_DIFF('minute', started_at, ended_at) AS mins FROM jobs; -- DuckDB
SELECT EXTRACT(EPOCH FROM (ended_at - started_at)) / 60 AS mins
FROM jobs; -- PostgresDialect comparison #
| Engine | Days between | Other units / gotcha |
|---|---|---|
| PostgreSQL / DuckDB | end - start | AGE(end, start) for calendar interval; DATE_DIFF(unit, start, end) (DuckDB) |
| MySQL | DATEDIFF(end, start) | TIMESTAMPDIFF(unit, start, end); argument order swaps |
| SQL Server / Snowflake / Redshift | DATEDIFF(DAY, start, end) | counts boundary crossings, not elapsed time |
| BigQuery | DATE_DIFF(end, start, DAY) | DATETIME_DIFF, TIMESTAMP_DIFF for those types |
| Oracle | end - start | MONTHS_BETWEEN(end, start) for months |
| SQLite | julianday(end) - julianday(start) | returns REAL (days) |
- Postgres / DuckDB:
date - datereturnsINTEGERdays;timestamp - timestampreturns anINTERVAL.AGE(end, start)returns a calendar-aware interval ("3 years 1 mon 12 days").DATE_DIFF(unit, start, end)(DuckDB) or(EXTRACT(EPOCH FROM ...))(Postgres) for unit-specific differences. - MySQL:
DATEDIFF(end, start)always returns days. For other units, useTIMESTAMPDIFF(unit, start, end)— note the arg order swap fromDATEDIFF. - SQL Server / Snowflake / Redshift:
DATEDIFF(unit, start, end)is the canonical form. Counts boundary crossings, not elapsed time.DATEDIFF(MONTH, '2026-01-31', '2026-02-01') = 1even though only one day passed. - BigQuery:
DATE_DIFF(end, start, unit)forDATE;DATETIME_DIFF,TIMESTAMP_DIFFfor the corresponding types. Also boundary-counting, not elapsed. - Oracle:
date2 - date1returns days (asNUMBER). For months:MONTHS_BETWEEN(end, start). - SQLite:
julianday(end) - julianday(start)returns days as aREAL.
"Months between" and "years between" are ambiguous, and engines disagree. DATEDIFF(MONTH, '2026-01-31', '2026-02-01') is 1 on SQL Server (one month boundary crossed) but 0 on systems that count "elapsed full months." Decide which definition you want — boundary-crossings or full-elapsed — and pick the function (or computation) that matches. For "age in years," boundary-counting gets you to "31 years old the day after their 31st birthday-eve," which is rarely what the business wants.