Hampir semua aplikasi pada akhirnya berurusan dengan waktu. Sebuah artikel blog punya tanggal terbit. Sebuah pesanan punya timestamp. Sebuah hitung mundur berjalan menuju tenggat. Label “terakhir online 3 menit lalu” terus berubah di depan matamu. Semua itu lewat objek bawaan JavaScript bernama Date — dan Date memang terkenal jadi salah satu bagian bahasa yang paling membingungkan.
Kabar baiknya: sebagian besar kebingungan itu berasal dari segelintir keanehan yang spesifik. Begitu kamu tahu apa saja keanehannya, mengurus tanggal nggak lagi terasa seperti melewati ranjau. Artikel ini membahas cara membuat tanggal, membaca bagian-bagiannya, memformatnya supaya enak dibaca manusia, melakukan hitungan, dan — yang paling penting — jebakan zona waktu serta parsing yang hampir selalu menjebak orang di percobaan pertama. Sebaiknya kamu sudah nyaman dengan objek sebelum masuk ke sini, karena Date sendiri sebenarnya adalah jenis objek yang istimewa.
Date itu sebenarnya apa
Di balik layar, sebuah Date di JavaScript hanyalah satu angka saja: jumlah milidetik sejak satu titik waktu tetap yang disebut Unix epoch — yaitu tengah malam UTC pada 1 Januari 1970. Setiap tanggal yang kamu buat, sekeren apa pun tampilannya di layar, di dalamnya disimpan sebagai satu angka itu.
Inilah ide paling penting di seluruh artikel ini. Sebuah Date tidak menyimpan “15 Maret di Jakarta.” Yang disimpan adalah satu momen waktu yang persis, dalam bentuk hitungan milidetik, lalu ditampilkan ke kamu dalam zona waktu lokal saat kamu memintanya. Pegang erat-erat model berpikir ini, dan sebagian besar perilaku anehnya akan mulai masuk akal.
const sekarang = new Date();
console.log(sekarang.getTime()); // misalnya 1781827200000 — milidetik sejak 1970
Membuat sebuah Date
Kamu membuat tanggal dengan konstruktor new Date(), dan perilakunya berbeda-beda tergantung apa yang kamu berikan ke dalamnya. Ada empat bentuk utama.
Tanpa argumen — momen saat ini:
const sekarang = new Date();
console.log(sekarang); // tanggal & waktu persis sekarang
Sebuah timestamp (milidetik sejak epoch):
const epoch = new Date(0); // 1 Jan 1970, 00:00:00 UTC
const nanti = new Date(1781827200000); // suatu momen di tahun 2026
Bagian-bagian angka (tahun, bulan, tanggal, …):
const d = new Date(2026, 6, 31, 14, 30, 0);
// 31 Juli 2026, 14:30:00 — dalam waktu LOKAL
Sebuah string tanggal:
const d = new Date("2026-07-31");
const d2 = new Date("2026-07-31T14:30:00");
Tiap bentuk punya jebakan yang perlu kamu tahu, dan bentuk bagian-angka punya jebakan yang paling terkenal di antara semuanya.
Bulan dihitung mulai dari nol
Saat kamu menyusun tanggal dari angka, bulan dihitung mulai dari 0, bukan 1. Jadi 0 itu Januari, 1 itu Februari, dan 11 itu Desember. Artinya new Date(2026, 6, 31) adalah Juli tanggal 31, bukan Juni. Anehnya, tanggal dalam bulan justru tetap mulai dari 1 — hanya bulan saja yang berbasis nol. Satu keanehan inilah penyebab bug tanggal paling banyak di JavaScript. Kalau suatu tanggal keluar meleset persis satu bulan, hampir pasti ini biang keladinya.
Membaca bagian-bagian tanggal
Begitu kamu punya sebuah Date, kamu mengambil potongan-potongannya pakai method get. Semua ini mengembalikan nilai dalam zona waktu lokalmu:
const d = new Date(2026, 6, 31, 14, 30, 0);
d.getFullYear(); // 2026
d.getMonth(); // 6 (Juli — ingat, berbasis nol!)
d.getDate(); // 31 (tanggal dalam bulan, 1–31)
d.getDay(); // 5 (hari dalam MINGGU: 0 = Minggu, 6 = Sabtu)
d.getHours(); // 14
d.getMinutes(); // 30
d.getSeconds(); // 0
Beberapa nama ini bikin orang keliru. getDate() memberi kamu tanggal dalam bulan (angka dari 1 sampai 31), sedangkan getDay() memberi hari dalam minggu sebagai angka, dengan hari Minggu bernilai 0. Kedengarannya mirip, padahal sama sekali beda — sering tertukar dan jadi kesalahan klasik.
Ada juga getTime(), yang mengembalikan timestamp milidetik mentah tadi. Inilah cara paling bersih untuk membandingkan dua tanggal, seperti yang sebentar lagi kita lihat.
Memformat tanggal untuk manusia
Sebuah Date mentah yang dicetak ke console kelihatan teknis dan kurang sedap dipandang. Untuk menampilkan sesuatu yang enak dibaca, kamu punya beberapa pilihan — dan pilihan modernnya jauh lebih baik daripada cara-cara lama.
Method bawaan yang serba cepat memberi kamu format yang sudah baku:
const d = new Date(2026, 6, 31, 14, 30);
d.toString(); // "Fri Jul 31 2026 14:30:00 GMT+0700 ..."
d.toDateString(); // "Fri Jul 31 2026"
d.toISOString(); // "2026-07-31T07:30:00.000Z" (selalu UTC)
d.toLocaleDateString();// "31/7/2026" (mengikuti locale pengguna)
toISOString() ini istimewa dan layak kamu ingat: ia selalu mengembalikan tanggal dalam UTC memakai format standar ISO 8601. Itulah format yang kamu inginkan saat menyimpan tanggal atau mengirimnya ke server, karena maknanya tunggal dan tak bisa salah tafsir.
Untuk menampilkan tanggal ke orang sungguhan, alat terbaiknya adalah Intl.DateTimeFormat (atau jalan pintasnya, toLocaleDateString dengan opsi). Ia memformat tanggal dengan benar untuk bahasa dan wilayah apa pun tanpa kamu harus merangkai string sendiri:
const d = new Date(2026, 6, 31, 14, 30);
const hasil = new Intl.DateTimeFormat("id-ID", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
}).format(d);
console.log(hasil); // "Jumat, 31 Juli 2026"
Berhenti merakit string tanggal manual
Godaannya memang besar untuk menyusun tanggal sendiri pakai sesuatu seperti `${tanggal}/${bulan}/${tahun}`. Tahan dulu. Kamu bakal selamanya bergulat dengan nol di depan, nama bulan, perbedaan locale, dan macam-macam kasus pinggiran. Intl.DateTimeFormat menangani itu semua — nama bulan yang sudah diterjemahkan, pemisah yang tepat, jam 12 vs 24 — secara gratis, dan sudah tertanam di setiap browser modern. Pakai ini setiap kali kamu perlu menampilkan tanggal ke pengguna.
Melakukan hitungan dengan tanggal
Hitungan tanggal adalah saat ide “tanggal itu sebenarnya angka milidetik” tadi mulai membuahkan hasil. Karena setiap tanggal sejatinya adalah angka, kamu bisa membandingkan dan mengurangkannya.
Membandingkan dua tanggal: ubah keduanya jadi timestamp pakai getTime() (atau pakai + di depan), lalu bandingkan angkanya.
const mulai = new Date("2026-01-01");
const akhir = new Date("2026-12-31");
console.log(akhir.getTime() > mulai.getTime()); // true — akhir lebih belakangan
Mencari selisih antara dua tanggal: kurangkan timestamp keduanya supaya dapat milidetik, lalu ubah ke satuan apa pun yang kamu mau.
const mulai = new Date("2026-07-01");
const akhir = new Date("2026-07-31");
const selisihMs = akhir - mulai; // selisih dalam milidetik
const selisihHari = selisihMs / (1000 * 60 * 60 * 24); // ms → detik → menit → jam → hari
console.log(selisihHari); // 30
Angka 1000 * 60 * 60 * 24 itu adalah jumlah milidetik dalam satu hari, dan kamu bakal sering menuliskannya (atau menyimpannya sebagai konstanta).
Menambah atau mengurangi waktu sedikit lebih rumit. Cara yang bersih dan bisa diandalkan adalah memakai method set, karena method-method ini otomatis menangani pergantian batas bulan dan tahun:
const d = new Date(2026, 6, 31); // 31 Juli 2026
d.setDate(d.getDate() + 7); // tambah 7 hari
console.log(d); // 7 Agustus 2026 — berpindah ke bulan berikutnya dengan benar
Perhatikan, menambah 7 hari ke 31 Juli tidak menghasilkan “Juli tanggal 38” — ia dengan benar maju ke Agustus. Method set mengerjakan hitungan itu untukmu, termasuk tahun kabisat, dan justru inilah alasannya kamu sebaiknya memilih method ini ketimbang mengutak-atik penjumlahan milidetik mentah untuk menambah hari, bulan, atau tahun.
Jebakan mutability
Ada satu jebakan halus yang benar-benar memicu bug: objek Date itu mutable. Method seperti setDate() dan setMonth() tidak mengembalikan tanggal baru — mereka mengubah tanggal aslinya langsung di tempat.
const asli = new Date(2026, 6, 31);
const salinan = asli; // BUKAN salinan — objek yang sama!
salinan.setDate(1);
console.log(asli.getDate()); // 1 — yang "asli" ikut berubah!
Menyalin Date tidak benar-benar menggandakannya
Karena Date adalah objek, menulis const salinan = tanggalTertentu cuma membuat salinan menunjuk ke tanggal yang sama. Mengubah satu berarti mengubah keduanya. Kalau kamu butuh salinan yang benar-benar mandiri sebelum mengubahnya, buat tanggal baru dari timestamp-nya: const salinan = new Date(asli.getTime()). Lupa soal ini adalah sumber umum dari bug “lho kok variabel satunya ikut berubah?” — dan ini perilaku referensi yang sama seperti yang mungkin sudah kamu temui pada objek dan array secara umum.
Jebakan zona waktu
Yang satu ini cepat atau lambat menjebak semua orang, jadi pantas dapat bagian sendiri. Ingat lagi bahwa Date menyimpan satu momen dalam UTC dan menampilkannya dalam waktu lokal. Di celah itulah kejutan-kejutan bersembunyi.
Contoh yang paling terkenal adalah saat mem-parsing sebuah string tanggal saja:
const d = new Date("2026-07-31");
console.log(d.toISOString()); // "2026-07-31T00:00:00.000Z"
String dengan format "YYYY-MM-DD" di-parsing sebagai tengah malam UTC. Kalau kamu berada di zona waktu yang lebih maju dari UTC — misalnya Jakarta, UTC+7 — momen yang sama itu berarti jam 7 pagi tanggal 31 Juli secara lokal, yang kelihatannya aman-aman saja. Tapi di zona waktu yang lebih mundur dari UTC, seperti New York, tengah malam UTC tanggal 31 Juli sebenarnya adalah jam 7 malam tanggal 30 Juli. Jadi new Date("2026-07-31").getDate() bisa mengembalikan 30 untuk sebagian penggunamu. Sebuah tanggal yang kelihatan benar di komputermu diam-diam bergeser sehari untuk orang lain.
String tanggal-saja di-parsing sebagai UTC; string tanggal-waktu sebagai lokal
Ada satu ketidakkonsistenan yang sudah tertanam di spesifikasi dan layak kamu hafalkan. new Date("2026-07-31") (tanggal saja) diperlakukan sebagai UTC. Tapi new Date("2026-07-31T14:30") (ada waktunya, tanpa Z atau offset) diperlakukan sebagai waktu lokal. String yang kelihatan mirip, aturannya berbeda. Untuk menghindari kerepotan ini sama sekali, selalu sertakan offset zona waktu yang eksplisit (huruf Z untuk UTC, atau +07:00), atau bangun tanggalnya dari bagian-angka pakai new Date(tahun, bulan, hari), yang selalu ditafsirkan secara lokal.
Intinya secara praktis: kalau sebuah tanggal meleset sehari dan hanya untuk sebagian pengguna, curigai parsing yang berbasis zona waktu. Simpan dan kirim tanggal sebagai string ISO penuh dalam UTC, dan baru ubah ke waktu lokal tepat di saat kamu menampilkannya.
Contoh realistis
Mari kita rangkai semuanya lewat sebuah helper “berapa lama lalu” kecil — jenis fungsi yang mengubah timestamp jadi “3 hari lalu”, yang sudah sering kamu lihat di banyak situs:
function berapaLamaLalu(tanggal) {
const detik = Math.floor((new Date() - tanggal) / 1000);
const satuan = {
tahun: 31536000,
bulan: 2592000,
hari: 86400,
jam: 3600,
menit: 60,
};
for (const [nama, detikPerSatuan] of Object.entries(satuan)) {
const jumlah = Math.floor(detik / detikPerSatuan);
if (jumlah >= 1) {
return `${jumlah} ${nama} lalu`;
}
}
return "baru saja";
}
const diposting = new Date("2026-07-28T10:00:00Z");
console.log(berapaLamaLalu(diposting)); // misalnya "3 hari lalu"
Fungsi ini merangkum semua yang ada di artikel: mengurangkan dua tanggal untuk dapat selisih milidetik, mengubahnya ke satuan yang lebih besar, lalu melakukan perulangan atas entry sebuah objek (yang sudah kamu kenal di artikel perulangan) untuk memilih satuan yang pas. Ini benar-benar fungsi berguna yang bisa langsung kamu pasang di proyek nyata.
Saat Date bawaan tidak lagi cukup
Untuk banyak pekerjaan, Date bawaan sudah lebih dari memadai. Tapi begitu kamu mulai banyak menjungkir-balikkan zona waktu, mem-parsing yang rumit, atau memformat dalam jumlah besar, sisi-sisi kasarnya jadi menumpuk. Dulu orang mengandalkan library seperti Moment.js, tapi sekarang Moment dianggap warisan lama. Pilihan modern yang ringan seperti date-fns dan Day.js sudah menutupi sebagian besar kebutuhan.
Kabar yang lebih besar adalah Temporal, sebuah API bawaan baru yang dirancang untuk memperbaiki masalah-masalah lama Date — objek yang immutable, pemisahan yang jelas antara tanggal dan waktu, serta dukungan zona waktu sebagai fitur utama. Inilah masa depan tanggal di JavaScript, dan ia sedang bergulir masuk ke berbagai browser. Kalau kamu mulai dari nol hari ini, Date bawaan masih jadi yang paling sering kamu pakai, tapi ada baiknya kamu tahu Temporal itu ada untuk saat ia nanti tersedia luas.
Penutup
Tanggal terasa menakutkan terutama gara-gara beberapa sisi tajamnya. Kenali sisi-sisi itu dan kamu beres:
- Sebuah
Datedi dalamnya adalah jumlah milidetik sejak Unix epoch (1 Jan 1970 UTC). Ia menyimpan satu momen dan menampilkannya dalam waktu lokal. - Buat tanggal dengan
new Date()— tanpa argumen untuk sekarang, sebuah timestamp, bagian-angka, atau string. Bulan berbasis nol (6= Juli). - Baca bagiannya dengan method
get. Hati-hati pasangan ini:getDate()itu tanggal dalam bulan (1–31);getDay()itu hari dalam minggu (0 = Minggu). - Format untuk manusia pakai
Intl.DateTimeFormat, bukan merakit string sendiri. PakaitoISOString()untuk menyimpan dan mengirim. - Lakukan hitungan dengan memperlakukan tanggal sebagai angka: kurangkan timestamp untuk dapat selisih, dan pakai method
setuntuk menambah/mengurangi hari dengan aman. - Objek
Dateitu mutable — salin dengannew Date(d.getTime())sebelum mengubahnya. - Waspadai jebakan zona waktu: string tanggal-saja di-parsing sebagai UTC, string tanggal-waktu sebagai lokal. Simpan dalam UTC, tampilkan dalam lokal.
Begitu kamu nyaman dengan aturan-aturan ini, reputasi “seram” tanggal JavaScript langsung memudar. Sekarang kamu sudah punya semua bekal untuk membuat hitung mundur, timestamp, dan tampilan tanggal yang ramah manusia dengan percaya diri.