Bekerja dengan tanggal hampir selalu melibatkan perhitungan: kapan kedaluwarsa, berapa lama pengguna berlangganan, kapan pengingat berikutnya, atau berapa hari antara dua momen. java.time menyediakan dua kelompok operasi: aritmatika berbasis kalender (plusDays, plusMonths, Period) yang menghormati panjang bulan, dan aritmatika berbasis waktu absolut (plus(Duration), ChronoUnit.SECONDS.between) yang menghitung secara presisi. Bab ini membedah keduanya dan menyoroti jebakan Period versus Duration.
Penambahan dan Pengurangan #
Setiap tipe temporal di java.time menyediakan method plusXxx(...) dan minusXxx(...). Operasi ini tidak mengubah instance asli (semua tipe java.time bersifat immutable), melainkan mengembalikan instance baru.
| Operasi | Tipe | Menghormati Kalender? | Contoh |
|---|---|---|---|
plusDays(7) | LocalDate | Ya | 15 Jan + 7 hari = 22 Jan |
plusMonths(1) | LocalDate | Ya | 31 Jan + 1 bln = 28 Feb |
plus(Duration) | LocalDateTime | Tidak | 15 Jan 08:30 + 24 jam |
plus(Period) | LocalDate | Ya | 15 Jan + P1M15D |
minusYears(1) | LocalDate | Ya | 2026 → 2025 |
Perbedaan Period dan Duration adalah sumber bug tersekat. Duration.ofDays(1) selalu 24 jam absolut, sedangkan Period.ofDays(1) berarti “tanggal besok” yang bisa 23 atau 25 jam saat daylight saving time.
Contoh Lengkap: Penambahan, Period vs Duration, ChronoUnit #
Kode berikut mendemonstrasikan enam skenario aritmatika, termasuk perbedaan Period vs Duration dan perhitungan selisih antar tanggal dengan dua pendekatan:
import java.time.Duration
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.Period
import java.time.temporal.ChronoUnit
fun main() {
val tanggal: LocalDate = LocalDate.of(2026, 1, 15)
val jadwal: LocalDateTime = LocalDateTime.of(2026, 1, 15, 8, 30)
// 1. Penambahan berbasis tanggal: plusDays, plusWeeks, plusMonths, plusYears
println("=== Penambahan Period (kalender) ===")
println("tanggal : $tanggal")
println("plusDays(7) : ${tanggal.plusDays(7)}")
println("plusWeeks(2) : ${tanggal.plusWeeks(2)}")
println("plusMonths(1) : ${tanggal.plusMonths(1)}")
println("plusYears(1) : ${tanggal.plusYears(1)}")
println("minusDays(10) : ${tanggal.minusDays(10)}")
// 2. Penambahan Period sebagai objek
val periodeHalal: Period = Period.of(0, 6, 15)
println()
println("=== Period sebagai objek ===")
println("periodeHalal : $periodeHalal")
println("tanggal.plus(periode) : ${tanggal.plus(periodeHalal)}")
println("tanggal.minus(periode) : ${tanggal.minus(periodeHalal)}")
// 3. Penambahan berbasis jam: plusHours, plusMinutes via Duration
println()
println("=== Penambahan Duration (jam/menit/detik) ===")
val durasiRapat: Duration = Duration.ofMinutes(90)
val durasiIstirahat: Duration = Duration.ofHours(2).plusMinutes(15)
println("jadwal : $jadwal")
println("plus(durasiRapat) : ${jadwal.plus(durasiRapat)}")
println("plus(durasiIstirahat) : ${jadwal.plus(durasiIstirahat)}")
println("minusNanos(500) : ${jadwal.minusNanos(500)}")
// 4. Period vs Duration: mengapa 1 hari bisa berbeda
println()
println("=== Period vs Duration ===")
val periodeSehari: Period = Period.ofDays(1)
val durasiSehari: Duration = Duration.ofDays(1)
println("Period.ofDays(1) : $periodeSehari (kalender)")
println("Duration.ofDays(1) : $durasiSehari (24 jam absolut)")
println("tanggal + Period(1 day): ${tanggal.plus(periodeSehari)}")
println("jadwal + Duration(1 day): ${jadwal.plus(durasiSehari)}")
// 5. Menghitung selisih antar tanggal
println()
println("=== Selisih antar tanggal ===")
val awal = LocalDate.of(2026, 1, 15)
val akhir = LocalDate.of(2026, 3, 2)
val selisihPeriod: Period = Period.between(awal, akhir)
val selisihHari: Long = ChronoUnit.DAYS.between(awal, akhir)
val selisihBulan: Long = ChronoUnit.MONTHS.between(awal, akhir)
println("awal : $awal")
println("akhir : $akhir")
println("Period.between : $selisihPeriod")
println("DAYS.between : $selisihHari")
println("MONTHS.between : $selisihBulan")
// 6. Menghitung selisih antar instan (jam/detik)
println()
println("=== Selisih antar instan ===")
val a = LocalDateTime.of(2026, 1, 15, 8, 0)
val b = LocalDateTime.of(2026, 1, 15, 10, 45)
val selisihDurasi: Duration = Duration.between(a, b)
println("a : $a")
println("b : $b")
println("Duration.between : $selisihDurasi (${selisihDurasi.toMinutes()} menit)")
println("HOURS.between : ${ChronoUnit.HOURS.between(a, b)}")
println("MINUTES.between : ${ChronoUnit.MINUTES.between(a, b)}")
// 7. Iterasi tanggal per minggu
println()
println("=== Iterasi per minggu ===")
var cursor = tanggal
repeat(4) { i ->
println("minggu ${i + 1}: $cursor (${cursor.dayOfWeek})")
cursor = cursor.plusWeeks(1)
}
}Output yang Dihasilkan:
=== Penambahan Period (kalender) ===
tanggal : 2026-01-15
plusDays(7) : 2026-01-22
plusWeeks(2) : 2026-01-29
plusMonths(1) : 2026-02-15
plusYears(1) : 2027-01-15
minusDays(10) : 2026-01-05
=== Period sebagai objek ===
periodeHalal : P6M15D
tanggal.plus(periode) : 2026-07-30
tanggal.minus(periode) : 2025-06-30
=== Penambahan Duration (jam/menit/detik) ===
jadwal : 2026-01-15T08:30
plus(durasiRapat) : 2026-01-15T10:00
plus(durasiIstirahat) : 2026-01-15T10:45
minusNanos(500) : 2026-01-15T08:29:59.999999500
=== Period vs Duration ===
Period.ofDays(1) : P1D (kalender)
Duration.ofDays(1) : PT24H (24 jam absolut)
tanggal + Period(1 day): 2026-01-16
jadwal + Duration(1 day): 2026-01-16T08:30
=== Selisih antar tanggal ===
awal : 2026-01-15
akhir : 2026-03-02
Period.between : P1M15D
DAYS.between : 46
MONTHS.between : 1
=== Selisih antar instan ===
a : 2026-01-15T08:00
b : 2026-01-15T10:45
Duration.between : PT2H45M (165 menit)
HOURS.between : 2
MINUTES.between : 165
=== Iterasi per minggu ===
minggu 1: 2026-01-15 (THURSDAY)
minggu 2: 2026-01-22 (THURSDAY)
minggu 3: 2026-01-29 (THURSDAY)
minggu 4: 2026-02-05 (THURSDAY)Mari kita telaah baris-baris kode di atas:
- Baris 14-18 (
plusDays(7),plusMonths(1)): method ini mengembalikan instance baru, instancetanggalasli tidak berubah.plusMonths(1)pada 15 Januari 2026 menghasilkan 15 Februari 2026 — ini aman karena 15 ada di bulan Februari. NamunplusMonths(1)pada 31 Januari akan menghasilkan 28 Februari (atau 29 di tahun kabisat), bukan 3 Maret, karenajava.timememotong ke akhir bulan. - Baris 23 (
Period.of(0, 6, 15)): membuat periode 6 bulan 15 hari.Period.of(years, months, days)menerima tiga komponen. Menambahkannya ke 15 Januari 2026 menghasilkan 30 Juli 2026 (15 Jan + 6 bulan = 15 Jul; + 15 hari = 30 Jul). - Baris 32-33 (
Duration.ofHours(2).plusMinutes(15)): durasi dapat dirantai. HasilnyaPT2H15Msetara denganDuration.ofMinutes(135).LocalDateTime.plus(Duration)menambahkan durasi absolut ke jam. - Baris 41-46 (Period vs Duration):
Period.ofDays(1)ditambahkan keLocalDatemenghasilkan tanggal besok.Duration.ofDays(1)ditambahkan keLocalDateTimemenghasilkan jam 24 jam kemudian. Pada tanggal yang sama tanpa DST, hasilnya tampak sama; perbedaan muncul saat ada perubahan DST. - Baris 52 (
Period.between(awal, akhir)): menghasilkanP1M15D— 1 bulan 15 hari.ChronoUnit.DAYS.betweenmenghasilkan 46 hari total. Keduanya benar namun melaporkan satuan berbeda — pilih sesuai kebutuhan tampilan. - Baris 64 (
Duration.between(a, b)): untukLocalDateTimebekerja sepertiInstant. HasilnyaPT2H45M= 165 menit.ChronoUnit.MINUTES.betweenlangsung memberi angka menit (165), sedangkanHOURS.betweenmembulatkan ke bawah menjadi 2 (kehilangan 45 menit).
Strategi Iterasi dan Akumulasi #
Iterasi tanggal per minggu, per bulan, atau per tahun dapat dilakukan dengan loop sederhana seperti pada blok 7. Untuk iterasi panjang (mis. 5 tahun per minggu = 260 iterasi), pendekatan ini tetap efisien karena LocalDate ringan. Alternatif idiomatik Kotlin menggunakan sequence:
fun rentangMingguan(awal: LocalDate, jumlah: Int): Sequence<LocalDate> = sequence {
var cursor = awal
repeat(jumlah) {
yield(cursor)
cursor = cursor.plusWeeks(1)
}
}
rentangMingguan(LocalDate.of(2026, 1, 15), 4).forEach { println(it) }Pola ini memproses tanggal secara lazy, cocok bila jumlah iterasi besar atau bila kita ingin memutus lebih awal dengan takeWhile.
Referensi Lanjutan #
- java.time package
— dokumentasi
java.timeuntukPeriod,Duration, dan operasi aritmetika tanggal-jam
Selanjutnya #
Konsep individual sudah lengkap — saatnya menyatukannya menjadi praktik terbaik. Bab Date-Time Best Practices akan merangkum pedoman memilih tipe yang benar, strategi penyimpanan, dan jebakan umum yang harus dihindari.