Sebuah timestamp menyimpan momen secara utuh — 2026-01-15 08:30:45 — tetapi laporan bisnis hampir tidak pernah bertanya secara utuh seperti itu. Laporan penjualan ingin tahu “berapa transaksi di bulan Januari”, aplikasi memberi diskon ulang tahun dengan “berapa usia pelanggan ini”, dan tampilan web ingin menampilkan “15 Januari 2026, pukul 08.30 WIB”. Ketiga kebutuhan itu adalah pekerjaan fungsi tanggal dan waktu: membaca komponen, memotong ke awal periode, menghitung jarak, dan memformat tampilan. Chapter ini mengulas fungsi-fungsi tersebut dengan literal tetap seperti TIMESTAMP '2026-01-15 08:30:45' di seluruh contoh, sehingga setiap output bisa Anda reproduksi persis di server latihan.
Membaca Komponen dengan EXTRACT #
EXTRACT(bagian FROM nilai) mengambil satu komponen dari timestamp, date, atau interval dan mengembalikannya sebagai angka. Komponen yang paling sering dipakai:
| Komponen | Makna | Contoh untuk 2026-01-15 08:30:45 | Hasil |
|---|---|---|---|
YEAR | Tahun | EXTRACT(YEAR FROM ts) | 2026 |
MONTH | Bulan 1-12 | EXTRACT(MONTH FROM ts) | 1 |
DAY | Tanggal 1-31 | EXTRACT(DAY FROM ts) | 15 |
HOUR | Jam 0-23 | EXTRACT(HOUR FROM ts) | 8 |
MINUTE | Menit 0-59 | EXTRACT(MINUTE FROM ts) | 30 |
DOW | Hari dalam minggu, 0 = Minggu | EXTRACT(DOW FROM ts) | 4 (Kamis) |
-- EXTRACT mengambil satu komponen dari timestamp
SELECT EXTRACT(YEAR FROM TIMESTAMP '2026-01-15 08:30:45') AS tahun,
EXTRACT(MONTH FROM TIMESTAMP '2026-01-15 08:30:45') AS bulan,
EXTRACT(DAY FROM TIMESTAMP '2026-01-15 08:30:45') AS hari,
EXTRACT(HOUR FROM TIMESTAMP '2026-01-15 08:30:45') AS jam;
-- DOW: hari dalam minggu, 0 = Minggu sampai 6 = Sabtu
SELECT EXTRACT(DOW FROM DATE '2026-01-15') AS hari_kamis,
EXTRACT(DOW FROM DATE '2026-01-18') AS hari_minggu;
-- date_part() adalah padanan EXTRACT dengan argumen string
SELECT date_part('month', TIMESTAMP '2026-01-15 08:30:45') AS dp_bulan,
EXTRACT(MONTH FROM TIMESTAMP '2026-01-15 08:30:45') AS ex_bulan;Output yang Dihasilkan:
tahun | bulan | hari | jam
-------+-------+------+-----
2026 | 1 | 15 | 8
(1 row)
hari_kamis | hari_minggu
------------+-------------
4 | 0
(1 row)
dp_bulan | ex_bulan
----------+----------
1 | 1
(1 row)Query pertama memperlihatkan empat komponen inti yang diambil dari satu literal tetap. Query kedua memperkenalkan DOW — 15 Januari 2026 jatuh pada Kamis sehingga hasilnya 4, dan 18 Januari 2026 adalah Minggu sehingga hasilnya 0. Penomoran yang dimulai dari Minggu ini penting diingat ketika menyaring hari kerja: kondisi “Senin sampai Jumat” ditulis WHERE EXTRACT(DOW FROM waktu) BETWEEN 1 AND 5. Query ketiga membuktikan bahwa date_part('month', ...) dan EXTRACT(MONTH FROM ...) menghasilkan angka yang sama — date_part adalah notasi fungsi lawas, sedangkan EXTRACT adalah sintaks standar SQL; pilih salah satu dan gunakan secara konsisten.
Memotong ke Awal Periode dengan date_trunc #
Jika EXTRACT mengambil satu komponen sebagai angka, date_trunc('periode', nilai) memotong timestamp ke awal periode dan tetap mengembalikan timestamp utuh. Ini adalah fungsi kunci untuk mengelompokkan data ke dalam ember waktu — per jam, per hari, per bulan:
| Ekspresi | Hasil | Yang dipotong |
|---|---|---|
date_trunc('hour', ts) | 2026-01-15 08:00:00 | Menit dan detik |
date_trunc('day', ts) | 2026-01-15 00:00:00 | Seluruh jam |
date_trunc('month', ts) | 2026-01-01 00:00:00 | Tanggal ke awal bulan |
date_trunc('year', ts) | 2026-01-01 00:00:00 | Semua ke awal tahun |
-- date_trunc() memotong timestamp ke awal periode yang diminta
SELECT date_trunc('month', TIMESTAMP '2026-01-15 08:30:45') AS awal_bulan,
date_trunc('day', TIMESTAMP '2026-01-15 08:30:45') AS awal_hari,
date_trunc('hour', TIMESTAMP '2026-01-15 08:30:45') AS awal_jam,
date_trunc('year', TIMESTAMP '2026-01-15 08:30:45') AS awal_tahun;
-- EXTRACT menghasilkan angka, date_trunc menghasilkan timestamp
SELECT EXTRACT(MONTH FROM TIMESTAMP '2026-01-15 08:30:45') AS bulan_angka,
date_trunc('month', TIMESTAMP '2026-01-15 08:30:45') AS bulan_timestamp;
-- pemakaian pada tabel: lompat ke awal bulan untuk tiap baris
CREATE TABLE kunjungan (
id integer PRIMARY KEY,
waktu timestamp NOT NULL
);
INSERT INTO kunjungan (id, waktu) VALUES
(1, TIMESTAMP '2026-01-05 09:15:00'),
(2, TIMESTAMP '2026-01-28 16:40:00'),
(3, TIMESTAMP '2026-02-02 10:05:00');
SELECT id, waktu, date_trunc('month', waktu) AS awal_bulan
FROM kunjungan
ORDER BY id;Output yang Dihasilkan:
awal_bulan | awal_hari | awal_jam | awal_tahun
---------------------+---------------------+---------------------+---------------------
2026-01-01 00:00:00 | 2026-01-15 00:00:00 | 2026-01-15 08:00:00 | 2026-01-01 00:00:00
(1 row)
bulan_angka | bulan_timestamp
-------------+---------------------
1 | 2026-01-01 00:00:00
(1 row)
CREATE TABLE
INSERT 0 3
id | waktu | awal_bulan
----+---------------------+---------------------
1 | 2026-01-05 09:15:00 | 2026-01-01 00:00:00
2 | 2026-01-28 16:40:00 | 2026-01-01 00:00:00
3 | 2026-02-02 10:05:00 | 2026-02-01 00:00:00
(3 rows)Query kedua menunjukkan perbedaan hasil yang paling mendasar: EXTRACT(MONTH FROM ...) mengembalikan angka 1, sedangkan date_trunc('month', ...) mengembalikan timestamp 2026-01-01 00:00:00. Pada tabel kunjungan, dua baris pertama yang jatuh di bulan yang berbeda hari (5 dan 28 Januari) keduanya terpotong ke 2026-01-01 00:00:00 — itulah sifat yang membuat date_trunc menjadi pasangan sempurna untuk GROUP BY ketika kita ingin menghitung jumlah kunjungan per bulan. Pola lengkap GROUP BY date_trunc(...) dibahas tuntas di section Agregasi Data
.
Menghitung Jarak Waktu dengan age dan justify #
Fungsi AGE(nilai_akhir, nilai_awal) menghitung jarak dua titik waktu dan menghasilkan interval yang sudah dinormalisasi menjadi tahun-bulan-hari — format yang persis seperti cara manusia membaca usia:
-- AGE() menghitung jarak dua waktu sebagai interval tahun-bulan-hari
SELECT AGE(TIMESTAMP '2026-03-20', TIMESTAMP '1998-07-25') AS usia_pelanggan;
-- jarak lebih pendek: di bawah dua tahun
SELECT AGE(DATE '2026-03-20', DATE '2024-12-25') AS selisih;
-- justify_hours(): rapikan jam yang melewati batas 24 menjadi hari
SELECT INTERVAL '30 hours' AS mentah,
justify_hours(INTERVAL '30 hours') AS jam_dirapikan;
-- justify_days(): rapikan hari menjadi bulan kalender
SELECT INTERVAL '45 days' AS mentah_hari,
justify_days(INTERVAL '45 days') AS hari_dirapikan;
-- justify_interval() merapikan hari dan jam sekaligus
SELECT justify_interval(INTERVAL '45 days 30 hours') AS lengkap;Output yang Dihasilkan:
usia_pelanggan
-------------------------
27 years 7 mons 26 days
(1 row)
selisih
-----------------------
1 year 2 mons 26 days
(1 row)
mentah | jam_dirapikan
----------+----------------
30:00:00 | 1 day 06:00:00
(1 row)
mentah_hari | hari_dirapikan
-------------+----------------
45 days | 1 mon 15 days
(1 row)
lengkap
------------------------
1 mon 16 days 06:00:00
(1 row)Usia pelanggan lahir 25 Juli 1998 per 20 Maret 2026 terbaca 27 years 7 mons 26 days — tahun dan bulan mengikuti kalender, bukan konversi 365 hari mekanis. Keluarga justify melanjutkan tema perapikan itu pada interval: justify_hours mengubah 30 hours menjadi 1 day 06:00:00, justify_days mengubah 45 days menjadi 1 mon 15 days, dan justify_interval merapikan keduanya sekaligus menjadi 1 mon 16 days 06:00:00. Aturan lengkap tipe interval — bulan yang mengikuti kalender, operasi antar tipe waktu — sudah dibedah di chapter Interval
.
Memformat Tampilan dengan to_char #
Fungsi to_char(nilai, pola) mengubah tanggal/waktu menjadi teks sesuai pola yang Anda susun sendiri. Tabel berikut merangkum pola yang paling sering dipakai untuk TIMESTAMP '2026-01-15 08:30:00':
| Pola | Makna | Hasil |
|---|---|---|
YYYY | Tahun empat digit | 2026 |
MM | Bulan dua digit | 01 |
DD | Tanggal dua digit | 15 |
Month | Nama bulan (rata kanan 9 karakter) | January |
FMMonth | Nama bulan tanpa pengisi spasi | January |
Day | Nama hari (rata kanan 9 karakter) | Thursday |
DY | Nama hari tiga huruf kapital | THU |
HH24 | Jam 00-23 | 08 |
MI | Menit 00-59 | 30 |
AM | Penanda pagi/siang | AM |
-- pola dasar: susunan tanggal
SELECT to_char(TIMESTAMP '2026-01-15 08:30:00', 'YYYY-MM-DD') AS pola_iso,
to_char(TIMESTAMP '2026-01-15 08:30:00', 'DD/MM/YYYY') AS pola_hari_bulan;
-- nama bulan dan hari (selalu dalam bahasa Inggris di server ini)
SELECT to_char(TIMESTAMP '2026-01-15 08:30:00', 'Month') AS bulan_panjang,
to_char(TIMESTAMP '2026-01-15 08:30:00', 'FMMonth') AS bulan_tanpa_pad,
to_char(TIMESTAMP '2026-01-15 08:30:00', 'Day') AS hari_panjang,
to_char(TIMESTAMP '2026-01-15 08:30:00', 'DY') AS hari_singkat;
-- jam dan menit dalam dua format
SELECT to_char(TIMESTAMP '2026-01-15 08:30:00', 'HH24:MI') AS jam_24,
to_char(TIMESTAMP '2026-01-15 08:30:00', 'HH12:MI AM') AS jam_12;
-- teks tetap ditulis di dalam kutip ganda
SELECT to_char(TIMESTAMP '2026-01-15 08:30:00', '"jam" HH24 "lewat" MI "menit"') AS kalimat;Output yang Dihasilkan:
pola_iso | pola_hari_bulan
------------+-----------------
2026-01-15 | 15/01/2026
(1 row)
bulan_panjang | bulan_tanpa_pad | hari_panjang | hari_singkat
---------------+-----------------+--------------+--------------
January | January | Thursday | THU
(1 row)
jam_24 | jam_12
--------+----------
08:30 | 08:30 AM
(1 row)
kalimat
-----------------------
jam 08 lewat 30 menit
(1 row)Empat perilaku patut dibedah dari hasil di atas:
'Month'vs'FMMonth': tanpa prefiksFM, nama bulan dan hari dirata-kanan ke lebar 9 karakter — hasilJanuarymemuat dua spasi di belakangnya agar semua nama bulan sama lebar. PrefiksFM(fill mode) memangkas pengisi itu.- Nama selalu bahasa Inggris: server latihan berjalan dengan locale
C, sehinggato_charmenghasilkanJanuarydanThursday, bukanJanuaridanKamis. Nama lokal bergantung pada konfigurasilc_timeyang ditetapkan saat database dibuat. HH12:MI AM: format 12 jam lengkap dengan penandaAM/PM;08:30:00menjadi08:30 AM.- Teks tetap dalam kutip ganda: bagian pola yang diapit
"..."dikeluarkan apa adanya, sehingga kalimatjam 08 lewat 30 menitbisa dibangun langsung dari satu pola.
Parsing String menjadi Tanggal dengan to_date dan to_timestamp #
Arah sebaliknya dari to_char adalah parsing: menerima teks dari pengguna atau berkas ekspor, lalu mengubahnya menjadi date atau timestamptz. Pola parsing memakai kode yang sama seperti pola format:
-- to_date() mem-parsing string menjadi date
SELECT to_date('15-01-2026', 'DD-MM-YYYY') AS tanggal;
-- pemisah yang berbeda dari pola tetap diterima
SELECT to_date('15/01/2026', 'DD-MM-YYYY') AS pemisah_miring;
-- to_timestamp() mem-parsing string lengkap menjadi timestamptz
SELECT to_timestamp('15-01-2026 08:30', 'DD-MM-YYYY HH24:MI') AS waktu_lengkap;
-- round-trip: hasil to_char bisa diparsing balik
SELECT to_char(TIMESTAMP '2026-01-15 08:30:00', 'YYYY-MM-DD HH24:MI') AS teks,
to_timestamp('2026-01-15 08:30', 'YYYY-MM-DD HH24:MI') AS kembali;Output yang Dihasilkan:
tanggal
------------
2026-01-15
(1 row)
pemisah_miring
----------------
2026-01-15
(1 row)
waktu_lengkap
------------------------
2026-01-15 08:30:00+07
(1 row)
teks | kembali
------------------+------------------------
2026-01-15 08:30 | 2026-01-15 08:30:00+07
(1 row)String '15-01-2026' yang ambigu bagi DATE literal (lihat kembali jebakan DateStyle di chapter Date dan Time
) menjadi tidak ambigu lagi begitu pola DD-MM-YYYY disebutkan secara eksplisit — tanggal 15, bulan 01. Perhatikan dua hal dari hasil di atas: pemisah boleh berbeda antara data dan pola (15/01/2026 tetap terbaca), dan to_timestamp menghasilkan timestamptz yang ditampilkan dengan offset zona waktu sesi (+07). Query terakhir memperlihatkan pola round-trip: teks hasil to_char dengan pola YYYY-MM-DD HH24:MI berhasil diparsing kembali menjadi nilai waktu yang sama.
Meski toleran terhadap pemisah, parser tetap memvalidasi isi kalendernya:
-- tanggal 32 tidak pernah ada di kalender mana pun
SELECT to_date('32-01-2026', 'DD-MM-YYYY') AS gagal;Output yang Dihasilkan:
psql:to-date-error.sql:2: ERROR: date/time field value out of range: "32-01-2026"Error date/time field value out of range menegaskan bahwa parsing gagal pada tahap validasi nilai, bukan pada pola. Pola DD-MM-YYYY sah, tetapi tidak ada bulan dengan 32 hari. Bila data eksternal kerap menyampurkan format yang berbeda, parsinglah di satu tempat yang sama dengan pola eksplisit seperti ini — dan biarkan kolom tabel tetap bertipe date, bukan teks.
Itulah perangkat lengkap untuk membaca, memotong, menghitung, memformat, dan mem-parsing nilai waktu. Perhatikan bahwa sepanjang chapter ini kita bolak-balik mengubah wujud data — timestamp menjadi teks, teks menjadi tanggal, angka menjadi interval. transformasi antar tipe seperti itu punya aturan formalnya sendiri di PostgreSQL, dan itulah topik chapter berikutnya: Fungsi Konversi Tipe Data .
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
age-dan-justify.sql
Unduh berkas-- AGE() menghitung jarak dua waktu sebagai interval tahun-bulan-hari
SELECT AGE(TIMESTAMP '2026-03-20', TIMESTAMP '1998-07-25') AS usia_pelanggan;
-- jarak lebih pendek: di bawah dua tahun
SELECT AGE(DATE '2026-03-20', DATE '2024-12-25') AS selisih;
-- justify_hours(): rapikan jam yang melewati batas 24 menjadi hari
SELECT INTERVAL '30 hours' AS mentah,
justify_hours(INTERVAL '30 hours') AS jam_dirapikan;
-- justify_days(): rapikan hari menjadi bulan kalender
SELECT INTERVAL '45 days' AS mentah_hari,
justify_days(INTERVAL '45 days') AS hari_dirapikan;
-- justify_interval() merapikan hari dan jam sekaligus
SELECT justify_interval(INTERVAL '45 days 30 hours') AS lengkap;
date-trunc.sql
Unduh berkas-- date_trunc() memotong timestamp ke awal periode yang diminta
SELECT date_trunc('month', TIMESTAMP '2026-01-15 08:30:45') AS awal_bulan,
date_trunc('day', TIMESTAMP '2026-01-15 08:30:45') AS awal_hari,
date_trunc('hour', TIMESTAMP '2026-01-15 08:30:45') AS awal_jam,
date_trunc('year', TIMESTAMP '2026-01-15 08:30:45') AS awal_tahun;
-- EXTRACT menghasilkan angka, date_trunc menghasilkan timestamp
SELECT EXTRACT(MONTH FROM TIMESTAMP '2026-01-15 08:30:45') AS bulan_angka,
date_trunc('month', TIMESTAMP '2026-01-15 08:30:45') AS bulan_timestamp;
-- pemakaian pada tabel: lompat ke awal bulan untuk tiap baris
CREATE TABLE kunjungan (
id integer PRIMARY KEY,
waktu timestamp NOT NULL
);
INSERT INTO kunjungan (id, waktu) VALUES
(1, TIMESTAMP '2026-01-05 09:15:00'),
(2, TIMESTAMP '2026-01-28 16:40:00'),
(3, TIMESTAMP '2026-02-02 10:05:00');
SELECT id, waktu, date_trunc('month', waktu) AS awal_bulan
FROM kunjungan
ORDER BY id;
extract.sql
Unduh berkas-- EXTRACT mengambil satu komponen dari timestamp
SELECT EXTRACT(YEAR FROM TIMESTAMP '2026-01-15 08:30:45') AS tahun,
EXTRACT(MONTH FROM TIMESTAMP '2026-01-15 08:30:45') AS bulan,
EXTRACT(DAY FROM TIMESTAMP '2026-01-15 08:30:45') AS hari,
EXTRACT(HOUR FROM TIMESTAMP '2026-01-15 08:30:45') AS jam;
-- DOW: hari dalam minggu, 0 = Minggu sampai 6 = Sabtu
SELECT EXTRACT(DOW FROM DATE '2026-01-15') AS hari_kamis,
EXTRACT(DOW FROM DATE '2026-01-18') AS hari_minggu;
-- date_part() adalah padanan EXTRACT dengan argumen string
SELECT date_part('month', TIMESTAMP '2026-01-15 08:30:45') AS dp_bulan,
EXTRACT(MONTH FROM TIMESTAMP '2026-01-15 08:30:45') AS ex_bulan;
to-char.sql
Unduh berkas-- pola dasar: susunan tanggal
SELECT to_char(TIMESTAMP '2026-01-15 08:30:00', 'YYYY-MM-DD') AS pola_iso,
to_char(TIMESTAMP '2026-01-15 08:30:00', 'DD/MM/YYYY') AS pola_hari_bulan;
-- nama bulan dan hari (selalu dalam bahasa Inggris di server ini)
SELECT to_char(TIMESTAMP '2026-01-15 08:30:00', 'Month') AS bulan_panjang,
to_char(TIMESTAMP '2026-01-15 08:30:00', 'FMMonth') AS bulan_tanpa_pad,
to_char(TIMESTAMP '2026-01-15 08:30:00', 'Day') AS hari_panjang,
to_char(TIMESTAMP '2026-01-15 08:30:00', 'DY') AS hari_singkat;
-- jam dan menit dalam dua format
SELECT to_char(TIMESTAMP '2026-01-15 08:30:00', 'HH24:MI') AS jam_24,
to_char(TIMESTAMP '2026-01-15 08:30:00', 'HH12:MI AM') AS jam_12;
-- teks tetap ditulis di dalam kutip ganda
SELECT to_char(TIMESTAMP '2026-01-15 08:30:00', '"jam" HH24 "lewat" MI "menit"') AS kalimat;
to-date-dan-to-timestamp.sql
Unduh berkas-- to_date() mem-parsing string menjadi date
SELECT to_date('15-01-2026', 'DD-MM-YYYY') AS tanggal;
-- pemisah yang berbeda dari pola tetap diterima
SELECT to_date('15/01/2026', 'DD-MM-YYYY') AS pemisah_miring;
-- to_timestamp() mem-parsing string lengkap menjadi timestamptz
SELECT to_timestamp('15-01-2026 08:30', 'DD-MM-YYYY HH24:MI') AS waktu_lengkap;
-- round-trip: hasil to_char bisa diparsing balik
SELECT to_char(TIMESTAMP '2026-01-15 08:30:00', 'YYYY-MM-DD HH24:MI') AS teks,
to_timestamp('2026-01-15 08:30', 'YYYY-MM-DD HH24:MI') AS kembali;
to-date-error.sql
Unduh berkas-- tanggal 32 tidak pernah ada di kalender mana pun
SELECT to_date('32-01-2026', 'DD-MM-YYYY') AS gagal;