API Date-Time java.time

Mengganti Date dan Calendar dengan LocalDate, LocalDateTime, Duration, Period, dan ZonedDateTime.

Daftar isi

Menghitung tanggal terasa mudah sampai panjang bulan, tahun kabisat, dan zona waktu ikut campur. Kelas Date dan Calendar warisan Java awal menangani hal itu dengan gaya yang rawan salah, antara lain bulan yang dihitung mulai dari nol dan objek yang isinya bisa berubah kapan saja. Sejak Java 8, penggantinya adalah java.time, yaitu API tanggal dan jam modern yang menjadi standar kode Java masa kini. Topik ini memperkenalkan LocalDate, LocalDateTime, Duration, Period, dan ZonedDateTime lewat contoh yang dapat dijalankan penuh.

Keluarga kelas java.time untuk tanggal dan jam#

java.time dibangun di atas kelas-kelas immutable (imutabel), yaitu objek yang nilainya tidak pernah berubah setelah dibuat. Metode seperti plusDays atau minusMonths tidak mengubah objek lamanya, melainkan mengembalikan objek baru berisi hasil perhitungannya. Sifat ini menguntungkan kode konkuren, karena satu objek tanggal yang dibagikan ke banyak thread tidak dapat dirusak oleh siapa pun. Tiga kelas inti yang paling sering dipakai adalah LocalDate untuk tanggal tanpa jam, LocalTime untuk jam tanpa tanggal, dan LocalDateTime untuk keduanya tanpa zona waktu. Semuanya dibuat dengan metode pabrik of yang menerima angka tanggal secara eksplisit.

Program berikut menyusun tenggat pekerjaan dari tanggal mulainya, mengujinya dengan pembanding isAfter, lalu memformat tanggal untuk pembaca manusia.

java DemoWaktu.java
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class DemoWaktu {

    public static void main(String[] args) {
        LocalDate mulai = LocalDate.of(2026, 11, 2);
        LocalDate tenggat = mulai.plusDays(21);

        System.out.println("Mulai   : " + mulai);
        System.out.println("Tenggat : " + tenggat);

        LocalDate serahTerima = LocalDate.of(2026, 11, 24);
        System.out.println("Sudah lewat tenggat? " + serahTerima.isAfter(tenggat));

        DateTimeFormatter pola = DateTimeFormatter.ofPattern("dd/MM/yyyy");
        System.out.println("Diformat: " + tenggat.format(pola));

        LocalDate dicari = LocalDate.of(2026, 11, 14);
        System.out.println("14 Nov 2026 jatuh pada " + dicari.getDayOfWeek());
    }
}
text Keluaran
Mulai   : 2026-11-02
Tenggat : 2026-11-23
Sudah lewat tenggat? true
Diformat: 23/11/2026
14 Nov 2026 jatuh pada SATURDAY

Metode of(2026, 11, 2) menghasilkan 2 November 2026 tanpa jebakan bulan karena angkanya ditulis apa adanya, tidak dihitung dari nol. Pemanggilan mulai.plusDays(21) mengembalikan objek baru bernilai 23 November 2026, sedangkan mulai tetap menunjuk 2 November, dan itulah alasan hasil penjumlahannya harus ditampung. Pemformat memakai pola dd/MM/yyyy yang isinya murni angka, sehingga keluarannya sama di mesin mana pun, sedangkan pola yang memuat nama hari atau nama bulan mengikuti pengaturan bahasa (locale) mesin yang menjalankan program.

Selisih waktu dengan Duration dan Period#

Dua kelas menangani selisih waktu dengan pembagian tugas yang jelas. Duration menghitung selisih berbasis waktu, yaitu jam, menit, dan detik, dan dipakai untuk LocalTime maupun LocalDateTime. Period menghitung selisih berbasis kalender, yaitu tahun, bulan, dan hari, dan dipakai untuk LocalDate. Pembagian ini penting karena panjang satu bulan tidak tetap dalam satuan detik, sehingga pernyataan “dua bulan” hanya bisa dijawab oleh kalender, bukan oleh hitungan detik.

Contoh berikut menghitung lama buka sebuah layanan dan masa kerja seseorang, lalu menurunkan tanggal pengingat dari operasi plus yang dirangkai.

java DemoSelisih.java
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;

public class DemoSelisih {

    public static void main(String[] args) {
        LocalTime buka = LocalTime.of(8, 0);
        LocalTime tutup = LocalTime.of(17, 30);
        Duration lamaBuka = Duration.between(buka, tutup);
        System.out.println("Lama buka    : " + lamaBuka);
        System.out.println("Dalam menit  : " + lamaBuka.toMinutes());

        LocalDateTime mulaiKerja = LocalDateTime.of(2024, 8, 5, 9, 0);
        LocalDate perhitungan = LocalDate.of(2026, 11, 2);
        Period masaKerja = Period.between(mulaiKerja.toLocalDate(), perhitungan);
        System.out.println("Masa kerja   : " + masaKerja.getYears() + " tahun "
                + masaKerja.getMonths() + " bulan " + masaKerja.getDays() + " hari");

        LocalDate pengingat = perhitungan.plusMonths(1).plusDays(10);
        System.out.println("Pengingat    : " + pengingat);
    }
}
text Keluaran
Lama buka    : PT9H30M
Dalam menit  : 570
Masa kerja   : 2 tahun 2 bulan 28 hari
Pengingat    : 2026-12-12

Duration.between menjawab pertanyaan pertama dengan PT9H30M, yaitu notasi standar ISO-8601 untuk sembilan jam tiga puluh menit, dan metode toMinutes menerjemahkannya menjadi 570 menit. Untuk masa kerja, Period.between mengerjakan hal yang tidak bisa dijawab oleh detik. Selisih 5 Agustus 2024 sampai 2 November 2026 dipecah menjadi dua tahun, dua bulan, dan dua puluh delapan hari sesuai panjang bulan yang sebenarnya. Rangkaian plusMonths(1).plusDays(10) pada baris terakhir menunjukkan gaya berantai yang lazim dipakai karena setiap operasi mengembalikan objek siap pakai.

Waktu lintas zona dengan ZonedDateTime#

ZonedDateTime melengkapi tanggal dan jam dengan ZoneId, yaitu identitas zona waktu seperti Asia/Jakarta atau America/New_York. Konsep kuncinya adalah instant, yakni satu titik waktu absolut yang sama untuk seluruh dunia, sedangkan angka jam yang tampil bergantung pada zona yang memandangnya. Metode withZoneSameInstant memindahkan pandangan tanpa mengubah titik waktunya, sehingga wajar bila angka jamnya ikut bergeser.

Program berikut menjadwalkan rapat pada pukul 09:00 waktu Jakarta dan menampilkannya pada zona Tokyo, lalu memperlihatkan perilaku java.time ketika pergantian daylight saving time (DST) memakan satu jam di New York.

java DemoZona.java
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class DemoZona {

    public static void main(String[] args) {
        ZonedDateTime rapatJakarta = LocalDateTime.of(2026, 11, 3, 9, 0)
                .atZone(ZoneId.of("Asia/Jakarta"));
        ZonedDateTime rapatTokyo = rapatJakarta.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));

        System.out.println("Rapat di Jakarta : " + rapatJakarta);
        System.out.println("Rapat di Tokyo   : " + rapatTokyo);

        ZonedDateTime lalu = LocalDateTime.of(2026, 3, 8, 1, 30)
                .atZone(ZoneId.of("America/New_York"));
        ZonedDateTime sesudah = lalu.plusHours(1);
        System.out.println("Sebelum pergantian DST : " + lalu);
        System.out.println("Sesudah plus satu jam  : " + sesudah);
    }
}
text Keluaran
Rapat di Jakarta : 2026-11-03T09:00+07:00[Asia/Jakarta]
Rapat di Tokyo   : 2026-11-03T11:00+09:00[Asia/Tokyo]
Sebelum pergantian DST : 2026-03-08T01:30-05:00[America/New_York]
Sesudah plus satu jam  : 2026-03-08T03:30-04:00[America/New_York]

Pukul 09:00 di Jakarta memang setara pukul 11:00 di Tokyo karena selisih dua jam, dan kedua baris itu menggambarkan satu titik waktu yang sama. Contoh kedua lebih menarik. Pada 8 Maret 2026, jam di New York digeser maju dari pukul 02:00 langsung menjadi 03:00, sehingga pukul 02:30 pada tanggal itu tidak pernah ada. Menambahkan satu jam pada 01:30 pun menghasilkan 03:30, dan perpindahan offset dari -05:00 ke -04:00 terlihat langsung pada keluarannya. Perhitungan semacam inilah yang dulu sering meleset di tangan Calendar.

Mengganti kode Date dan Calendar#

Migrasi dari kelas lama menjadi mudah bila setiap kebutuhan dipetakan ke padanannya, sebagaimana tabel berikut.

KebutuhanGaya lamaGaya java.time
Tanggal hari ininew Date()LocalDate.now()
Tanggal tertentunew GregorianCalendar(2026, Calendar.NOVEMBER, 2)LocalDate.of(2026, 11, 2)
Menambah 21 harical.add(Calendar.DAY_OF_MONTH, 21)tenggat = mulai.plusDays(21)
Selisih dua tanggalhitung manual dalam milidetikPeriod.between(a, b)
Format untuk pembacaSimpleDateFormatDateTimeFormatter

Potongan berikut memperlihatkan gaya lama yang menjadi sumber keluhan, ditulis hanya agar bentuknya dapat dikenali.

java Contoh: gaya lama dengan Date dan Calendar
// tidak-dijalankan
Date dulu = new GregorianCalendar(2026, Calendar.NOVEMBER, 2).getTime();

// menambah 21 hari lewat milidetik pada objek yang sama
dulu.setTime(dulu.getTime() + 21L * 24 * 60 * 60 * 1000);

Dua keluhan utama terlihat jelas pada potongan itu. Pertama, angka bulan pada Calendar dihitung dari nol, sehingga pemanggilan new GregorianCalendar(2026, 11, 2) yang terlihat seperti November sebenarnya jatuh pada Desember. Kedua, setTime mengubah objek yang sudah ada, sehingga kode lain yang masih memegang referensinya mendadak membaca tanggal yang sudah berubah. Kelas SimpleDateFormat menambah satu masalah karena tidak aman dipakai bersama thread, sedangkan DateTimeFormatter aman karena imutabel seperti kelas tanggalnya.

Kesalahan umum#

  • Mengabaikan nilai kembalian operasi tanggal. Karena kelas java.time imutabel, pemanggilan mulai.plusDays(21) tanpa penugasan tidak mengubah apa pun. Tangkap hasilnya, misalnya tenggat = mulai.plusDays(21).
  • Memakai Duration.between untuk dua LocalDate. Kelas itu bekerja pada satuan waktu, sehingga pemanggilannya pada tanggal murni melempar UnsupportedTemporalTypeException. Gunakan Period.between untuk LocalDate.
  • Membuat ZonedDateTime dari now() tanpa zona eksplisit. JVM memakai zona default mesin, sehingga hasilnya berbeda antar mesin dan programnya sulit diuji. Sebutkan ZoneId secara eksplisit pada kode yang peduli zona.
  • Salah membaca huruf pola DateTimeFormatter. Huruf pola punya arti tetap, misalnya MM untuk bulan dua digit dan mm untuk menit, sehingga pola yang tertukar menghasilkan angka yang salah pula tanpa pesan error.

Ringkasan#

  • Kelas java.time imutabel, sehingga setiap operasi mengembalikan objek baru dan objeknya aman dibagikan antar-thread.
  • LocalDate, LocalTime, dan LocalDateTime menampung tanggal dan jam tanpa zona, dibuat dengan of dan diubah lewat plus serta minus.
  • Duration menghitung selisih berbasis waktu, sedangkan Period menghitung selisih berbasis kalender.
  • ZonedDateTime melengkapinya dengan ZoneId, dan withZoneSameInstant memindahkan pandangan zona tanpa mengubah titik waktunya.

Referensi#