kodingup.camp

Beranda/JavaScript Mastery: Prototype, Class, Waktu, Error & Async

Teknik Perhitungan Waktu

Modul 8 dari 153 menit bacaTime Calculation Techniques

Isi modul

    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

    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

    1. 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.
    2. Menghitung umur dengan / 365. Tahun kabisat membuat umur salah tepat di sekitar ulang tahun. Bandingkan tahun, bulan, tanggal.
    3. setMonth(+1) di tanggal 29-31. Bisa melompat dua bulan. Jepit ke tanggal terakhir bulan.
    4. 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.
    5. Mengubah Date input di dalam function. Pemanggil kaget datanya berubah. Selalu buat salinan.

    Latihan

    1. Mudah, Tulis hariMenujuTahunBaru(hariIni) yang mengembalikan jumlah hari kalender sampai 1 Januari tahun berikutnya.
    2. Sedang, Tulis bolehDaftar(tanggalLahir, hariIni) yang mengembalikan true jika umur minimal 17 tahun.
    3. 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

    Kuis pemahaman

    Pilih satu jawaban per soal. Penjelasan muncul setelah kamu menjawab.