Kotlin berjalan di atas JVM sehingga seluruh paket java.time dapat dipakai langsung tanpa library tambahan. Tidak seperti Java yang memerlukan boilerplate static import dan pemanggilan method eksplisit, Kotlin memungkinkan kita memperluas tipe java.time dengan extension function yang mempercantik pemakaian sehari-hari. Bab ini menunjukkan cara mengonsumsi LocalDate, LocalTime, Instant, ZonedDateTime, Duration, dan Period dari Kotlin, serta bagaimana Kotlin menambah lapisan idiomatik tanpa mengubah API inti.

java.time Sebagai Warga Kelas Satu #

Setiap kelas di java.time.* dapat diimpor dan dipakai langsung di Kotlin. Tidak ada adapter, tidak ada wrapper, tidak ada tipe bayangan. Yang ada adalah dua peningkatan kecil yang membuat pemakaian lebih nyaman:

AspekJavaKotlin
Pemanggilan statisLocalDate.of(2026, 1, 15)LocalDate.of(2026, 1, 15) (sama)
Getter propertidate.getDayOfMonth()date.dayOfMonth (sintaks properti)
Extension customharus di utility classboleh dideklarasikan di file mana pun
Interpolasi stringString.format(...)"hasil: ${date.dayOfMonth}"

Konversi dari method Java menjadi properti Kotlin bersifat otomatis untuk pasangan getXxx()/setXxx(). Kita menulis tanggal.dayOfMonth, kompilator menerjemahkan menjadi tanggal.getDayOfMonth() pada bytecode.

Contoh Lengkap: java.time + Extension Kotlin #

Kode berikut mengonsumsi LocalDate, LocalTime, LocalDateTime, Instant, Duration, ZonedDateTime, Period, dan ChronoUnit dari Kotlin, lalu mendefinisikan dua extension function yang bekerja di atas tipe tersebut:

kotlin
import java.time.Duration
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.Period
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit

// Extension function idiomatik Kotlin di atas tipe java.time
fun LocalDateTime.hariKerjaBerikutnya(): LocalDateTime {
    var hasil = this.plusDays(1)
    while (hasil.dayOfWeek.value >= 6) { // 6=Sabtu, 7=Minggu
        hasil = hasil.plusDays(1)
    }
    return hasil
}

fun Instant.formatRfc1123(zoneId: ZoneId = ZoneId.of("UTC")): String {
    val zdt = this.atZone(zoneId)
    val fmt = DateTimeFormatter.RFC_1123_DATE_TIME
    return fmt.format(zdt)
}

fun main() {
    // 1. Mengonsumsi java.time langsung dari Kotlin, tanpa wrapper
    val tanggal: LocalDate = LocalDate.of(2026, 1, 15)
    val jam: LocalTime = LocalTime.of(8, 30)
    val jadwal: LocalDateTime = LocalDateTime.of(tanggal, jam)
    println("=== java.time langsung ===")
    println("LocalDate     : $tanggal")
    println("LocalTime     : $jam")
    println("LocalDateTime : $jadwal")

    // 2. Instant dan Duration
    val instan: Instant = Instant.parse("2026-01-15T08:30:00Z")
    val durasi: Duration = Duration.between(instan, instan.plusSeconds(3600))
    println()
    println("=== Instant + Duration ===")
    println("instan        : $instan")
    println("durasi        : $durasi (${durasi.toMinutes()} menit)")

    // 3. ZonedDateTime dengan ZoneId
    val zonaJakarta: ZoneId = ZoneId.of("Asia/Jakarta")
    val zdt: ZonedDateTime = jadwal.atZone(zonaJakarta)
    println()
    println("=== ZonedDateTime ===")
    println("zdt           : $zdt")
    println("offset        : ${zdt.offset}")

    // 4. Period dan ChronoUnit
    val periode: Period = Period.between(tanggal, tanggal.plusMonths(1).plusDays(3))
    println()
    println("=== Period + ChronoUnit ===")
    println("periode       : $periode")
    val hariAntara: Long = ChronoUnit.DAYS.between(tanggal, tanggal.plusDays(45))
    println("hariAntara    : $hariAntara")

    // 5. Extension function Kotlin di atas java.time
    val jumat: LocalDateTime = LocalDateTime.of(2026, 1, 16, 17, 0) // Jumat
    val seninBerikutnya: LocalDateTime = jumat.hariKerjaBerikutnya()
    println()
    println("=== Extension function Kotlin ===")
    println("jumat         : $jumat (${jumat.dayOfWeek})")
    println("seninBerikutnya: $seninBerikutnya (${seninBerikutnya.dayOfWeek})")

    // 6. RFC-1123 format via extension pada Instant
    println()
    println("=== RFC-1123 via extension ===")
    println("formatJakarta : ${instan.formatRfc1123(ZoneId.of("Asia/Jakarta"))}")
    println("formatUTC     : ${instan.formatRfc1123()}")
}

Output yang Dihasilkan:

text
=== java.time langsung ===
LocalDate     : 2026-01-15
LocalTime     : 08:30
LocalDateTime : 2026-01-15T08:30

=== Instant + Duration ===
instan        : 2026-01-15T08:30:00Z
durasi        : PT1H (60 menit)

=== ZonedDateTime ===
zdt           : 2026-01-15T08:30+07:00[Asia/Jakarta]
offset        : +07:00

=== Period + ChronoUnit ===
periode       : P1M3D
hariAntara    : 45

=== Extension function Kotlin ===
jumat         : 2026-01-16T17:00 (FRIDAY)
seninBerikutnya: 2026-01-19T17:00 (MONDAY)

=== RFC-1123 via extension ===
formatJakarta : Thu, 15 Jan 2026 15:30:00 +0700
formatUTC     : Thu, 15 Jan 2026 08:30:00 GMT

Mari kita telaah baris-baris kode di atas:

  1. Baris 16-21 (fun LocalDateTime.hariKerjaBerikutnya()): extension function pada LocalDateTime. Kita menulis this.plusDays(1) dan this.dayOfWeek.value layaknya method instance, padahal di bytecode ini menjadi static method dengan parameter LocalDateTime receiver. Ini idiom khas Kotlin di atas API Java.
  2. Baris 23-27 (fun Instant.formatRfc1123(zoneId)): extension function dengan parameter default ZoneId.of("UTC"). Default parameter adalah fitur Kotlin yang tidak ada di Java, sehingga pemanggil dapat memilih zona atau memakai default tanpa overload manual.
  3. Baris 38-39 (tanggal.dayOfMonth, jam dipakai langsung): LocalDate.of(2026, 1, 15) memanggil method statis Java. Tidak ada perubahan, hanya sintaks pemanggilan yang sama. Yang berbeda adalah saat membaca properti: kita menulis tanggal.dayOfWeek alih-alih tanggal.getDayOfWeek().
  4. Baris 50 (jadwal.atZone(zonaJakarta)): method instance Java dipanggil seperti biasa. LocalDateTime.atZone(ZoneId) mengembalikan ZonedDateTime dengan offset aktif. Karena 15 Januari tidak berada pada masa DST di Jakarta, offset yang dihasilkan +07:00.
  5. Baris 56 (ChronoUnit.DAYS.between(a, b)): enum ChronoUnit dari java.time.temporal menyediakan unit untuk menghitung jarak antar tipe temporal. DAYS.between mengembalikan Long, bukan Int, sehingga aman untuk rentang lebih dari 2 miliar hari.
  6. Baris 62 (jumat.hariKerjaBerikutnya()): pemanggilan extension function sama persis dengan method instance. 16 Januari 2026 adalah hari Jumat; melewatkan Sabtu/Minggu menghasilkan Senin 19 Januari 2026.

Strategi Mengemas Helper Tanggal #

Bila kita membangun aplikasi yang sering menghitung hari kerja, jangan membuat utility class berisi static method seperti di Java. Lebih idiomatik bila setiap perilaku didefinisikan sebagai extension function pada tipe yang relevan. Dengan begitu, kode pemanggil terlihat seperti method bawaan: jumat.hariKerjaBerikutnya(). Penempatan extension function dapat dipisah di file terkhusus (mis. DateExtensions.kt) sehingga dapat diimpor secara selektif.

Referensi Lanjutan #

Selanjutnya #

Pemakaian java.time langsung berfungsi penuh di JVM, namun kode yang sama tidak dapat dikompilasi ke Kotlin/Native atau Kotlin/JS. Bab kotlinx-datetime memperkenalkan library resmi JetBrains yang menyediakan API serupa untuk Kotlin Multiplatform.