Tanggal lahir pelanggan, jam pengiriman pesanan, waktu transaksi persis — kolom-kolom waktu ada di setiap database serius, dan begitu pula jebakannya: tanggal yang ambigu (01/02/2026 itu 1 Februari atau 2 Januari?), tanggal yang tidak pernah ada (30 Februari), dan zona waktu yang membuat jam “yang sama” berbeda artinya di Jakarta dan Tokyo. PostgreSQL menyediakan empat tipe utama untuk menjawabnya, semuanya dengan resolusi sampai mikrodetik. Chapter ini membedah keempatnya, menetapkan format penulisan yang aman, lalu menguji perilaku zona waktu langsung di server latihan kita yang berzona waktu Asia/Jakarta.
Empat Tipe Waktu Utama #
| Tipe | Penyimpanan | Rentang / resolusi | Kegunaan |
|---|---|---|---|
date | 4 byte | 4713 SM sampai 5874897 M, resolusi 1 hari | Tanggal kalender tanpa jam |
time | 8 byte | 00:00:00 sampai 24:00:00, mikrodetik | Jam dalam sehari, tanpa tanggal |
timestamp | 8 byte | 4713 SM sampai 294276 M, mikrodetik | Tanggal dan jam, tanpa zona |
timestamptz | 8 byte | Sama dengan timestamp, mikrodetik | Tanggal dan jam dengan zona waktu |
timestamptz adalah alias resmi untuk timestamp with time zone. Nama itu sedikit menyesatkan — seperti yang kita buktikan nanti, tipe ini tidak menyimpan zona waktunya; ia menyimpan titik waktu absolut lalu menampilkannya mengikuti zona waktu sesi. Untuk jam kerja berulang setiap hari (jam buka toko “08:00”), time adalah pilihan tepat; untuk momen historis (“pesanan dibuat jam 07:45 tanggal 15 Januari”), timestamp; dan untuk momen yang harus dipahami sama oleh pengguna di semua zona (jadwal rilis, log server), timestamptz.
Membuat Tabel dengan date, time, dan timestamp #
Tabel pesanan berikut memakai ketiga tipe tanpa zona dalam perannya masing-masing — tanggal pesan, jam kirim harian, dan momen pesanan dibuat:
CREATE TABLE pesanan (
id integer PRIMARY KEY,
pelanggan text NOT NULL,
tanggal_pesan date NOT NULL,
jam_kirim time NOT NULL,
dibuat_pada timestamp NOT NULL
);
INSERT INTO pesanan (id, pelanggan, tanggal_pesan, jam_kirim, dibuat_pada) VALUES
(1, 'Budi', DATE '2026-01-15', TIME '08:30:00', TIMESTAMP '2026-01-15 07:45:12'),
(2, 'Siti', DATE '2026-01-16', TIME '13:05:30', TIMESTAMP '2026-01-16 12:40:00'),
(3, 'Toko Maju Jaya', DATE '2026-02-01', TIME '09:15:00', TIMESTAMP '2026-02-01 09:00:00');
SELECT id, pelanggan, tanggal_pesan, jam_kirim, dibuat_pada
FROM pesanan
ORDER BY id;Output yang Dihasilkan:
CREATE TABLE
INSERT 0 3
id | pelanggan | tanggal_pesan | jam_kirim | dibuat_pada
----+----------------+---------------+-----------+---------------------
1 | Budi | 2026-01-15 | 08:30:00 | 2026-01-15 07:45:12
2 | Siti | 2026-01-16 | 13:05:30 | 2026-01-16 12:40:00
3 | Toko Maju Jaya | 2026-02-01 | 09:15:00 | 2026-02-01 09:00:00
(3 rows)Literalnya ditulis dengan kata kunci tipe di depan string berkutip — DATE '2026-01-15', TIME '08:30:00', TIMESTAMP '2026-01-15 07:45:12' — bentuk eksplisit yang sekaligus memastikan string diterjemahkan sesuai tipe yang kita maksud. Nilai yang dikembalikan server tampil dalam format ISO: tanggal 2026-01-15, jam 08:30:00, dan gabungan keduanya untuk timestamp. Di aplikasi nyata kolom seperti dibuat_pada biasanya diisi fungsi now(); di seluruh contoh chapter ini kita memakai literal tetap supaya hasilnya dapat direproduksi ulang persis.
Literal ISO 8601 dan Jebakan Format #
Format paling aman untuk tanggal dan waktu adalah ISO 8601: tahun dulu, lalu bulan, lalu tanggal — 2026-01-15 tidak mungkin dibaca dua cara. PostgreSQL menerima format lain, dan di situlah bahaya menyusul:
SELECT DATE '2026-01-15' AS tanggal,
TIME '08:30:00' AS jam,
TIMESTAMP '2026-01-15 08:30:00' AS waktu;
SELECT TIMESTAMP '2026-01-15 08:30:00.123456' AS presisi_mikrodetik;
SELECT DATE '01/15/2026' AS format_ambigu;Output yang Dihasilkan:
tanggal | jam | waktu
------------+----------+---------------------
2026-01-15 | 08:30:00 | 2026-01-15 08:30:00
(1 row)
presisi_mikrodetik
----------------------------
2026-01-15 08:30:00.123456
(1 row)
format_ambigu
---------------
2026-01-15
(1 row)Query pertama memperlihatkan ketiga literal ISO inti; nilai-nilainya disimpan dan ditampilkan utuh. Query kedua menunjukkan presisi timestamp hingga enam digit pecahan detik — mikrodetik — yang berlaku juga untuk time dan timestamptz. Query ketiga adalah peringatan berjalan: string '01/15/2026' berhasil dibaca sebagai 15 Januari 2026 karena konfigurasi DateStyle server latihan kita adalah ISO, MDY (bulan dulu, tanggal kemudian); di server dengan gaya Eropa DMY, string yang sama justru error dan '15/01/2026' yang sah.
Tanggal yang Tidak Ada #
Kalender punya aturan yang tidak bisa ditawar: Februari 2026 hanya sampai tanggal 28 (2026 bukan tahun kabisat). Server menolak tanggal karangan tanpa kompromi:
SELECT DATE '2026-02-30' AS tanggal_tidak_ada;Output yang Dihasilkan:
psql:tanggal-tidak-valid.sql:1: ERROR: date/time field value out of range: "2026-02-30"
LINE 1: SELECT DATE '2026-02-30' AS tanggal_tidak_ada;
^Error date/time field value out of range muncul pada tahap parsing — nilai itu tidak pernah sampai disimpan, baik lewat SELECT seperti di atas maupun lewat INSERT ke kolom date. Validasi kalender mencakup jumlah hari tiap bulan dan tahun kabisat, jadi DATE '2028-02-29' sah sementara DATE '2026-02-29' ditolak, dan DATE '2026-04-31' gagal seperti 2026-02-30.
Zona Waktu dan timestamptz #
Inilah bagian yang paling sering disalahpahami. timestamptz tidak menyimpan zona waktu — ia menyimpan titik waktu absolut (secara internal: berapa mikrodetik sejak tahun 2000 UTC), lalu pada saat ditampilkan mengonversinya ke zona waktu sesi. Server latihan kita berzona waktu Asia/Jakarta, dan semua output di bawah ini ditampilkan di zona itu:
SELECT TIMESTAMPTZ '2026-01-15 08:00:00+07' AS waktu_jakarta;
SELECT TIMESTAMPTZ '2026-01-15 08:00:00+07' AT TIME ZONE 'Asia/Tokyo' AS waktu_tokyo,
TIMESTAMPTZ '2026-01-15 08:00:00+07' AT TIME ZONE 'UTC' AS waktu_utc;
SELECT TIMESTAMP '2026-01-15 08:00:00' AT TIME ZONE 'Asia/Jakarta' AS diberi_zona_waktu;Output yang Dihasilkan:
waktu_jakarta
------------------------
2026-01-15 08:00:00+07
(1 row)
waktu_tokyo | waktu_utc
---------------------+---------------------
2026-01-15 10:00:00 | 2026-01-15 01:00:00
(1 row)
diberi_zona_waktu
------------------------
2026-01-15 08:00:00+07
(1 row)Mari kita bedah tiga hasil itu:
- Query pertama: literal
timestamptzmemakai offset+07(WIB). Ditampilkan di sesi Asia/Jakarta, hasilnya2026-01-15 08:00:00+07— jam dinding Jakarta untuk momen itu. Nilai yang sama akan tampil2026-01-15 02:00:00+01di sesi zona Eropa Barat; momennya identik, tampilannya mengikuti sesi. - Query kedua:
AT TIME ZONEmengubahtimestamptzmenjaditimestamppolos berisi jam dinding di zona yang disebut. Momen pukul 08:00 WIB adalah 10:00 waktu Tokyo (WIB+2) dan 01:00 UTC — ketiga jam itu menunjuk detik yang sama. - Query ketiga: arah sebaliknya —
timestamppolos yang diberi makna zona.08:00:00diartikan sebagai jam dinding Jakarta dan hasilnyatimestamptzdengan offset+07.
Aritmetika Tanggal Sekilas #
Nilai tanggal dan waktu bisa dihitung: menambah dan mengurangi hari, mengukur selisih, bahkan melangkah per bulan. Empat contoh ini sekaligus menjadi jembatan ke chapter berikutnya:
SELECT DATE '2026-01-15' + 7 AS minggu_depan,
DATE '2026-01-15' - 7 AS minggu_lalu;
SELECT DATE '2026-01-31' - DATE '2026-01-15' AS selisih_hari;
SELECT DATE '2026-01-15' + INTERVAL '1 month' AS bulan_depan;
SELECT TIMESTAMP '2026-01-15 18:00:00' - TIMESTAMP '2026-01-15 08:30:00' AS durasi;Output yang Dihasilkan:
minggu_depan | minggu_lalu
--------------+-------------
2026-01-22 | 2026-01-08
(1 row)
selisih_hari
--------------
16
(1 row)
bulan_depan
---------------------
2026-02-15 00:00:00
(1 row)
durasi
----------
09:30:00
(1 row)Aturan dasarnya: date + integer menghasilkan date tujuh hari ke depan atau ke belakang; date - date menghasilkan bilangan hari (16 hari antara 15 dan 31 Januari); dan pengurangan dua timestamp menghasilkan interval — tipe khusus berisi durasi 09:30:00 (9 jam 30 menit). Kata kunci INTERVAL '1 month' pada query ketiga memperkenalkan tipe itu: penambah durasi kalender yang mengerti bahwa satu bulan dari 15 Januari adalah 15 Februari. Interval punya aturan mainnya sendiri — presisi, justify_hours, operasi antar tipe waktu — dan itu seluruhnya materi chapter Interval berikutnya.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
aritmetika-preview.sql
Unduh berkasSELECT DATE '2026-01-15' + 7 AS minggu_depan,
DATE '2026-01-15' - 7 AS minggu_lalu;
SELECT DATE '2026-01-31' - DATE '2026-01-15' AS selisih_hari;
SELECT DATE '2026-01-15' + INTERVAL '1 month' AS bulan_depan;
SELECT TIMESTAMP '2026-01-15 18:00:00' - TIMESTAMP '2026-01-15 08:30:00' AS durasi;
buat-tabel.sql
Unduh berkasCREATE TABLE pesanan (
id integer PRIMARY KEY,
pelanggan text NOT NULL,
tanggal_pesan date NOT NULL,
jam_kirim time NOT NULL,
dibuat_pada timestamp NOT NULL
);
INSERT INTO pesanan (id, pelanggan, tanggal_pesan, jam_kirim, dibuat_pada) VALUES
(1, 'Budi', DATE '2026-01-15', TIME '08:30:00', TIMESTAMP '2026-01-15 07:45:12'),
(2, 'Siti', DATE '2026-01-16', TIME '13:05:30', TIMESTAMP '2026-01-16 12:40:00'),
(3, 'Toko Maju Jaya', DATE '2026-02-01', TIME '09:15:00', TIMESTAMP '2026-02-01 09:00:00');
SELECT id, pelanggan, tanggal_pesan, jam_kirim, dibuat_pada
FROM pesanan
ORDER BY id;
format-iso.sql
Unduh berkasSELECT DATE '2026-01-15' AS tanggal,
TIME '08:30:00' AS jam,
TIMESTAMP '2026-01-15 08:30:00' AS waktu;
SELECT TIMESTAMP '2026-01-15 08:30:00.123456' AS presisi_mikrodetik;
SELECT DATE '01/15/2026' AS format_ambigu;
tanggal-tidak-valid.sql
Unduh berkasSELECT DATE '2026-02-30' AS tanggal_tidak_ada;
zona-waktu.sql
Unduh berkasSELECT TIMESTAMPTZ '2026-01-15 08:00:00+07' AS waktu_jakarta;
SELECT TIMESTAMPTZ '2026-01-15 08:00:00+07' AT TIME ZONE 'Asia/Tokyo' AS waktu_tokyo,
TIMESTAMPTZ '2026-01-15 08:00:00+07' AT TIME ZONE 'UTC' AS waktu_utc;
SELECT TIMESTAMP '2026-01-15 08:00:00' AT TIME ZONE 'Asia/Jakarta' AS diberi_zona_waktu;