SQL date functions cheat sheet
Every common date/time operation, with the dialect that disagrees flagged inline. This is the function family that varies most across engines, so bookmark it if you write SQL across more than one warehouse.
Current date / time #
CURRENT_DATE -- today, no time
CURRENT_TIMESTAMP -- right now, with timezone (ANSI)
NOW() -- Postgres / MySQL / DuckDB
CURRENT_TIMESTAMP() -- BigQuery (parens required)
datetime('now') -- SQLite
GETDATE() -- SQL Server (server-local)
SYSDATETIME() -- SQL Server (higher precision)
SYSDATE -- Oracle (server-local, no TZ)
LOCALTIME / LOCALTIMESTAMP -- ANSI: like CURRENT_* without timezone
-- UTC explicitly.
CURRENT_TIMESTAMP AT TIME ZONE 'UTC' -- Postgres / DuckDB
UTC_TIMESTAMP() -- MySQL
GETUTCDATE() -- SQL ServerExtract parts (year, month, day, ...) #
EXTRACT(YEAR FROM date_col) -- ANSI; supported almost everywhere
EXTRACT(MONTH FROM date_col)
EXTRACT(DAY FROM date_col)
EXTRACT(HOUR FROM ts_col)
EXTRACT(QUARTER FROM date_col)
EXTRACT(DOW FROM date_col) -- day of week (0-6 or 1-7 depending on engine)
DATE_PART('year', date_col) -- Postgres / DuckDB / Snowflake / Redshift synonym
YEAR(date_col) -- MySQL / SQL Server shortcut (Oracle uses EXTRACT)
MONTH(date_col) -- same family
DAY(date_col)Truncate (round down to period) #
DATE_TRUNC('day', ts_col) -- Postgres / DuckDB / Snowflake / Redshift / Databricks
DATE_TRUNC('month', ts_col)
DATE_TRUNC('year', ts_col)
DATE_TRUNC('week', ts_col) -- Monday by default on most engines
TIMESTAMP_TRUNC(ts_col, MONTH) -- BigQuery (TIMESTAMP); DATE_TRUNC for DATE; DATETIME_TRUNC for DATETIME
DATETRUNC(MONTH, ts_col) -- SQL Server 2022+
TRUNC(date_col, 'MM') -- Oracle
date(d, 'start of month') -- SQLite
DATE_FORMAT(d, '%Y-%m-01') -- MySQL workaroundDate arithmetic #
-- Add / subtract a period.
date_col + INTERVAL '7 days' -- Postgres / DuckDB / Oracle
DATE_ADD(date_col, INTERVAL 7 DAY) -- MySQL / BigQuery
DATEADD(DAY, 7, date_col) -- SQL Server / Snowflake / Redshift
date_col - INTERVAL '1 month' -- works on most engines
-- Difference between two dates (in days).
end_date - start_date -- Postgres / DuckDB / Oracle
julianday(end) - julianday(start) -- SQLite
DATEDIFF(end, start) -- MySQL: returns days
DATEDIFF(DAY, start, end) -- SQL Server / Snowflake / Redshift
DATE_DIFF(end, start, DAY) -- BigQuery (note arg order)
MONTHS_BETWEEN(end, start) -- Oracle, returns months as fraction
-- "X days/months/years from now."
CURRENT_DATE + INTERVAL '30 days'
DATE_TRUNC('month', CURRENT_DATE) - INTERVAL '1 day' -- last day of last monthFirst / last day of period #
-- First day of the month.
DATE_TRUNC('month', date_col)
-- Last day of the month.
DATE_TRUNC('month', date_col) + INTERVAL '1 month' - INTERVAL '1 day' -- Postgres / DuckDB
LAST_DAY(date_col) -- MySQL / Oracle / Snowflake / DuckDB
LAST_DAY(date_col, MONTH) -- BigQuery / Snowflake (with unit)
EOMONTH(date_col) -- SQL Server (since 2012)
-- First day of the year, quarter, week.
DATE_TRUNC('year', date_col)
DATE_TRUNC('quarter', date_col)
DATE_TRUNC('week', date_col)Format date as string #
TO_CHAR(d, 'YYYY-MM-DD') -- Postgres / Oracle / Snowflake / Redshift
STRFTIME(d, '%Y-%m-%d') -- DuckDB (canonical)
DATE_FORMAT(d, '%Y-%m-%d') -- MySQL
FORMAT_DATE('%Y-%m-%d', d) -- BigQuery
FORMAT(d, 'yyyy-MM-dd') -- SQL Server
strftime('%Y-%m-%d', d) -- SQLiteParse string as date #
-- ISO format ('2026-04-26'): CAST works everywhere.
CAST('2026-04-26' AS DATE)
'2026-04-26'::DATE -- Postgres / DuckDB shorthand
-- Custom format.
TO_DATE('26/04/2026', 'DD/MM/YYYY') -- Postgres / Oracle / Snowflake / Redshift
STRPTIME('26/04/2026', '%d/%m/%Y') -- DuckDB (canonical)
STR_TO_DATE('26/04/2026', '%d/%m/%Y') -- MySQL
PARSE_DATE('%d/%m/%Y', '26/04/2026') -- BigQuery
CONVERT(DATE, '26/04/2026', 103) -- SQL Server (103 = "dd/mm/yyyy")Format string conventions #
Two competing families. Postgres / Oracle / Snowflake / Redshift use one; MySQL / BigQuery / SQLite / DuckDB STRPTIME use strftime.
| Token | Postgres family | strftime family |
|---|---|---|
| 4-digit year | YYYY | %Y |
| 2-digit month | MM | %m |
| 2-digit day | DD | %d |
| Abbrev month | Mon | %b |
| Full month | Month | %B |
| 24-hour | HH24 | %H |
| Minute | MI | %M (MySQL: %i) |
| Second | SS | %S |
Common patterns #
-- Group by month (preserves the year).
SELECT DATE_TRUNC('month', order_date) AS month, SUM(total) AS revenue
FROM orders
GROUP BY DATE_TRUNC('month', order_date);
-- "Last 30 days" — index-friendly half-open range.
SELECT * FROM orders
WHERE order_date >= CURRENT_DATE - INTERVAL '30 days'
AND order_date < CURRENT_DATE;
-- Filter to a single year — index-friendly.
SELECT * FROM orders
WHERE order_date >= DATE '2026-01-01'
AND order_date < DATE '2027-01-01';
-- Year-over-year via self-join on calendar date — robust to gaps and leap years.
-- (LAG(revenue, 365) only works on a perfectly dense daily series.)
WITH daily_revenue AS (
SELECT order_date AS day, ROUND(SUM(total), 2) AS revenue
FROM orders
GROUP BY order_date
)
SELECT
today.day,
today.revenue AS revenue,
prev_year.revenue AS revenue_year_ago
FROM daily_revenue today
LEFT JOIN daily_revenue prev_year
ON prev_year.day = (today.day - INTERVAL '1 year')::DATE;Common mistakes #
WHERE EXTRACT(YEAR FROM order_date) = 2026defeats indexes onorder_date. Rewrite as a half-open range (see common patterns above).- Locale-dependent date string parsing.
'04/05/2026'is April 5 in some locales, May 4 in others. Always pin a format string:TO_DATE(s, 'DD/MM/YYYY'). CURRENT_TIMESTAMPreturning local time on some engines, UTC on others. Confirm your engine before relying on it. Default to storing UTC, displaying local at the edge.- DATEDIFF "boundary counting" vs "elapsed time".
DATEDIFF(MONTH, '2026-01-31', '2026-02-01')is1(one month boundary crossed) on SQL Server, even though only one day passed. Pick the right semantics.
FAQ #
How do I extract the year from a date in SQL?
EXTRACT(YEAR FROM date_col) is the ANSI standard, working on Postgres, MySQL, Oracle, DuckDB, BigQuery, Snowflake, Redshift. Shortcuts: YEAR(date_col) on MySQL / SQL Server. SQLite has no EXTRACT, so use strftime('%Y', date_col). SQL Server also lacks native EXTRACT; use YEAR(date_col) or DATEPART(year, date_col).
What's the difference between DATE_TRUNC syntax across engines?
Postgres / DuckDB / Snowflake / Redshift: DATE_TRUNC('month', date_col). BigQuery: DATE_TRUNC(date_col, MONTH), with the argument order swapped. SQL Server (2022+): DATETRUNC(MONTH, date_col). Oracle: TRUNC(date_col, 'MM'). MySQL has no DATE_TRUNC; use DATE_FORMAT(d, '%Y-%m-01') for first of month.
How do I calculate the difference between two dates?
Postgres / DuckDB: subtract, so end_date - start_date returns days. MySQL: DATEDIFF(end, start) (always days). SQL Server / Snowflake / Redshift: DATEDIFF(unit, start, end). BigQuery: DATE_DIFF(end, start, DAY). Beware boundary semantics: DATEDIFF(MONTH, '2026-01-31', '2026-02-01') is 1 on SQL Server even though only 1 day passed.
Should I store timestamps in UTC or local time?
UTC, in a timezone-aware column (TIMESTAMPTZ on Postgres, TIMESTAMP on BigQuery, TIMESTAMP_LTZ on Snowflake). Convert to local time only at display. Storing local time (or naive timestamps) leads to off-by-N-hour bugs across daylight-saving transitions, server moves, and locale differences.
Why does WHERE EXTRACT(YEAR FROM order_date) = 2026 not use my index?
Wrapping a column in a function defeats the index. The engine has to compute EXTRACT per row to evaluate the predicate. Rewrite as a half-open range: WHERE order_date >= DATE '2026-01-01' AND order_date < DATE '2027-01-01'. Same logical result, the index is usable, and the query speeds up by orders of magnitude on a large table.
How do I parse a string into a date?
For ISO format ('2026-04-26'), CAST(s AS DATE) works everywhere. For other formats: TO_DATE(s, 'DD/MM/YYYY') (Postgres / Oracle / Snowflake), STRPTIME(s, '%d/%m/%Y') (DuckDB), STR_TO_DATE(s, '%d/%m/%Y') (MySQL), PARSE_DATE('%d/%m/%Y', s) (BigQuery), CONVERT(DATE, s, 103) (SQL Server). Always pin the format string for ambiguous input: 04/05/2026 is April 5 OR May 4 depending on locale.
Most date bugs come from one of three sources: timezones, format-string ambiguity, and indexed-column-wrapped-in-function. Memorize the defensive patterns: store UTC; pin format strings; rewrite EXTRACT(...) = X predicates as ranges. Detail pages: DATE_TRUNC, Extract year from date, Difference between dates, Convert string to date, TIMESTAMP vs DATETIME.