Cheat sheet

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 #

sql
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 Server

Extract parts (year, month, day, ...) #

sql
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) #

sql
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 workaround

Date arithmetic #

sql
-- 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 month

First / last day of period #

sql
-- 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 #

sql
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)                -- SQLite

Parse string as date #

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

TokenPostgres familystrftime family
4-digit yearYYYY%Y
2-digit monthMM%m
2-digit dayDD%d
Abbrev monthMon%b
Full monthMonth%B
24-hourHH24%H
MinuteMI%M (MySQL: %i)
SecondSS%S

Common patterns #

sql
-- 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) = 2026 defeats indexes on order_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_TIMESTAMP returning 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') is 1 (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.

Tip

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.