Recipe

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 #

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;  -- Oracle

Example #

Loading SQL editor...

Common variants #

sql
-- 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; -- Postgres

Dialect comparison #

EngineDays betweenOther units / gotcha
PostgreSQL / DuckDBend - startAGE(end, start) for calendar interval; DATE_DIFF(unit, start, end) (DuckDB)
MySQLDATEDIFF(end, start)TIMESTAMPDIFF(unit, start, end); argument order swaps
SQL Server / Snowflake / RedshiftDATEDIFF(DAY, start, end)counts boundary crossings, not elapsed time
BigQueryDATE_DIFF(end, start, DAY)DATETIME_DIFF, TIMESTAMP_DIFF for those types
Oracleend - startMONTHS_BETWEEN(end, start) for months
SQLitejulianday(end) - julianday(start)returns REAL (days)
  • Postgres / DuckDB: date - date returns INTEGER days; timestamp - timestamp returns an INTERVAL. 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, use TIMESTAMPDIFF(unit, start, end) — note the arg order swap from DATEDIFF.
  • 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') = 1 even though only one day passed.
  • BigQuery: DATE_DIFF(end, start, unit) for DATE; DATETIME_DIFF, TIMESTAMP_DIFF for the corresponding types. Also boundary-counting, not elapsed.
  • Oracle: date2 - date1 returns days (as NUMBER). For months: MONTHS_BETWEEN(end, start).
  • SQLite: julianday(end) - julianday(start) returns days as a REAL.
Warning

"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.