SQL Date Functions

Almost every real database tracks when something happened: an order date, a hire date, a signup timestamp. SQL date functions let you extract parts of a date (year, month, day), do date arithmetic (add 30 days, find the difference between two dates), and format dates for display or comparison. Getting comfortable with them is essential, because dates are one of the most common sources of subtle, silent bugs in SQL code.

Overview: How SQL Engines Handle Dates

Unlike numbers or text, “date” is not one universal storage format across database systems. SQLite (the engine used for the examples on this page) has no dedicated date/time storage class at all. Instead, it lets you store dates as:

  • TEXT in ISO 8601 format, e.g. '2024-05-01' or '2024-05-01 14:30:00' — the recommended approach, because ISO text sorts correctly as plain strings.
  • REAL as a Julian day number — the number of days since noon on November 24, 4714 BC (proleptic Julian calendar).
  • INTEGER as Unix time — seconds since 1970-01-01 00:00:00 UTC.

SQLite’s date functions (date(), time(), datetime(), julianday(), strftime()) all work by parsing whichever of these formats you give them, internally converting to a Julian day number, doing the requested calculation, and formatting the result back out. This is why you can freely mix a text date and a Julian-day calculation in the same query — SQLite normalizes everything through Julian day arithmetic behind the scenes.

Other engines take a stricter approach: MySQL and PostgreSQL have native DATE, DATETIME/TIMESTAMP column types with fixed internal binary representations, and SQL Server has DATE, DATETIME2, and related types. The function names differ between engines even though the underlying ideas — extract a part, add an interval, compute a difference — are the same everywhere.

Syntax

The general form of SQLite’s date functions is:

DATE(timestring, modifier, modifier, ...)
TIME(timestring, modifier, modifier, ...)
DATETIME(timestring, modifier, modifier, ...)
JULIANDAY(timestring, modifier, modifier, ...)
STRFTIME(format, timestring, modifier, modifier, ...)
Function Returns Typical use
DATE(...) Text, YYYY-MM-DD Normalize or shift a date, dropping the time part
TIME(...) Text, HH:MM:SS Extract or shift just the time part
DATETIME(...) Text, YYYY-MM-DD HH:MM:SS Normalize or shift a full timestamp
JULIANDAY(...) Real number Precise arithmetic, especially date differences
STRFTIME(format, ...) Text, custom format Extract a specific part (year, month, weekday, etc.)
CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP Text The current date/time in UTC

The optional modifiers are applied left to right and let you shift a date: '+N days', '-N months', '+N years', 'start of month', 'start of year', 'start of day', and 'weekday N' are the most common. strftime() format codes include %Y (4-digit year), %m (2-digit month), %d (2-digit day), %H/%M/%S (time), and %j (day of year, 001–366).

Equivalent functions in other dialects

Task SQLite MySQL PostgreSQL SQL Server
Current date CURRENT_DATE CURDATE() CURRENT_DATE GETDATE()
Add days DATE(d,'+7 days') DATE_ADD(d, INTERVAL 7 DAY) d + INTERVAL '7 days' DATEADD(day,7,d)
Extract year strftime('%Y', d) YEAR(d) EXTRACT(YEAR FROM d) YEAR(d)
Difference in days julianday(d2)-julianday(d1) DATEDIFF(d2, d1) d2 - d1 DATEDIFF(day,d1,d2)

Examples

Example 1: Extracting parts of a date

CREATE TABLE events (
  event_name TEXT,
  event_date TEXT
);
INSERT INTO events (event_name, event_date) VALUES
  ('Kickoff Meeting', '2024-01-15'),
  ('Product Launch', '2024-03-22'),
  ('Mid-Year Review', '2024-07-01'),
  ('Year-End Party', '2024-12-20');

SELECT event_name, event_date,
       strftime('%Y', event_date) AS year,
       strftime('%m', event_date) AS month,
       strftime('%j', event_date) AS day_of_year
FROM events
ORDER BY event_date;

Result:

event_name event_date year month day_of_year
Kickoff Meeting 2024-01-15 2024 01 015
Product Launch 2024-03-22 2024 03 082
Mid-Year Review 2024-07-01 2024 07 183
Year-End Party 2024-12-20 2024 12 355

strftime() reads the stored ISO text, parses it into a Julian day internally, and re-formats it according to the codes you supply. Note that every value it returns is TEXT, even year — that detail matters, as you’ll see in Common Mistakes below.

Example 2: Date arithmetic with JULIANDAY

CREATE TABLE order_shipping (
  order_id INTEGER,
  order_date TEXT,
  ship_date TEXT
);
INSERT INTO order_shipping (order_id, order_date, ship_date) VALUES
  (1, '2024-05-01', '2024-05-04'),
  (2, '2024-05-10', '2024-05-10'),
  (3, '2024-05-15', '2024-05-20');

SELECT order_id, order_date, ship_date,
       julianday(ship_date) - julianday(order_date) AS days_to_ship
FROM order_shipping
ORDER BY order_id;

Result:

order_id order_date ship_date days_to_ship
1 2024-05-01 2024-05-04 3.0
2 2024-05-10 2024-05-10 0.0
3 2024-05-15 2024-05-20 5.0

julianday() converts each date to a floating-point Julian day number, so subtracting two of them gives an exact number of days (including fractional days if a time-of-day is present). This is far safer than trying to subtract date strings directly.

Example 3: Shifting dates with modifiers

SELECT DATE('2026-07-20', '+30 days') AS due_date,
       DATE('2026-07-20', 'start of month') AS month_start,
       DATE('2026-07-20', 'start of month', '+1 month', '-1 day') AS month_end;

Result:

due_date month_start month_end
2026-08-19 2026-07-01 2026-07-31

Modifiers apply in sequence, left to right. 'start of month' snaps the date to the 1st, then '+1 month' moves to the 1st of the following month, and '-1 day' steps back one day — a common trick for finding the last day of a month regardless of how many days it has.

How It Works Step by Step (Under the Hood)

When SQLite evaluates something like strftime('%Y', order_date) inside a query, it performs these steps in order:

  • Parse: the input string (or number) is matched against SQLite’s recognized date/time formats (YYYY-MM-DD, YYYY-MM-DD HH:MM:SS, Julian day, Unix epoch, etc.).
  • Normalize: the parsed value is converted internally into a Julian day number (a floating-point value), which is the common currency for all date math.
  • Apply modifiers: each modifier argument ('+7 days', 'start of month', …) adjusts that Julian day number, applied strictly in the order given.
  • Format: the final Julian day is converted back into whatever output the function promises — a full timestamp for datetime(), just the date for date(), or a custom string for strftime().

This matters for query planning too: because date functions are ordinary scalar functions, SQLite cannot use an index to skip evaluating them for every row unless the expression matches an indexed expression exactly. A condition like WHERE strftime('%Y', order_date) = '2024' forces a full scan and a per-row function call, whereas a range condition like WHERE order_date >= '2024-01-01' AND order_date < '2025-01-01' can use a plain index on order_date, because it compares the stored text directly instead of transforming every row first. In query-processing order, this filter is applied during the WHERE stage — after FROM builds the row set, before GROUP BY, HAVING, SELECT, ORDER BY, and LIMIT run.

Common Mistakes

Mistake 1: Mixing date formats breaks sorting and filtering

CREATE TABLE signups (
  user_name TEXT,
  signup_date TEXT
);
INSERT INTO signups (user_name, signup_date) VALUES
  ('Alice', '2024-01-05'),
  ('Bob', '01/20/2024'),
  ('Carla', '2024-02-10');

-- Wrong: signup_date is stored in two different formats
SELECT user_name, signup_date
FROM signups
ORDER BY signup_date;

Result (wrong order): Bob (01/20/2024), Alice (2024-01-05), Carla (2024-02-10). Because ORDER BY on text compares characters left to right, '01/20/2024' sorts before '2024-01-05' simply because the character '0' comes before '2' — not because it’s chronologically earlier. The dates aren’t actually being compared as dates at all.

CREATE TABLE signups_fixed (
  user_name TEXT,
  signup_date TEXT
);
INSERT INTO signups_fixed (user_name, signup_date) VALUES
  ('Alice', '2024-01-05'),
  ('Bob', '2024-01-20'),
  ('Carla', '2024-02-10');

SELECT user_name, signup_date
FROM signups_fixed
ORDER BY signup_date;

Result (correct order): Alice (2024-01-05), Bob (2024-01-20), Carla (2024-02-10). Once every row uses the same ISO 8601 format, plain string sorting is also correct chronological sorting.

Mistake 2: Comparing a text result to a number

CREATE TABLE payments (
  payment_id INTEGER,
  payment_date TEXT
);
INSERT INTO payments (payment_id, payment_date) VALUES
  (1, '2023-11-05'),
  (2, '2024-02-14'),
  (3, '2024-06-30');

-- Wrong: strftime() returns TEXT, but 2024 here is an INTEGER literal
SELECT * FROM payments WHERE strftime('%Y', payment_date) = 2024;

Result: 0 rows. This is surprising but consistent with SQLite’s type rules: when neither side of a comparison has column affinity (a function result and a bare integer literal both lack it), SQLite compares by storage class, and every INTEGER value is defined to sort below every TEXT value regardless of what the text says. '2024' (text) is never equal to 2024 (integer), even though they look the same.

CREATE TABLE payments (
  payment_id INTEGER,
  payment_date TEXT
);
INSERT INTO payments (payment_id, payment_date) VALUES
  (1, '2023-11-05'),
  (2, '2024-02-14'),
  (3, '2024-06-30');

-- Correct: compare text to text
SELECT * FROM payments WHERE strftime('%Y', payment_date) = '2024';

Result: payment_id 2 (2024-02-14) and payment_id 3 (2024-06-30). Quoting the year as a string fixes the comparison.

Best Practices

  • Always store dates as ISO 8601 text (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS) so lexicographic sorting matches chronological order.
  • Use julianday() for date differences instead of subtracting strings or relying on manual day-counting.
  • Remember that strftime() always returns TEXT — compare its result to a quoted string, never a bare number.
  • Prefer range comparisons (date_col >= '2024-01-01' AND date_col < '2025-01-01') over wrapping the column in a function, since range comparisons on the raw column can use an index.
  • Store timestamps in UTC and convert to local time only for display, since SQLite has no native timezone type.
  • Use date()/datetime() to normalize input before comparing values that might carry unexpected precision (extra time components, etc.).

Practice Exercises

  • Using the events table from Example 1, write a query that returns only events that happened in July (any year). Hint: filter on strftime('%m', event_date).
  • Using the order_shipping table from Example 2, write a query that returns only orders where days_to_ship was greater than 2.
  • Write a single expression using DATE() and modifiers that computes the last day of the previous month relative to '2026-07-20'. Expected result: 2026-06-30.

Summary

  • SQL date functions let you extract parts of a date, shift dates forward or backward, and compute precise differences between dates.
  • SQLite stores dates as TEXT, REAL (Julian day), or INTEGER (Unix time) and normalizes everything to Julian day internally when you call a date function.
  • Key functions: date(), time(), datetime(), julianday(), and strftime(), plus modifiers like '+N days' and 'start of month'.
  • Store dates as ISO 8601 text so string sorting equals chronological sorting.
  • Never compare a strftime() result to an unquoted number — it always returns TEXT.
  • Function names for the same task differ across MySQL, PostgreSQL, and SQL Server, even though the underlying logic is the same.