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 #

TipePenyimpananRentang / resolusiKegunaan
date4 byte4713 SM sampai 5874897 M, resolusi 1 hariTanggal kalender tanpa jam
time8 byte00:00:00 sampai 24:00:00, mikrodetikJam dalam sehari, tanpa tanggal
timestamp8 byte4713 SM sampai 294276 M, mikrodetikTanggal dan jam, tanpa zona
timestamptz8 byteSama dengan timestamp, mikrodetikTanggal 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:

sql buat-tabel.sql
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:

text
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:

sql format-iso.sql
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:

text
  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:

text
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:

sql zona-waktu.sql
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:

text
     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:

  1. Query pertama: literal timestamptz memakai offset +07 (WIB). Ditampilkan di sesi Asia/Jakarta, hasilnya 2026-01-15 08:00:00+07 — jam dinding Jakarta untuk momen itu. Nilai yang sama akan tampil 2026-01-15 02:00:00+01 di sesi zona Eropa Barat; momennya identik, tampilannya mengikuti sesi.
  2. Query kedua: AT TIME ZONE mengubah timestamptz menjadi timestamp polos 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.
  3. Query ketiga: arah sebaliknya — timestamp polos yang diberi makna zona. 08:00:00 diartikan sebagai jam dinding Jakarta dan hasilnya timestamptz dengan 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:

text
 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 berkas
sql
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;

buat-tabel.sql

Unduh berkas
sql
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;

format-iso.sql

Unduh berkas
sql
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;

tanggal-tidak-valid.sql

Unduh berkas
sql
SELECT DATE '2026-02-30' AS tanggal_tidak_ada;

zona-waktu.sql

Unduh berkas
sql
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;