Interval

How to store and manipulate time periods in PostgreSQL using the INTERVAL data type: syntax, input formats, arithmetic, and practical examples.

5 min read · Last updated: March 2026 · Back to overview

Quick Answer

The PostgreSQL INTERVAL data type stores a period of time expressed in years, months, days, hours, minutes, and seconds. It uses 16 bytes of storage and supports arithmetic with DATE and TIMESTAMP values.

Spin up a Postgres database in 20 seconds with Vela.

Try Vela Sandbox

Introduction to the PostgreSQL INTERVAL data type

The INTERVAL data type stores a duration — a period of time rather than a specific point in time. Intervals are essential for date arithmetic: calculating expiry dates, measuring elapsed time, filtering recent records, and aggregating durations.

The syntax for an interval literal is:

@ interval [ fields ] [ (p) ]

The @ sign is optional. The optional precision p (0–6) controls the number of fractional digits retained in the seconds field. An interval requires 16 bytes and supports a range of approximately -178,000,000 to +178,000,000 years.

Interval input formats

PostgreSQL accepts several syntaxes for interval literals:

-- Verbose (postgres) format
INTERVAL '1 year 2 months 3 days'
INTERVAL '2 weeks ago'
INTERVAL '3 hours 20 minutes'

-- ISO 8601 designator format
INTERVAL 'P6Y5M4DT3H2M1S'

-- ISO 8601 alternative format
INTERVAL 'P0006-05-04T03:02:01'

Interval arithmetic with dates and timestamps

Intervals are most useful when combined with TIMESTAMP or DATE values using arithmetic operators:

-- Timestamp from 1 year and 3 hours 20 minutes ago
SELECT
  now(),
  now() - INTERVAL '1 year 3 hours 20 minutes' AS "3 hrs 20 min ago last year";

-- Output:
-- now                            | 3 hrs 20 min ago last year
-- -------------------------------+-------------------------------------
-- 2024-01-31 21:34:52.242914-05  | 2023-01-31 18:14:52.242914-05
-- Interval arithmetic
SELECT INTERVAL '2h 50m' + INTERVAL '10m';   -- 03:00:00
SELECT INTERVAL '2h 50m' - INTERVAL '50m';   -- 02:00:00
SELECT 600 * INTERVAL '1 minute';            -- 10:00:00

Using INTERVAL in a table

Store event durations in an INTERVAL column and query against them:

CREATE TABLE event (
  id          SERIAL PRIMARY KEY,
  event_name  VARCHAR(255) NOT NULL,
  duration    INTERVAL NOT NULL
);

INSERT INTO event (event_name, duration) VALUES
  ('pgConf', '1 hour 30 minutes'),
  ('pgDAY',  '2 days 5 hours')
RETURNING *;

-- Retrieve events longer than one day
SELECT * FROM event WHERE duration > INTERVAL '1 day';

-- Sum all durations
SELECT SUM(duration) AS total_duration FROM event;

Extracting interval components

-- Extract minutes from an interval
SELECT EXTRACT(MINUTE FROM INTERVAL '5 hours 21 minutes');
-- Returns: 21

-- Extract components from a table column
SELECT
  event_name,
  duration,
  EXTRACT(DAY    FROM duration) AS days,
  EXTRACT(HOUR   FROM duration) AS hours,
  EXTRACT(MINUTE FROM duration) AS minutes
FROM event;

Converting intervals to strings

SELECT TO_CHAR(INTERVAL '17h 20m 05s', 'HH24:MI:SS');
-- Returns: 17:20:05

Adjusting interval values

SELECT justify_days(INTERVAL '30 days');     -- 1 mon
SELECT justify_hours(INTERVAL '24 hours');   -- 1 day
SELECT justify_interval(INTERVAL '1 year -1 hour');
-- Returns: 11 mons 29 days 23:00:00

Production tips

  • Use INTERVAL literals instead of integer day counts when doing date arithmetic — it makes intent explicit and handles month/year boundaries correctly.
  • Avoid storing intervals as plain integers (e.g. seconds) — you lose the ability to use PostgreSQL's built-in interval functions and comparisons.
  • Use justify_interval() before displaying durations to users so "30 days" shows as "1 month" rather than an ambiguous day count.
  • When filtering by recency (e.g. WHERE created_at >= now() - INTERVAL '30 days'), verify the query uses an index on the timestamp column with EXPLAIN.

Reference: PostgreSQL documentation — Date/Time types.

Continue in PostgreSQL Data Types: TIME.

Related in this section: Boolean · CHAR, VARCHAR, and TEXT · NUMERIC

Frequently Asked Questions

What is the PostgreSQL INTERVAL data type?

INTERVAL stores a duration rather than a point in time. It can represent periods like "2 months", "3 hours 20 minutes", or "1 year 4 days". Internally PostgreSQL stores intervals as months, days, and seconds. The type requires 16 bytes and supports a range of roughly -178,000,000 to +178,000,000 years.

How do you write an INTERVAL literal in PostgreSQL?

PostgreSQL accepts several formats. The verbose format is: INTERVAL '1 year 2 months 3 days'. The ISO 8601 designator format is: INTERVAL 'P1Y2M3DT4H5M6S'. The ISO 8601 alternative format is: INTERVAL 'P0001-02-03T04:05:06'. The verbose format is the most readable for humans.

How do you add or subtract an interval from a timestamp?

Use standard arithmetic operators. For example: SELECT now() - INTERVAL '1 year 3 hours 20 minutes'; returns the timestamp from one year and 3 hours 20 minutes ago. You can also add intervals: SELECT created_at + INTERVAL '30 days' AS expires_at FROM subscriptions;

How do you extract parts from an interval in PostgreSQL?

Use the EXTRACT() function: SELECT EXTRACT(MINUTE FROM INTERVAL '5 hours 21 minutes'); returns 21. You can extract year, month, day, hour, minute, and second fields. For normalizing intervals, use justify_days() to convert 30-day periods to months, justify_hours() to convert 24-hour periods to days, or justify_interval() to apply both adjustments.

How do you control how intervals are displayed?

Use SET intervalstyle to choose the output format. Options are: postgres (default, e.g. "6 years 5 mons 4 days 03:02:01"), sql_standard (e.g. "+6-5 +4 +3:02:01"), postgres_verbose (e.g. "@ 6 years 5 mons 4 days 3 hours 2 mins 1 sec"), and iso_8601 (e.g. "P6Y5M4DT3H2M1S"). You can also convert an interval to a formatted string using TO_CHAR(interval, format).