PostgreSQL timestamp vs timestamptz: Apa Sebenarnya yang Tersimpan di Balik Layar?
PostgreSQL menyimpan baik timestamp maupun timestamptz sebagai satu integer 64-bit: jumlah mikrodetik sejak 1970-01-01 00:00:00 UTC. Perbedaannya muncul hanya saat data diformat untuk konsumsi manusia.
Mengapa Ini Membingungkan Banyak Orang?
- Dua kolom, satu tanggal… dua hasil query yang berbeda
- Aplikasi Anda menyisipkan
2025-07-29 10:00, tapi tim lain melihat02:00 - Frontend merender string ISO yang tidak cocok dengan log backend
Dua Kaleng Buah Persik: Satu Polos, Satu Berlabel
| Tipe Data | Nama Resmi | Nilai Tersimpan | Apa yang Terjadi saat SELECT |
|---|---|---|---|
timestamp | timestamp without time zone | hitungan mikrodetik mentah | Dikirim kembali tanpa perubahan — Postgres tidak pernah menebak timezone |
timestamptz | timestamp with time zone | hitungan mikrodetik yang sama | Postgres menerapkan pengaturan TimeZone sesi tepat sebelum mengirim teks |
Analogi
timestamp= stoples buah persik tanpa label asal. Anda tahu itu buah, tapi tidak tahu di mana diproduksi.timestamptz= stoples yang dicap “Dibuat di UTC+8.” Siapa pun yang membukanya bisa memutuskan apakah perlu mengkonversi label nutrisi.
Di Balik Layar: Ini Hanya Angka Besar
2000-01-01 00:00:00 UTC → 0
2000-01-01 00:00:01 UTC → 1 000 000
- Unit: mikrodetik (seperjuta detik)
- Rentang: 4713 SM – 294276 M — disetujui Indiana Jones
- Penyimpanan untuk
timestampdantimestamptzidentik; yang berbeda adalah interpretasinya
Demo 15 Detik
-- Client berpikir dalam waktu Shanghai
SET TimeZone = 'Asia/Shanghai';
CREATE TABLE demo (
created_ts timestamp,
created_tz timestamptz
);
INSERT INTO demo VALUES ('2025-07-29 10:00', '2025-07-29 10:00');
| Query | Hasil | Alasan |
|---|---|---|
SELECT created_ts FROM demo; | 2025-07-29 10:00:00 | Nilai mentah, tanpa kalkulasi TZ |
SELECT created_tz FROM demo; | 2025-07-29 10:00:00+08 | Tag diterapkan saat output |
SET TimeZone = 'UTC'; lalu select | 2025-07-29 02:00:00+00 | Momen yang sama, lensa baru |
Aritmetika Timestamp dan Interval
Salah satu aspek paling praktis dari timestamp PostgreSQL adalah aritmetika interval. Karena kedua tipe menyimpan hitungan mikrodetik, Anda bisa menambah dan mengurangi interval secara langsung:
-- Tambah 3 jam dan 30 menit
SELECT '2025-07-29 10:00'::timestamptz + INTERVAL '3 hours 30 minutes';
-- → 2025-07-29 13:30:00+08
-- Temukan selisih antara dua timestamp
SELECT '2025-07-30 09:00'::timestamptz - '2025-07-29 10:00'::timestamptz;
-- → 23:00:00 (sebuah interval)
-- Ekstrak field tertentu
SELECT EXTRACT(EPOCH FROM '2025-07-29 10:00:00+08'::timestamptz);
-- → 1753768800 (Unix timestamp dalam detik)
-- Potong ke batas hari (berguna untuk agregasi harian)
SELECT date_trunc('day', '2025-07-29 15:42:19+08'::timestamptz);
-- → 2025-07-29 00:00:00+08
Fungsi EXTRACT(EPOCH FROM ...) sangat berguna ketika Anda perlu meneruskan timestamp ke sistem eksternal yang mengharapkan detik epoch Unix. Sebaliknya, Anda bisa mengkonversi epoch kembali ke timestamp:
SELECT to_timestamp(1753768800);
-- → 2025-07-29 10:00:00+08 (dalam sesi Asia/Shanghai)
Poin penting yang halus: aritmetika interval dengan timestamp (tanpa timezone) mengabaikan transisi DST sepenuhnya, sementara timestamptz menghormatinya. Ini berarti menambahkan INTERVAL '1 day' ke nilai timestamptz yang melintasi batas DST akan mengembalikan waktu jam dinding yang sama dengan benar — bukan tepat 24 jam kemudian.
Pertimbangan Indexing dan Performa
Baik timestamp maupun timestamptz disimpan sebagai integer 8-byte, jadi tidak ada perbedaan performa antara keduanya untuk penyimpanan atau indexing. Index B-tree bekerja identik pada kedua tipe karena perbandingan yang mendasarinya hanyalah perbandingan integer.
Namun, ada beberapa pertimbangan praktis:
- Range query:
WHERE created_at > '2025-07-01'bekerja efisien dengan index pada kedua tipe. Dengantimestamptz, PostgreSQL mengkonversi literal ke UTC sebelum perbandingan, sehingga index tetap digunakan. - Partition key: Saat menggunakan range partitioning pada kolom timestamp,
timestamptzumumnya lebih aman karena batas partisi tidak ambigu (selalu UTC). Dengantimestamp, batas seperti'2025-07-01 00:00'bisa berarti hal berbeda untuk sesi yang berbeda. - Functional index: Jika Anda sering melakukan query berdasarkan tanggal saja (mengabaikan waktu), pertimbangkan index pada
date_trunc('day', created_at)untuk mempercepat query agregasi harian.
Jebakan Umum & Solusi Cepat
1. Pengguna berbeda, jam berbeda
- Penyebab: client menggunakan pengaturan
TimeZoneyang berbeda dengantimestamptz - Solusi: tetap gunakan semua
timestamp+ sepakati satu zona, atau paksaSET TimeZone = 'UTC'saat inisialisasi koneksi
Pola umum dalam kode aplikasi adalah mengatur timezone sekali saat inisialisasi connection pool:
-- Dalam setup koneksi Anda (misalnya, konfigurasi pg pool)
SET timezone = 'UTC';
Ini memastikan semua sesi melihat representasi UTC yang sama, dan application layer Anda menangani konversi ke waktu lokal untuk tampilan.
2. Menyimpan “waktu jam dinding” tapi memilih tipe yang salah
- Kalender bisnis (jam toko, deadline) harus menggunakan
timestamp - Alur kerja lintas negara (pesanan, log) harus menyimpan UTC di
timestamptz
Tesnya sederhana: jika pertanyaannya adalah “pada momen apa ini terjadi?” gunakan timestamptz. Jika pertanyaannya adalah “jam berapa di dinding?” gunakan timestamp.
3. API yang bergeser
- Selalu kirimkan
timestamptzsebagai string ISO-8601 dengan offset (Zatau+08:00) - Biarkan UI memformat secara lokal
4. Membandingkan timestamp lintas tipe
Mencampurkan timestamp dan timestamptz dalam perbandingan atau join adalah sumber bug yang halus:
-- Berbahaya: cast implisit menerapkan timezone sesi
SELECT * FROM orders o
JOIN schedules s ON o.created_tz = s.start_ts;
-- PostgreSQL melakukan cast s.start_ts ke timestamptz menggunakan timezone sesi
-- Sesi yang berbeda bisa mendapatkan hasil join yang berbeda!
Solusi: selalu lakukan cast secara eksplisit saat membandingkan lintas tipe, atau standarisasi pada satu tipe per domain.
5. Jebakan default ORM
Banyak ORM (Django, SQLAlchemy, ActiveRecord) default ke timestamp tanpa timezone. Periksa file migrasi Anda — jika aplikasi Anda melayani pengguna lintas timezone, override default ke timestamptz. Di Django, atur USE_TZ = True di settings. Di SQLAlchemy, gunakan DateTime(timezone=True).
Cheat Sheet: Mana yang Harus Saya Gunakan?
Kalender lokal saja → timestamp
Apa pun yang global → timestamptz (simpan UTC)
- Laporan keuangan, jadwal kelas →
timestamp - Log audit, pesanan e-commerce →
timestamptz
Verifikasi dalam Hitungan Detik dengan Go Tools
| Kebutuhan | Tool | Cara |
|---|---|---|
| Inspeksi nilai epoch dari SQL | Epoch Converter | Tempel 1690622400, tekan Convert |
| Bandingkan dua timezone sekilas | Timezone Converter | Masukkan 10:00 Asia/Shanghai |
| Rapikan JSON massal dengan field waktu | JSON Formatter | Drop payload, prettify & scan |
Semua utilitas berjalan sepenuhnya di browser Anda — data tidak pernah meninggalkan mesin Anda.
Pertanyaan yang Sering Diajukan
Apa perbedaan antara timestamp dan timestamptz di PostgreSQL?
timestamp (tanpa time zone) menyimpan nilai date-time apa adanya, tanpa konteks timezone. timestamptz (dengan time zone) mengkonversi input ke UTC untuk penyimpanan dan mengkonversi kembali ke timezone sesi saat pengambilan. Gunakan timestamptz untuk hampir semua kasus — ini mencegah bug terkait timezone di sistem terdistribusi.
Apakah PostgreSQL benar-benar menyimpan timezone di timestamptz?
Tidak — meskipun namanya, PostgreSQL tidak menyimpan timezone itu sendiri. Ia mengkonversi input ke UTC dan menyimpan hanya nilai UTC (hitungan mikrodetik dari 2000-01-01). Saat pengambilan, ia mengkonversi dari UTC ke timezone apa pun yang ditentukan oleh pengaturan timezone sesi Anda. Informasi timezone asli dibuang.
Bagaimana cara mengubah timezone untuk sesi PostgreSQL?
Jalankan SET timezone = 'America/New_York'; untuk mengubah timezone sesi. Ini memengaruhi bagaimana nilai timestamptz ditampilkan dan diinterpretasikan. Untuk default seluruh server, atur timezone di postgresql.conf. Selalu gunakan nama timezone IANA (seperti Asia/Shanghai) daripada singkatan (seperti CST) untuk menghindari ambiguitas.
Haruskah saya menggunakan timestamp atau timestamptz untuk menyimpan waktu event?
Gunakan timestamptz untuk hampir semua hal — aksi pengguna, panggilan API, log audit, dan event terjadwal. Hanya gunakan timestamp (tanpa timezone) untuk waktu abstrak yang tidak terikat pada momen tertentu, seperti “toko buka pukul 09:00” yang berarti jam 9 pagi dalam timezone lokal apa pun, bukan instant UTC tertentu.
Bagaimana PostgreSQL menangani daylight saving time dengan timestamptz?
PostgreSQL menangani DST dengan benar saat menggunakan timestamptz karena ia menyimpan semuanya dalam UTC secara internal. Saat Anda mengambil nilai, PostgreSQL mengkonversi dari UTC menggunakan aturan DST saat ini untuk timezone sesi Anda. Ini berarti instant UTC tersimpan yang sama menampilkan waktu lokal yang berbeda dengan benar sebelum dan sesudah transisi DST.
Untuk panduan komprehensif tentang Unix timestamp — termasuk penanganan presisi, praktik terbaik timezone, dan contoh kode dalam JavaScript, Python, dan Go — baca Panduan Unix Timestamp kami.
Penutup
- Kedua tipe waktu Postgres adalah penghitung mikrodetik; labelnya adalah satu-satunya perbedaan
- Memilih yang salah berarti timestamp yang membingungkan dan perhitungan yang rusak
- Uji, konversi, dan periksa kewarasan dengan tool yang tepat untuk menghemat berjam-jam debugging