Menggeser tanggal adalah salah satu operasi paling sering dilakukan aplikasi: jatuh tempo invoice tujuh hari setelah terbit, tenggat laporan akhir bulan, pengingat tiga hari sebelum acara, cuti tahunan bertambah dua belas bulan tiap periode. java.time menyediakan aritmetika yang mengikuti kalender sungguhan — bukan asumsi “satu bulan = 30 hari” yang hasilnya meleset diam-diam — dengan method bernama jelas yang semuanya mengembalikan objek baru. Pada Chapter ini kita latihan penjumlahan dan pengurangan, memahami aturan penyesuaian ujung bulan, lalu melompat ke posisi kalender istimewa memakai TemporalAdjusters.
Penjumlahan dan Pengurangan #
Setiap tipe inti punya keluarga method plus dan minus: plusDays, plusWeeks, plusMonths, plusYears, hingga minusDays dan kawan-kawannya. Selain bentuk khusus per satuan, ada dua bentuk umum: plus(Period) untuk menambah rentang kalender, dan plus(angka, ChronoUnit) untuk menambah satuan pilihan. Ketiganya saling melengkapi dan hasilnya konsisten satu sama lain:
import java.time.LocalDate;
import java.time.Period;
import java.time.temporal.ChronoUnit;
public class AritmetikaTanggal {
public static void main(String[] args) {
LocalDate mulai = LocalDate.of(2026, 1, 15);
System.out.println("plusDays(45) : " + mulai.plusDays(45));
System.out.println("plusWeeks(2) : " + mulai.plusWeeks(2));
System.out.println("plusMonths(2) : " + mulai.plusMonths(2));
System.out.println("plusYears(4) : " + mulai.plusYears(4));
System.out.println("minusDays(10) : " + mulai.minusDays(10));
System.out.println("plus ChronoUnit : " + mulai.plus(10, ChronoUnit.DAYS));
System.out.println("plus Period : " + mulai.plus(Period.ofMonths(2)));
// Penjumlahan bulan menyesuaikan panjang bulan tujuan.
System.out.println("31 Mar minus 1 bln : " + LocalDate.of(2026, 3, 31).minusMonths(1));
System.out.println("31 Jan plus 1 bln : " + LocalDate.of(2026, 1, 31).plusMonths(1));
// Tahun kabisat: 29 Feb 2024 maju 2 tahun jatuh ke 28 Feb.
System.out.println("29 Feb 2024 + 2 thn: " + LocalDate.of(2024, 2, 29).plusYears(2));
}
}Output yang Dihasilkan:
plusDays(45) : 2026-03-01
plusWeeks(2) : 2026-01-29
plusMonths(2) : 2026-03-15
plusYears(4) : 2030-01-15
minusDays(10) : 2026-01-05
plus ChronoUnit : 2026-01-25
plus Period : 2026-03-15
31 Mar minus 1 bln : 2026-02-28
31 Jan plus 1 bln : 2026-02-28
29 Feb 2024 + 2 thn: 2026-02-28Mari kita telaah baris-baris kode di atas:
- Baris 8 (
plusDays(45)): 15 Januari 2026 maju 45 hari melewati sisa Januari dan Februari yang berjumlah 28 hari, mendarat di2026-03-01— kalender sungguhan yang bekerja, bukan aritmetika modulo 30. - Baris 13-14:
plus(10, ChronoUnit.DAYS)danplus(Period.ofMonths(2))adalah bentuk umum; hasilnya sama dengan method khusus per satuan. - Baris 17-18: penjumlahan bulan menjaga tanggal bila memungkinkan — 15 Januari maju dua bulan tetap jatuh di tanggal 15 Maret.
- Baris 21-22: penyesuaian ujung bulan dijelaskan pada bagian berikutnya.
Penyesuaian Ujung Bulan dan Tahun Kabisat #
Bila hasil penjumlahan bulan atau tahun tidak muat di bulan/tahun tujuan, java.time tidak melempar exception dan tidak pula meluber ke bulan berikutnya. Aturannya: hasil dipatok ke tanggal terakhir yang sahih pada bulan tujuan. Dari contoh di atas, 31 Januari + 1 bulan menjadi 28 Februari (bukan 3 Maret), 31 Maret − 1 bulan menjadi 28 Februari, dan 29 Februari 2024 + 2 tahun menjadi 28 Februari 2026 karena 2026 bukan tahun kabisat. Patokan ini masuk akal untuk domain seperti tagihan dan kontrak — “akhir Februari” tetap berada di dalam Februari.
Melompat dengan TemporalAdjusters #
Kebutuhan aritmetika sering bukan “tambah tujuh hari” melainkan “ke posisi tertentu”: hari pertama bulan berikutnya, hari terakhir bulan ini, atau Minggu ketiga bulan ini. Menghitung sendiri posisi-posisi itu rawan salah; class TemporalAdjusters (perhatikan bentuk jamaknya — factory dari penyesuai) menyediakan penyesuai siap pakai yang dipakai lewat method with:
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
public class PenyesuaiTemporal {
public static void main(String[] args) {
LocalDate hariIni = LocalDate.of(2026, 1, 15); // Kamis
System.out.println("firstDayOfMonth : " + hariIni.with(TemporalAdjusters.firstDayOfMonth()));
System.out.println("lastDayOfMonth : " + hariIni.with(TemporalAdjusters.lastDayOfMonth()));
System.out.println("firstDayOfNextMonth : " + hariIni.with(TemporalAdjusters.firstDayOfNextMonth()));
System.out.println("lastDayOfYear : " + hariIni.with(TemporalAdjusters.lastDayOfYear()));
System.out.println("next SUNDAY : " + hariIni.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)));
System.out.println("nextOrSame THURSDAY : " + hariIni.with(TemporalAdjusters.nextOrSame(DayOfWeek.THURSDAY)));
System.out.println("Minggu ke-3 Januari : " + hariIni.with(TemporalAdjusters.dayOfWeekInMonth(3, DayOfWeek.SUNDAY)));
// Pengukuran jarak dengan ChronoUnit.
LocalDate target = LocalDate.of(2026, 3, 20);
System.out.println("jarak DAYS : " + hariIni.until(target, ChronoUnit.DAYS));
System.out.println("jarak WEEKS : " + hariIni.until(target, ChronoUnit.WEEKS));
// Rangkaian konversi: tanggal -> tanggal-waktu -> berzona.
LocalDateTime rapat = hariIni.atTime(9, 0);
System.out.println("atTime + atZone : " + rapat.atZone(ZoneId.of("Asia/Jakarta")));
}
}Output yang Dihasilkan:
firstDayOfMonth : 2026-01-01
lastDayOfMonth : 2026-01-31
firstDayOfNextMonth : 2026-02-01
lastDayOfYear : 2026-12-31
next SUNDAY : 2026-01-18
nextOrSame THURSDAY : 2026-01-15
Minggu ke-3 Januari : 2026-01-18
jarak DAYS : 64
jarak WEEKS : 9
atTime + atZone : 2026-01-15T09:00+07:00[Asia/Jakarta]Bedah singkatnya: baris 14-17 memakai empat penyesuai posisi — firstDayOfMonth, lastDayOfMonth, firstDayOfNextMonth, dan lastDayOfYear — yang semuanya mengembalikan objek LocalDate baru. Baris 18 memakai next(DayOfWeek.SUNDAY): dari Kamis 15 Januari, hari Minggu berikutnya adalah 18 Januari. Baris 19 memakai nextOrSame(DayOfWeek.THURSDAY) yang mengembalikan tanggal yang sama karena 15 Januari 2026 memang sudah Kamis. Baris 20 memakai dayOfWeekInMonth(3, DayOfWeek.SUNDAY) untuk mencari Minggu ketiga Januari 2026 — jatuh di tanggal 18; penyesuai ini tepat untuk jadwal bulanan seperti “rapat rutin Senin pertama” atau “gajian tanggal 25”.
Mengukur Jarak dan Merangkai Konversi #
Aritmetika dua arah: plus/minus menggeser, until mengukur. Baris 23-25 pada contoh menghitung jarak 15 Januari ke 20 Maret 2026 — 64 hari atau 9 minggu penuh (ChronoUnit.WEEKS membulatkan ke bawah karena sisa lima hari tidak membentuk minggu utuh). Terakhir, baris 28-29 memperlihatkan rangkaian konversi yang lazim di lapisan presentasi: atTime(9, 0) menempelkan jam pada tanggal menjadi LocalDateTime, lalu atZone(...) memberinya makna zona menjadi ZonedDateTime siap tampil.
Aritmetika pada tipe tanpa zona memang lurus: satu hari selalu 24 jam di atas kertas. Begitu zona waktu dengan aturan daylight saving masuk ke perhitungan, “satu hari” bisa berarti 23 atau 25 jam nyata — dan di situlah banyak aplikasi tersandung. Bab berikutnya membahas penanganan time zones secara utuh, lengkap dengan studi kasus penerbangan lintas zona.
Referensi #
- Oracle: Class LocalDate (Java SE 25 API)
- Oracle: Class TemporalAdjusters (Java SE 25 API)
- Oracle: Class ChronoUnit (Java SE 25 API)
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
AritmetikaTanggal.java
Unduh berkasimport java.time.LocalDate;
import java.time.Period;
import java.time.temporal.ChronoUnit;
public class AritmetikaTanggal {
public static void main(String[] args) {
LocalDate mulai = LocalDate.of(2026, 1, 15);
System.out.println("plusDays(45) : " + mulai.plusDays(45));
System.out.println("plusWeeks(2) : " + mulai.plusWeeks(2));
System.out.println("plusMonths(2) : " + mulai.plusMonths(2));
System.out.println("plusYears(4) : " + mulai.plusYears(4));
System.out.println("minusDays(10) : " + mulai.minusDays(10));
System.out.println("plus ChronoUnit : " + mulai.plus(10, ChronoUnit.DAYS));
System.out.println("plus Period : " + mulai.plus(Period.ofMonths(2)));
// Penjumlahan bulan menyesuaikan panjang bulan tujuan.
System.out.println("31 Mar minus 1 bln : " + LocalDate.of(2026, 3, 31).minusMonths(1));
System.out.println("31 Jan plus 1 bln : " + LocalDate.of(2026, 1, 31).plusMonths(1));
// Tahun kabisat: 29 Feb 2024 maju 2 tahun jatuh ke 28 Feb.
System.out.println("29 Feb 2024 + 2 thn: " + LocalDate.of(2024, 2, 29).plusYears(2));
}
}
PenyesuaiTemporal.java
Unduh berkasimport java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
public class PenyesuaiTemporal {
public static void main(String[] args) {
LocalDate hariIni = LocalDate.of(2026, 1, 15); // Kamis
System.out.println("firstDayOfMonth : " + hariIni.with(TemporalAdjusters.firstDayOfMonth()));
System.out.println("lastDayOfMonth : " + hariIni.with(TemporalAdjusters.lastDayOfMonth()));
System.out.println("firstDayOfNextMonth : " + hariIni.with(TemporalAdjusters.firstDayOfNextMonth()));
System.out.println("lastDayOfYear : " + hariIni.with(TemporalAdjusters.lastDayOfYear()));
System.out.println("next SUNDAY : " + hariIni.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)));
System.out.println("nextOrSame THURSDAY : " + hariIni.with(TemporalAdjusters.nextOrSame(DayOfWeek.THURSDAY)));
System.out.println("Minggu ke-3 Januari : " + hariIni.with(TemporalAdjusters.dayOfWeekInMonth(3, DayOfWeek.SUNDAY)));
// Pengukuran jarak dengan ChronoUnit.
LocalDate target = LocalDate.of(2026, 3, 20);
System.out.println("jarak DAYS : " + hariIni.until(target, ChronoUnit.DAYS));
System.out.println("jarak WEEKS : " + hariIni.until(target, ChronoUnit.WEEKS));
// Rangkaian konversi: tanggal -> tanggal-waktu -> berzona.
LocalDateTime rapat = hariIni.atTime(9, 0);
System.out.println("atTime + atZone : " + rapat.atZone(ZoneId.of("Asia/Jakarta")));
}
}