Date & Time Functions

Dates power a huge share of real queries: orders this month, customers who signed up last year, days since the last purchase. Date handling is also where databases differ most. This lesson teaches the concepts with SQLite's functions and shows the equivalents in other systems.

How dates are stored

Most databases have dedicated types: DATE, TIME, TIMESTAMP (date and time), and timestamps with time zones. SQLite has no date type; it stores dates as ISO-8601 text such as '2026-03-05' or '2026-03-05 14:30:00', and its date functions understand that format. Because ISO dates sort correctly as text, comparisons and ORDER BY just work.

The current date and time

SQL
SELECT date('now') AS today, datetime('now') AS now_utc, time('now') AS time_utc;

The standard keywords CURRENT_DATE, CURRENT_TIME and CURRENT_TIMESTAMP work in nearly every database, including SQLite. Times are in UTC unless you ask for local time.

Extracting parts of a date

SQL
SELECT order_date,
       strftime('%Y', order_date) AS year,
       strftime('%m', order_date) AS month,
       strftime('%d', order_date) AS day,
       strftime('%w', order_date) AS weekday
FROM orders
WHERE id IN (1, 9, 15);
Result 3 rows
order_dateyearmonthdayweekday
2025-09-03202509033
2026-01-07202601073
2026-03-05202603054

%w gives the day of the week, 0 = Sunday. Other databases use EXTRACT(YEAR FROM order_date) (standard; PostgreSQL, MySQL, Oracle), or YEAR(order_date)/DATEPART (MySQL, SQL Server).

Grouping by month

SQL
SELECT strftime('%Y-%m', order_date) AS month, COUNT(*) AS orders
FROM orders
GROUP BY month
ORDER BY month;
Result 7 rows
monthorders
2025-092
2025-102
2025-112
2025-122
2026-012
2026-023
2026-032

Turning dates into a "year-month" label is the standard way to build monthly reports (grouping is covered in lesson 19).

Date arithmetic

SQL
SELECT order_date,
       date(order_date, '+14 days') AS return_deadline,
       date(order_date, '+1 month') AS next_month,
       date(order_date, 'start of month') AS month_start,
       date(order_date, 'start of month', '+1 month', '-1 day') AS month_end
FROM orders
WHERE id IN (2, 11);
Result 2 rows
order_datereturn_deadlinenext_monthmonth_startmonth_end
2025-09-142025-09-282025-10-142025-09-012025-09-30
2026-02-032026-02-172026-03-032026-02-012026-02-28

SQLite's modifiers are applied left to right. Other databases add intervals: order_date + INTERVAL '14 days' (PostgreSQL), DATE_ADD(order_date, INTERVAL 14 DAY) (MySQL), DATEADD(day, 14, order_date) (SQL Server).

SQL
SELECT date('2026-01-31', '+1 month') AS surprise;
Result 1 row
surprise
2026-03-03

Month arithmetic is genuinely ambiguous: there's no 31 February. SQLite overflows into March; PostgreSQL and MySQL clamp to the last day of February. Test end-of-month cases whenever you add months.

Differences between dates

SQL
SELECT c.name, c.signup_date,
       julianday('2026-03-31') - julianday(c.signup_date) AS days_as_customer
FROM customers AS c
WHERE c.id IN (1, 9, 10);
Result 3 rows
namesignup_datedays_as_customer
Ana Pereira2024-02-11779
Isaac Levi2025-11-04147
Julia Santos2026-01-1476

julianday turns a date into a day number, so subtracting gives the days between. Elsewhere: date2 - date1 (PostgreSQL), DATEDIFF(date2, date1) (MySQL), DATEDIFF(day, date1, date2) (SQL Server).

This lesson uses a fixed "today" (31 March 2026) so the results don't change. In a real report you'd write julianday('now') or CURRENT_DATE.

Age in whole years

SQL
SELECT first_name, hire_date,
       CAST(strftime('%Y', '2026-03-31') AS INTEGER) - CAST(strftime('%Y', hire_date) AS INTEGER)
         - (strftime('%m-%d', '2026-03-31') < strftime('%m-%d', hire_date)) AS years_of_service
FROM employees
WHERE id IN (1, 6, 11);
Result 3 rows
first_namehire_dateyears_of_service
Olivia2015-03-0111
Lucas2021-11-014
Priya2024-03-042

Subtract the years, then subtract one more if this year's anniversary hasn't arrived yet. PostgreSQL's age() and MySQL's TIMESTAMPDIFF(YEAR, …) do this for you.

Filtering by date ranges

SQL
SELECT id, order_date
FROM orders
WHERE order_date >= '2026-01-01' AND order_date < '2026-02-01';
Result 2 rows
idorder_date
92026-01-07
102026-01-22

Prefer ranges on the raw column to wrapping it in a function (WHERE strftime('%Y-%m', order_date) = '2026-01'). Both give the same rows, but only the range can use an index (lesson 51).

Time zones

Store timestamps in UTC (or in a "timestamp with time zone" type) and convert to local time for display. Storing local times without the zone makes daylight-saving changes and international users a constant source of bugs.

Try it yourself

For each order, show the weekday name and how many days it is from the order to the end of that month.

Try it yourself
Result