Kenapa ini penting
Aplikasi sering perlu menghitung waktu, bukan hanya menampilkannya. Berapa hari lagi jatuh tempo cicilan? Tanggal berapa 30 hari setelah pembelian (batas garansi)? Berapa umur pengguna, apakah sudah 17 tahun untuk mendaftar? Berapa lama lagi promo berakhir? Semua itu adalah perhitungan waktu.
Kelihatannya mudah, tinggal kurang-kurangi. Tetapi ada jebakan: zona waktu, jam yang ikut terhitung, bulan dengan jumlah hari berbeda, tahun kabisat, dan pembulatan. Perhitungan umur yang salah bisa menolak pengguna yang berhak; jatuh tempo yang meleset satu hari bisa memicu denda. Di modul ini kamu belajar pola perhitungan yang benar dan teruji.
Konsep inti
Prinsip: hitung dengan timestamp
Mengurangi dua Date menghasilkan selisih milidetik (JavaScript otomatis memanggil getTime()):
const selisihMs = tanggalB - tanggalA;
Konstanta yang membantu:
| Satuan | Milidetik |
|---|---|
| 1 detik | 1000 |
| 1 menit | 60 * 1000 |
| 1 jam | 60 * 60 * 1000 |
| 1 hari | 24 * 60 * 60 * 1000 = 86400000 |
Selisih hari kalender
Selisih "jumlah hari" yang dirasakan manusia adalah selisih tanggal kalender, bukan selisih 24 jam. Senin 23.00 ke Selasa 01.00 hanya 2 jam, tapi beda 1 hari kalender. Caranya: normalkan kedua tanggal ke tengah malam terlebih dahulu (atau pakai Date.UTC(tahun, bulan, tanggal) dari komponen tanggal) lalu bagi dengan 86.400.000. Memakai Date.UTC menghindari masalah daylight saving di negara yang memakainya, sehingga kodemu benar di mana pun.
Math.round lebih aman daripada Math.floor untuk hasil yang seharusnya bulat, karena pembagian bisa menghasilkan 0,9999 akibat pergeseran jam.
Menambah hari dan bulan
- Tambah hari:
d.setDate(d.getDate() + n). Rollover menangani akhir bulan dan tahun dengan benar. - Tambah bulan:
setMonth(getMonth() + n)bisa meloncat (31 Januari + 1 bulan → 3 Maret). Untuk jatuh tempo bulanan, biasanya aturan bisnisnya "jika tanggal tidak ada, pakai tanggal terakhir bulan itu". Kamu perlu menulisnya sendiri.
Menghitung umur
Umur bukan selisihMs / 365 hari, karena tahun kabisat membuat hasilnya meleset di sekitar ulang tahun. Cara benar: kurangi tahun, lalu kurangi 1 jika ulang tahun tahun ini belum lewat (bandingkan bulan, lalu tanggal).
Countdown
Countdown memecah selisih milidetik menjadi hari, jam, menit, detik dengan pembagian bulat dan sisa bagi (%). Penting: hitung ulang dari waktu target setiap kali (bukan mengurangi 1 detik setiap tick), karena timer JavaScript tidak selalu tepat waktu (dibahas di modul berikutnya).
Contoh kode
const MS_HARI = 24 * 60 * 60 * 1000;
// 1. Selisih hari kalender (aman dari jam & DST)
function selisihHari(dari, ke) {
const a = Date.UTC(dari.getFullYear(), dari.getMonth(), dari.getDate());
const b = Date.UTC(ke.getFullYear(), ke.getMonth(), ke.getDate());
return Math.round((b - a) / MS_HARI);
}
console.log(selisihHari(new Date(2026, 9, 23, 23, 0), new Date(2026, 9, 24, 1, 0))); // → 1
console.log(selisihHari(new Date(2026, 0, 1), new Date(2026, 11, 31))); // → 364
console.log(selisihHari(new Date(2026, 9, 30), new Date(2026, 9, 23))); // → -7 (sudah lewat)
// 2. Tambah hari tanpa mengubah aslinya
function tambahHari(date, n) {
const hasil = new Date(date);
hasil.setDate(hasil.getDate() + n);
return hasil;
}
const beli = new Date(2026, 9, 23);
console.log(tambahHari(beli, 30).toDateString()); // → Sun Nov 22 2026 (batas garansi)
console.log(tambahHari(beli, -23).toDateString()); // → Wed Sep 30 2026
// 3. Tambah bulan dengan "jepit" ke akhir bulan
function tambahBulan(date, n) {
const hasil = new Date(date);
const tanggalAsli = hasil.getDate();
hasil.setDate(1); // hindari loncatan sementara
hasil.setMonth(hasil.getMonth() + n);
const hariTerakhir = new Date(hasil.getFullYear(), hasil.getMonth() + 1, 0).getDate();
hasil.setDate(Math.min(tanggalAsli, hariTerakhir));
return hasil;
}
console.log(tambahBulan(new Date(2026, 0, 31), 1).toDateString()); // → Sat Feb 28 2026
console.log(tambahBulan(new Date(2026, 9, 15), 3).toDateString()); // → Fri Jan 15 2027
// 4. Umur yang akurat
function hitungUmur(lahir, acuan = new Date()) {
let umur = acuan.getFullYear() - lahir.getFullYear();
const belumUlangTahun =
acuan.getMonth() < lahir.getMonth() ||
(acuan.getMonth() === lahir.getMonth() && acuan.getDate() < lahir.getDate());
if (belumUlangTahun) umur--;
return umur;
}
const lahir = new Date(2009, 9, 24); // 24 Okt 2009
console.log(hitungUmur(lahir, new Date(2026, 9, 23))); // → 16 (besok baru 17)
console.log(hitungUmur(lahir, new Date(2026, 9, 24))); // → 17
// 5. Pecah durasi untuk countdown
function pecahDurasi(ms) {
if (ms <= 0) return { hari: 0, jam: 0, menit: 0, detik: 0, selesai: true };
const totalDetik = Math.floor(ms / 1000);
return {
hari: Math.floor(totalDetik / 86400),
jam: Math.floor((totalDetik % 86400) / 3600),
menit: Math.floor((totalDetik % 3600) / 60),
detik: totalDetik % 60,
selesai: false,
};
}
const sekarang = new Date("2026-10-23T14:30:00+07:00");
const promoBerakhir = new Date("2026-11-11T00:00:00+07:00"); // promo 11.11
const sisa = pecahDurasi(promoBerakhir - sekarang);
console.log(`${sisa.hari} hari ${sisa.jam} jam ${sisa.menit} menit ${sisa.detik} detik`);
// → 18 hari 9 jam 30 menit 0 detik
// 6. Format durasi jadi teks "02:05:09"
const dua = (n) => String(n).padStart(2, "0");
const s = pecahDurasi(2 * 3600e3 + 5 * 60e3 + 9e3);
console.log(`${dua(s.jam)}:${dua(s.menit)}:${dua(s.detik)}`); // → 02:05:09
// 7. Status jatuh tempo
function statusTagihan(jatuhTempo, hariIni) {
const n = selisihHari(hariIni, jatuhTempo);
if (n < 0) return `Terlambat ${-n} hari`;
if (n === 0) return "Jatuh tempo hari ini";
return `${n} hari lagi`;
}
console.log(statusTagihan(new Date(2026, 9, 25), new Date(2026, 9, 23))); // → 2 hari lagi
console.log(statusTagihan(new Date(2026, 9, 20), new Date(2026, 9, 23))); // → Terlambat 3 hari
Semua function di atas menerima parameter acuan atau hariIni alih-alih langsung memanggil new Date() di dalamnya. Ini kebiasaan penting: function yang bergantung pada "sekarang" sulit diuji, karena hasilnya berubah setiap hari. Dengan menjadikan waktu acuan sebagai parameter, kamu bisa mengujinya dengan tanggal tetap.
Kesalahan umum
- Membagi selisih ms dengan 86.400.000 tanpa menormalkan jam. 23.00 ke 01.00 keesokan hari menghasilkan 0,08 hari, bukan 1 hari kalender.
- Menghitung umur dengan
/ 365. Tahun kabisat membuat umur salah tepat di sekitar ulang tahun. Bandingkan tahun, bulan, tanggal. setMonth(+1)di tanggal 29-31. Bisa melompat dua bulan. Jepit ke tanggal terakhir bulan.- Countdown yang mengurangi 1 tiap detik. Timer bisa terlambat (tab tidak aktif, CPU sibuk), sehingga countdown makin meleset. Hitung ulang
target - Date.now()setiap tick. - Mengubah Date input di dalam function. Pemanggil kaget datanya berubah. Selalu buat salinan.
Latihan
- Mudah, Tulis
hariMenujuTahunBaru(hariIni)yang mengembalikan jumlah hari kalender sampai 1 Januari tahun berikutnya. - Sedang, Tulis
bolehDaftar(tanggalLahir, hariIni)yang mengembalikantruejika umur minimal 17 tahun. - Menantang, Buat
jadwalCicilan(mulai, jumlahBulan)yang menghasilkan array tanggal jatuh tempo bulanan dengan aturan jepit akhir bulan, misalnya mulai 31 Januari 2026 untuk 4 bulan.
Lihat solusi
const MS_HARI = 86400000;
const selisihHari = (a, b) =>
Math.round((Date.UTC(b.getFullYear(), b.getMonth(), b.getDate()) -
Date.UTC(a.getFullYear(), a.getMonth(), a.getDate())) / MS_HARI);
// 1
const hariMenujuTahunBaru = (hariIni) =>
selisihHari(hariIni, new Date(hariIni.getFullYear() + 1, 0, 1));
console.log(hariMenujuTahunBaru(new Date(2026, 9, 23))); // → 70
// 2
function bolehDaftar(lahir, hariIni) {
let umur = hariIni.getFullYear() - lahir.getFullYear();
const m = hariIni.getMonth() - lahir.getMonth();
if (m < 0 || (m === 0 && hariIni.getDate() < lahir.getDate())) umur--;
return umur >= 17;
}
console.log(bolehDaftar(new Date(2009, 9, 23), new Date(2026, 9, 23))); // → true
// 3
function jadwalCicilan(mulai, jumlahBulan) {
const hasil = [];
for (let i = 1; i <= jumlahBulan; i++) {
const tahun = mulai.getFullYear();
const bulan = mulai.getMonth() + i;
const terakhir = new Date(tahun, bulan + 1, 0).getDate();
hasil.push(new Date(tahun, bulan, Math.min(mulai.getDate(), terakhir)));
}
return hasil;
}
const fmt = new Intl.DateTimeFormat("id-ID", { dateStyle: "medium" });
console.log(jadwalCicilan(new Date(2026, 0, 31), 4).map((d) => fmt.format(d)));
// → [ '28 Feb 2026', '31 Mar 2026', '30 Apr 2026', '31 Mei 2026' ]
Ringkasan
- Selisih dua Date = milidetik; 1 hari = 86.400.000 ms.
- Untuk selisih hari kalender, normalkan ke tanggal (pakai
Date.UTC) laluMath.round. - Tambah hari dengan
setDate(getDate() + n)pada salinan; tambah bulan dengan aturan jepit. - Umur dihitung dari selisih tahun dikurangi 1 jika ulang tahun belum lewat.
- Countdown dihitung ulang dari target setiap tick; jadikan "sekarang" parameter agar mudah diuji.