Chapter sebelumnya membahas kapan sebuah kejadian terjadi lewat tipe date, time, dan timestamp. Pertanyaan yang belum terjawab adalah berapa lama kejadian itu berlangsung — dan di situlah tipe interval berperan. interval menyimpan durasi, bukan titik waktu: 2 jam, 3 hari, atau 1 tahun 2 bulan. Dengan interval, aritmetika tanggal menjadi ekspresif — mulai + durasi menghitung waktu selesai sebuah sesi, selesai - mulai menghasilkan durasi, dan fungsi AGE() merangkum jarak dua tanggal ke bentuk tahun-bulan-hari. Chapter ini mengupas format input interval, perilaku aritmetikanya, hingga jebakan satuan bulan yang sering mengejutkan pemula.
Deklarasi dan Format Input #
interval adalah tipe bawaan PostgreSQL untuk durasi waktu dan ditulis tanpa parameter: cukup interval pada definisi kolom, atau INTERVAL '...' untuk sebuah literal. PostgreSQL menerima beberapa format input yang berbeda, lalu menyimpan serta menampilkan nilainya dalam satu bentuk kanonik yang seragam:
| Format input | Contoh | Hasil kanonik |
|---|---|---|
| Satuan berbahasa Inggris | '2 hours 30 minutes' | 02:30:00 |
| Gabungan hari dan jam | '1 day 03:00:00' | 1 day 03:00:00 |
| Notasi ISO 8601 | 'P1Y2M' | 1 year 2 mons |
| ISO 8601 dengan bagian waktu | 'P1YT3H' | 1 year 03:00:00 |
-- interval menerima beragam format input,
-- tetapi selalu menampilkan bentuk kanoniknya sendiri
SELECT '2 hours'::interval AS dua_jam;
SELECT '2 hours 30 minutes'::interval AS dua_setengah_jam;
SELECT '1 day 03:00:00'::interval AS hari_dan_jam;
SELECT '90 minutes'::interval AS sembilan_puluh_menit;
SELECT 'P1Y2M'::interval AS iso_tahun_bulan;
SELECT 'P1YT3H'::interval AS iso_campuran;Output yang Dihasilkan:
dua_jam
----------
02:00:00
(1 row)
dua_setengah_jam
------------------
02:30:00
(1 row)
hari_dan_jam
----------------
1 day 03:00:00
(1 row)
sembilan_puluh_menit
----------------------
01:30:00
(1 row)
iso_tahun_bulan
-----------------
1 year 2 mons
(1 row)
iso_campuran
-----------------
1 year 03:00:00
(1 row)Dua hal penting dari hasil di atas. Pertama, '90 minutes' dikembalikan sebagai 01:30:00 — PostgreSQL tidak menyimpan teks input mentah, melainkan menormalisasi satuan menit menjadi jam-menit-detik. Kedua, pada notasi ISO 8601 huruf P membuka durasi (period), Y dan M setelahnya berarti tahun dan bulan, sedangkan T memisahkan bagian waktu (jam, menit) dari bagian tanggal. Notasi ISO ini ringkas dan dikenali banyak bahasa pemrograman, sehingga sering dipakai untuk pertukaran durasi antar sistem.
Aritmetika Timestamp dan Interval #
Nilai interval benar-benar berguna saat dipertemukan dengan timestamp. Penjumlahan timestamp + interval menghasilkan timestamp baru; pengurangan dua timestamp menghasilkan interval; dan khusus dua nilai date, pengurangan menghasilkan jumlah hari sebagai integer, bukan interval:
-- timestamp + interval = timestamp baru
SELECT TIMESTAMP '2026-01-15 08:00:00' + INTERVAL '3 hours' AS selesai;
-- timestamp - timestamp = interval
SELECT TIMESTAMP '2026-03-01 08:00:00' - TIMESTAMP '2026-02-28 10:30:00' AS durasi;
-- date - date = jumlah hari (integer, bukan interval)
SELECT DATE '2026-03-01' - DATE '2026-02-01' AS selisih_hari;
CREATE TABLE jadwal (
id integer PRIMARY KEY,
kegiatan text NOT NULL,
mulai timestamp NOT NULL,
durasi interval NOT NULL
);
INSERT INTO jadwal (id, kegiatan, mulai, durasi) VALUES
(1, 'Sesi Pembukaan', TIMESTAMP '2026-01-15 08:00:00', INTERVAL '45 minutes'),
(2, 'Workshop Database', TIMESTAMP '2026-01-15 09:00:00', INTERVAL '2 hours'),
(3, 'Makan Siang Bersama', TIMESTAMP '2026-01-15 12:00:00', INTERVAL '1 hour 30 minutes');
SELECT id, kegiatan, mulai, durasi, mulai + durasi AS selesai
FROM jadwal
ORDER BY id;Output yang Dihasilkan:
selesai
---------------------
2026-01-15 11:00:00
(1 row)
durasi
----------
21:30:00
(1 row)
selisih_hari
--------------
28
(1 row)
CREATE TABLE
INSERT 0 3
id | kegiatan | mulai | durasi | selesai
----+---------------------+---------------------+----------+---------------------
1 | Sesi Pembukaan | 2026-01-15 08:00:00 | 00:45:00 | 2026-01-15 08:45:00
2 | Workshop Database | 2026-01-15 09:00:00 | 02:00:00 | 2026-01-15 11:00:00
3 | Makan Siang Bersama | 2026-01-15 12:00:00 | 01:30:00 | 2026-01-15 13:30:00
(3 rows)Mari kita bedah bagian-bagian pentingnya:
TIMESTAMP '2026-01-15 08:00:00' + INTERVAL '3 hours': server menggeser titik waktu tiga jam ke depan dan menghasilkan2026-01-15 11:00:00— pola dasar perhitungan waktu selesai.TIMESTAMP '2026-03-01 08:00:00' - TIMESTAMP '2026-02-28 10:30:00': selisih dua titik waktu berupainterval21:30:00, bukan angka — durasi menyertakan satu hari penuh plus 21,5 jam karena Februari 2026 memiliki 28 hari.DATE '2026-03-01' - DATE '2026-02-01': menghasilkan28bertipeinteger— aturan khusus PostgreSQL untuk dua nilaidate.- Tabel
jadwal: kolomdurasibertipeinterval, dan query penutup menghitungmulai + durasi AS selesailangsung di daftar kolom — Sesi Pembukaan pukul 08:00:00 dengan durasi 45 menit berakhir pukul 08:45:00.
Bulan Tidak Selalu 30 Hari #
Satuan tahun dan bulan pada interval mengikuti kalender, sedangkan satuan hari dan yang lebih kecil bersifat tetap. Akibatnya, menambahkan INTERVAL '1 month' dan INTERVAL '30 days' ke tanggal yang sama bisa menghasilkan tanggal yang berbeda:
-- satuan bulan mengikuti kalender, bukan jumlah hari tetap
SELECT DATE '2026-01-31' + INTERVAL '1 month' AS tambah_satu_bulan;
SELECT DATE '2026-01-31' + INTERVAL '30 days' AS tambah_30_hari;
SELECT DATE '2026-01-15' + INTERVAL '2 months' AS dua_bulan_lagi;
-- justify_hours merapikan jam yang melewati batas 24
SELECT INTERVAL '30 hours' AS mentah;
SELECT JUSTIFY_HOURS(INTERVAL '30 hours') AS dirapikan;Output yang Dihasilkan:
tambah_satu_bulan
---------------------
2026-02-28 00:00:00
(1 row)
tambah_30_hari
---------------------
2026-03-02 00:00:00
(1 row)
dua_bulan_lagi
---------------------
2026-03-15 00:00:00
(1 row)
mentah
----------
30:00:00
(1 row)
dirapikan
----------------
1 day 06:00:00
(1 row)Tanggal 31 Januari 2026 ditambah satu bulan menghasilkan 28 Februari 2026 — hasil dipotong ke hari terakhir Februari karena 31 Februari tidak ada. Sebaliknya, menambah 30 hari melomat ke 2 Maret 2026. Kedua operasi itu sah, tetapi maknanya berbeda: “bulan berikutnya di kalender” lawan “30 hari kemudian”. Perhatikan juga INTERVAL '30 hours' yang tampil apa adanya sebagai 30:00:00; fungsi JUSTIFY_HOURS() yang merapikannya menjadi 1 day 06:00:00.
Mengambil Komponen Interval dengan EXTRACT #
Fungsi EXTRACT(bagian FROM interval) mengambil satu komponen durasi — jam, menit, hari, tahun — sebagai angka. Karena EXTRACT bekerja pada bentuk kanonik yang sudah dinormalisasi, hasilnya kadang berbeda dari teks input aslinya:
-- EXTRACT bekerja pada bentuk kanonik interval,
-- bukan pada teks input aslinya
SELECT EXTRACT(HOUR FROM INTERVAL '90 minutes') AS jam;
SELECT EXTRACT(MINUTE FROM INTERVAL '90 minutes') AS menit;
SELECT EXTRACT(YEAR FROM INTERVAL 'P18M') AS tahun;
SELECT EXTRACT(DAY FROM INTERVAL '1 day 03:00:00') AS hari;Output yang Dihasilkan:
jam
-----
1
(1 row)
menit
-------
30
(1 row)
tahun
-------
1
(1 row)
hari
------
1
(1 row)Nilai INTERVAL '90 minutes' tersimpan sebagai 01:30:00, sehingga EXTRACT(HOUR ...) menghasilkan 1 dan EXTRACT(MINUTE ...) menghasilkan 30 — bukan 90. Demikian pula 'P18M' dinormalisasi menjadi 1 tahun 6 bulan sehingga komponen tahunnya bernilai 1. Aturan praktisnya: bayangkan bentuk kanonik dulu (01:30:00, 1 year 6 mons), baru baca komponennya lewat EXTRACT.
AGE dan Perkalian Interval #
Fungsi AGE(akhir, awal) menghitung jarak dua titik waktu sekaligus menormalisasinya ke bentuk tahun-bulan-hari — persis cara manusia membaca umur. Di samping itu, interval mendukung perkalian dan pembagian dengan angka:
-- AGE() menghitung jarak dua waktu sebagai interval
-- yang sudah dinormalisasi ke tahun-bulan-hari
SELECT AGE(TIMESTAMP '2026-01-15', TIMESTAMP '2024-03-10') AS jarak_acara;
SELECT AGE(DATE '2026-01-15', DATE '1990-08-17') AS umur_pelanggan;
-- interval bisa dikalikan dan dibagi skalar
SELECT 3 * INTERVAL '45 minutes' AS tiga_sesi;
SELECT INTERVAL '2 hours' / 4 AS jeda_per_sesi;Output yang Dihasilkan:
jarak_acara
-----------------------
1 year 10 mons 5 days
(1 row)
umur_pelanggan
-------------------------
35 years 4 mons 29 days
(1 row)
tiga_sesi
-----------
02:15:00
(1 row)
jeda_per_sesi
---------------
00:30:00
(1 row)Panggilan AGE(...) pertama menghasilkan 1 year 10 mons 5 days — jarak dari 10 Maret 2024 ke 15 Januari 2026 dibaca sebagai kalender, bukan sekadar jumlah hari mentah. Panggilan kedua memperlihatkan pola klasik kolom umur pelanggan: 35 years 4 mons 29 days sejak 17 Agustus 1990. Perkalian 3 * INTERVAL '45 minutes' menghasilkan 02:15:00 — berguna saat satu durasi berulang, misalnya tiga sesi workshop tanpa jeda.
Input Interval yang Tidak Valid #
Sama seperti tipe tanggal, interval menolak teks yang tidak bisa ditafsirkan sebagai durasi. Input seperti 'besok' gagal diproses sejak awal:
SELECT INTERVAL 'besok';Output yang Dihasilkan:
psql:interval-invalid.sql:1: ERROR: invalid input syntax for type interval: "besok"
LINE 1: SELECT INTERVAL 'besok';
^Error invalid input syntax for type interval menandakan teks tersebut tidak cocok dengan pola satuan waktu mana pun yang dikenali parser. Daftar satuan yang diterima antara lain year/years, month/months, day/days, hour/hours, minute/minutes, second/seconds, beserta singkatan ISO 8601 — teks bebas seperti nama hari tidak termasuk.
Kapan Interval Dipakai #
Rangkuman pola pemakaian interval yang paling sering muncul di aplikasi nyata:
| Kebutuhan | Pola pemakaian |
|---|---|
| Penjadwalan acara | Kolom durasi interval, lalu mulai + durasi untuk waktu selesai |
| Durasi kejadian | selesai - mulai menghasilkan interval yang bisa disimpan |
| Batas waktu layanan (SLA) | Nilai ambang seperti INTERVAL '15 minutes' dibandingkan dengan durasi nyata |
| Umur data atau pelanggan | AGE(tanggal_acuan, tanggal_lahir) dibaca sebagai tahun-bulan-hari |
Dengan interval, seluruh pertanyaan durasi dan pergeseran waktu terjawab tanpa menghitung detik secara manual. Topik berikutnya meninggalkan dunia waktu dan beralih ke identitas baris: tipe UUID yang memungkinkan setiap baris memiliki identifier unik tanpa koordinasi antar server.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
age-dan-skala.sql
Unduh berkas-- AGE() menghitung jarak dua waktu sebagai interval
-- yang sudah dinormalisasi ke tahun-bulan-hari
SELECT AGE(TIMESTAMP '2026-01-15', TIMESTAMP '2024-03-10') AS jarak_acara;
SELECT AGE(DATE '2026-01-15', DATE '1990-08-17') AS umur_pelanggan;
-- interval bisa dikalikan dan dibagi skalar
SELECT 3 * INTERVAL '45 minutes' AS tiga_sesi;
SELECT INTERVAL '2 hours' / 4 AS jeda_per_sesi;
aritmetika.sql
Unduh berkas-- timestamp + interval = timestamp baru
SELECT TIMESTAMP '2026-01-15 08:00:00' + INTERVAL '3 hours' AS selesai;
-- timestamp - timestamp = interval
SELECT TIMESTAMP '2026-03-01 08:00:00' - TIMESTAMP '2026-02-28 10:30:00' AS durasi;
-- date - date = jumlah hari (integer, bukan interval)
SELECT DATE '2026-03-01' - DATE '2026-02-01' AS selisih_hari;
CREATE TABLE jadwal (
id integer PRIMARY KEY,
kegiatan text NOT NULL,
mulai timestamp NOT NULL,
durasi interval NOT NULL
);
INSERT INTO jadwal (id, kegiatan, mulai, durasi) VALUES
(1, 'Sesi Pembukaan', TIMESTAMP '2026-01-15 08:00:00', INTERVAL '45 minutes'),
(2, 'Workshop Database', TIMESTAMP '2026-01-15 09:00:00', INTERVAL '2 hours'),
(3, 'Makan Siang Bersama', TIMESTAMP '2026-01-15 12:00:00', INTERVAL '1 hour 30 minutes');
SELECT id, kegiatan, mulai, durasi, mulai + durasi AS selesai
FROM jadwal
ORDER BY id;
bulan-vs-hari.sql
Unduh berkas-- satuan bulan mengikuti kalender, bukan jumlah hari tetap
SELECT DATE '2026-01-31' + INTERVAL '1 month' AS tambah_satu_bulan;
SELECT DATE '2026-01-31' + INTERVAL '30 days' AS tambah_30_hari;
SELECT DATE '2026-01-15' + INTERVAL '2 months' AS dua_bulan_lagi;
-- justify_hours merapikan jam yang melewati batas 24
SELECT INTERVAL '30 hours' AS mentah;
SELECT JUSTIFY_HOURS(INTERVAL '30 hours') AS dirapikan;
ekstraksi.sql
Unduh berkas-- EXTRACT bekerja pada bentuk kanonik interval,
-- bukan pada teks input aslinya
SELECT EXTRACT(HOUR FROM INTERVAL '90 minutes') AS jam;
SELECT EXTRACT(MINUTE FROM INTERVAL '90 minutes') AS menit;
SELECT EXTRACT(YEAR FROM INTERVAL 'P18M') AS tahun;
SELECT EXTRACT(DAY FROM INTERVAL '1 day 03:00:00') AS hari;
format-input.sql
Unduh berkas-- interval menerima beragam format input,
-- tetapi selalu menampilkan bentuk kanoniknya sendiri
SELECT '2 hours'::interval AS dua_jam;
SELECT '2 hours 30 minutes'::interval AS dua_setengah_jam;
SELECT '1 day 03:00:00'::interval AS hari_dan_jam;
SELECT '90 minutes'::interval AS sembilan_puluh_menit;
SELECT 'P1Y2M'::interval AS iso_tahun_bulan;
SELECT 'P1YT3H'::interval AS iso_campuran;
interval-invalid.sql
Unduh berkasSELECT INTERVAL 'besok';