Hampir setiap aplikasi menyimpan waktu: kapan pesanan dibuat, kapan acara dimulai, kapan laporan dihasilkan. MySQL memecah kebutuhan itu menjadi lima tipe — DATE, TIME, DATETIME, TIMESTAMP, dan YEAR — dengan rentang dan perilaku yang berbeda. Pilihan yang paling sering menjerumuskan adalah memilih antara DATETIME dan TIMESTAMP, karena keduanya terlihat sama-sama menyimpan tanggal-plus-jam padahal cara simpannya bertolak belakang. Pada Chapter ini kita akan mengisi tabel dengan tanggal literal, menghitung selisih hari dengan DATEDIFF dan DATE_ADD, lalu membuktikan lewat eksperimen zona waktu bagaimana TIMESTAMP berperilaku berbeda dari DATETIME.
Lima Tipe Waktu #
Tabel berikut merangkum kelima tipe beserta format dan rentang nilainya:
| Tipe | Format | Rentang | Ukuran |
|---|---|---|---|
DATE | YYYY-MM-DD | 1000-01-01 s.d. 9999-12-31 | 3 byte |
TIME | HH:MM:SS | -838:59:59 s.d. 838:59:59 | 3 byte |
DATETIME | YYYY-MM-DD HH:MM:SS | 1000-01-01 00:00:00 s.d. 9999-12-31 23:59:59 | 5 byte |
TIMESTAMP | YYYY-MM-DD HH:MM:SS | 1970-01-01 00:01:00 s.d. 2038-01-19 03:14:07 (UTC) | 4 byte |
YEAR | YYYY | 1901 s.d. 2155 | 1 byte |
Perhatikan dua hal dari tabel tersebut. Pertama, TIME bukan sekadar jam dalam sehari — rentangnya sampai 838 jam positif dan negatif sehingga cocok juga menyimpan durasi seperti '125:30:00' (125,5 jam) atau selisih '-02:30:00'. Kedua, TIMESTAMP punya rentang paling sempit karena isinya adalah jumlah detik sejak epoch Unix (1 Januari 1970 UTC) yang disimpan dalam bilangan 32 bit — batas atasnya jatuh pada 19 January 2038, masalah yang dikenal sebagai Year 2038 problem.
Praktik dengan Literal Tanggal #
Kita mulai dengan tabel jadwal acara yang memakai empat tipe sekaligus. Nilai tanggal selalu kita tulis sebagai literal format ISO YYYY-MM-DD — format yang tidak ambigu di negara mana pun:
DROP DATABASE IF EXISTS belajar_tipe;
CREATE DATABASE belajar_tipe;
USE belajar_tipe;Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changedCREATE TABLE jadwal (
id INT,
judul VARCHAR(60),
tanggal DATE,
jam TIME,
waktu_mulai DATETIME,
tahun YEAR
);
INSERT INTO jadwal VALUES
(1, 'Rapat Tim Bulanan', '2026-01-15', '09:30:00', '2026-01-15 09:30:00', 2026),
(2, 'Pelatihan SQL Dasar', '2026-02-20', '13:00:00', '2026-02-20 13:00:00', 2026);
SELECT * FROM jadwal ORDER BY id;Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
+------+---------------------+------------+----------+---------------------+-------+
| id | judul | tanggal | jam | waktu_mulai | tahun |
+------+---------------------+------------+----------+---------------------+-------+
| 1 | Rapat Tim Bulanan | 2026-01-15 | 09:30:00 | 2026-01-15 09:30:00 | 2026 |
| 2 | Pelatihan SQL Dasar | 2026-02-20 | 13:00:00 | 2026-02-20 13:00:00 | 2026 |
+------+---------------------+------------+----------+---------------------+-------+
2 rows in set (0.00 sec)Mari kita bedah definisi tabel di atas baris per baris:
- Baris 3 (
tanggal DATE): Menyimpan tanggal tanpa jam — cukup untuk acara yang jamnya memang belum ditentukan. - Baris 4 (
jam TIME): Menyimpan waktu dalam hari09:30:00; nilainya berdiri sendiri, tidak terikat tanggal mana pun. - Baris 5 (
waktu_mulai DATETIME): Kombinasi tanggal dan jam sebagai satu nilai — inilah tipe yang kita pakai bila keduanya harus dibaca bersama. - Baris 6 (
tahun YEAR): Menyimpan satu tahun dalam 1 byte; cocok untuk kolom seperti tahun anggaran atau tahun produksi.
Nilai yang kita masukkan tampil kembali persis sama: 2026-01-15, 09:30:00, 2026-01-15 09:30:00, dan 2026. MySQL memvalidasi setiap nilai literal terhadap kalender sungguhan, bukan sekadar pola teks:
-- @error
INSERT INTO jadwal (id, judul, tanggal) VALUES (3, 'Rapat Darurat', '2026-02-30');Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
ERROR 1292 (22007): Incorrect date value: '2026-02-30' for column 'tanggal' at row 1
ByeTanggal 2026-02-30 ditolak keras dengan galat Incorrect date value — Februari 2026 tidak punya tanggal 30. Validasi kalender ini melindungi kita dari kesalahan ketik yang mudah terjadi saat aplikasi mengirim tanggal bentukan sendiri.
Operasi Dasar: DATEDIFF dan DATE_ADD #
Dua fungsi tanggal yang paling sering kita pakai adalah DATEDIFF (selisih hari) dan DATE_ADD (penambahan interval). Keduanya bekerja deterministik atas literal dan kolom:
SELECT judul, tanggal,
DATEDIFF(tanggal, '2026-01-01') AS hari_dari_awal_tahun,
DATE_ADD(tanggal, INTERVAL 30 DAY) AS pengingat
FROM jadwal
ORDER BY id;Output yang Dihasilkan:
+---------------------+------------+----------------------+------------+
| judul | tanggal | hari_dari_awal_tahun | pengingat |
+---------------------+------------+----------------------+------------+
| Rapat Tim Bulanan | 2026-01-15 | 14 | 2026-02-14 |
| Pelatihan SQL Dasar | 2026-02-20 | 50 | 2026-03-22 |
+---------------------+------------+----------------------+------------+
2 rows in set (0.00 sec)Fungsi DATEDIFF(tanggal, '2026-01-01') menghitung banyaknya hari dari tanggal kedua ke tanggal pertama — hasil 14 berarti rapat terjadi 14 hari setelah 1 Januari. Fungsi DATE_ADD(tanggal, INTERVAL 30 DAY) menggeser tanggal 30 hari ke depan sebagai tanggal pengingat. Kata kunci INTERVAL diikuti besaran dan satuan; satuan lain seperti MONTH, HOUR, dan MINUTE bisa dipakai dengan pola yang sama. Kedua fungsi ini hanya membaca nilai yang ada dan tidak bergantung pada jam server, sehingga hasilnya selalu bisa diuji ulang.
DATETIME vs TIMESTAMP #
Kedua tipe ini sama-sama menampilkan format YYYY-MM-DD HH:MM:SS, tetapi mekanisme penyimpanannya bertolak belakang. DATETIME menyimpan tulisan tanggal-jam apa adanya — nilai 2026-01-15 09:30:00 tersimpan sebagai string 5 byte yang tidak pernah berubah. TIMESTAMP mengonversi nilai ke jumlah detik sejak 1 Januari 1970 UTC saat menyimpan, lalu mengonversi kembali ke zona waktu sesi saat membaca. Batas rentang inilah yang membuat TIMESTAMP menolak tahun-tahun jauh:
CREATE TABLE log_server (id INT, aksi VARCHAR(40), tercatat TIMESTAMP);
INSERT INTO log_server VALUES (1, 'login', '2026-01-15 09:30:00');
SELECT * FROM log_server;
SET time_zone = '+00:00';
SELECT UNIX_TIMESTAMP('2026-01-15 09:30:00') AS detik_unix;
SET time_zone = DEFAULT;Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 1 row affected (0.00 sec)
+------+-------+---------------------+
| id | aksi | tercatat |
+------+-------+---------------------+
| 1 | login | 2026-01-15 09:30:00 |
+------+-------+---------------------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
+------------+
| detik_unix |
+------------+
| 1768469400 |
+------------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)Kolom tercatat menerima 2026-01-15 09:30:00 karena masih dalam rentang 1970–2038, dan fungsi UNIX_TIMESTAMP memperlihatkan bentuk aslinya: detik ke-1768469400 sejak epoch. Sebaliknya, tahun di luar rentang langsung ditolak:
-- @error
INSERT INTO log_server VALUES (2, 'sinkronisasi', '2040-01-01 10:00:00');Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
ERROR 1292 (22007): Incorrect datetime value: '2040-01-01 10:00:00' for column 'tercatat' at row 1
ByeNilai 2040-01-01 di luar batas TIMESTAMP sehingga server menolaknya dengan galat Incorrect datetime value — padahal nilai yang sama akan diterima tanpa keluhan oleh DATETIME yang rentangnya sampai tahun 9999. Konversi zona waktunya bisa kita amati langsung dengan eksperimen berikut:
CREATE TABLE uji_waktu (kolom_ts TIMESTAMP, kolom_dt DATETIME);
SET time_zone = '+07:00';
INSERT INTO uji_waktu VALUES ('2026-01-15 09:30:00', '2026-01-15 09:30:00');
SELECT kolom_ts, kolom_dt FROM uji_waktu;
SET time_zone = '+00:00';
SELECT kolom_ts, kolom_dt FROM uji_waktu;
SET time_zone = '+07:00';Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
Query OK, 1 row affected (0.00 sec)
+---------------------+---------------------+
| kolom_ts | kolom_dt |
+---------------------+---------------------+
| 2026-01-15 09:30:00 | 2026-01-15 09:30:00 |
+---------------------+---------------------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
+---------------------+---------------------+
| kolom_ts | kolom_dt |
+---------------------+---------------------+
| 2026-01-15 02:30:00 | 2026-01-15 09:30:00 |
+---------------------+---------------------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)Kita mengisi dua kolom dengan nilai yang sama persis di zona waktu WIB (+07:00), dan pembacaan pertama memang menampilkan 09:30:00 keduanya. Namun setelah sesi diganti ke zona +00:00, kolom kolom_ts tampil sebagai 2026-01-15 02:30:00 sedangkan kolom_dt tetap 09:30:00. Inilah bukti bahwa TIMESTAMP menyimpan satu titik waktu absolut (di sini: pukul 02:30 UTC) lalu menyesuaikan tampilannya dengan pembaca, sementara DATETIME menyimpan dan menampilkan tulisan yang sama untuk semua pembaca. Aplikasi multi-zona waktu — pengguna Jakarta dan London melihat jam acara sesuai kotanya — memanfaatkan perilaku TIMESTAMP ini.
Kapan Memakai yang Mana #
Aturan praktisnya ringkas. DATE untuk tanggal murni, TIME untuk jam atau durasi, YEAR untuk tahun saja. Antara DATETIME dan TIMESTAMP: pakai TIMESTAMP untuk kejadian yang dilihat server sendiri (waktu dibuat, waktu diubah, waktu login) karena otomatis diubah ke UTC dan hemat 4 byte; pakai DATETIME untuk event yang tanggalnya adalah fakta dunia nyata (jam mulai konser, tanggal lahir, jadwal kereta) karena nilainya tidak boleh bergeser hanya karena pembaca berpindah zona waktu. Kombinasi keduanya juga lazim: kolom created_at TIMESTAMP berdampingan dengan kolom jadwal_acara DATETIME.
Terakhir, kita bereskan database latihan:
DROP DATABASE belajar_tipe;Output yang Dihasilkan:
Query OK, 3 rows affected (0.00 sec)Referensi #
- Date and Time Data Types — MySQL 8.4 Reference Manual
- The DATE, DATETIME, and TIMESTAMP Types — MySQL 8.4 Reference Manual
- The YEAR Type — MySQL 8.4 Reference Manual
Angka, teks, dan waktu kini kita kuasai. Pertanyaan berikutnya: bagaimana menyimpan jawaban sederhana “ya atau tidak”? Chapter berikutnya membongkar BOOLEAN yang sebenarnya hanyalah TINYINT(1) plus konvensi, beserta arti TRUE dan FALSE di mata MySQL.