Library java.time sangat baik, namun kode yang memakainya hanya dapat dikompilasi ke JVM. Saat kita membangun aplikasi Kotlin Multiplatform (KMP) yang menargetkan Native, JS, dan Android secara bersamaan, diperlukan library tanggal yang dapat berjalan di semua target tersebut. JetBrains merilis kotlinx-datetime untuk memenuhi kebutuhan ini. Bab ini memperkenalkan tipe utama library ini, perbedaan API dengan java.time, dan strategi pemilihan antara keduanya.

Apa Itu kotlinx-datetime #

kotlinx-datetime adalah library multiplatform resmi JetBrains yang menyediakan subset java.time dalam desain ulang yang konsisten lintas target. Library ini tidak menggantikan java.time di JVM — keduanya dapat berdampingan. Pada JVM, kotlinx-datetime menjadi thin wrapper di atas java.time; pada target Native dan JS, library ini memakai implementasi sendiri.

Tabel berikut membandingkan tipe utama yang tersedia:

Tipe kotlinx-datetimePadanan java.timeCatatan
Instantjava.time.InstantHampir identik, sama-sama UTC
LocalDatejava.time.LocalDateMirip, beberapa method berbeda nama
LocalDateTimejava.time.LocalDateTimeTersedia, namun jarang dipakai di KMP
DateTimePeriod(gabungan Period + Duration)Pengukur waktu fleksibel
DayOfWeekjava.time.DayOfWeekEnum sama
Monthjava.time.MonthEnum sama

Yang tidak tersedia di kotlinx-datetime: ZonedDateTime, OffsetDateTime, DateTimeFormatter dengan pola kompleks, ChronoUnit, dan sebagian besar fitur lanjutan java.time. Pengembang sengaja menjaga API tetap minimal agar dapat diimplementasikan secara konsisten di semua target.

Konsep API dan Padanan java.time #

Bila kita membandingkan cuplikan kotlinx-datetime dengan padanan java.time-nya, perbedaannya tipis namun konsisten. Cuplikan berikut menunjukkan padanan dalam sintaks java.time (yang dapat dikompilasi di lingkungan JVM murni). Pada proyek KMP nyata, ganti impor java.time.* dengan kotlinx.datetime.*:

kotlin Perbandingan: kotlinx-datetime vs java.time (kotlinx-datetime)
// kotlinx-datetime:
import kotlinx.datetime.*

val sekarang: Instant = Clock.System.now()
val hariIni: LocalDate = sekarang.toLocalDateTime(TimeZone.currentSystemDefault()).date
val besok: LocalDate = hariIni.plus(DatePeriod(days = 1))
val antara: Int = hariIni.until(besok, DateTimeUnit.DAY)
kotlin Perbandingan: kotlinx-datetime vs java.time (java.time)
// java.time:
import java.time.*
val sekarang: Instant = Instant.now()
val hariIni: LocalDate = LocalDate.now()
val besok: LocalDate = hariIni.plusDays(1)
val antara: Long = ChronoUnit.DAYS.between(hariIni, besok)

Perbedaan inti yang patut dicatat: kotlinx-datetime memakai Clock.System untuk mendapatkan waktu saat ini (memungkinkan injeksi test clock), DatePeriod sebagai padanan Period, dan DateTimeUnit sebagai padanan ChronoUnit. Pemisahan Clock sebagai parameter eksplisit adalah pola yang disarankan karena memudahkan testing.

Contoh Padanan java.time yang Dapat Dijalankan #

Karena library kotlinx-datetime tidak terpasang di lingkungan ini, kode berikut memakai java.time sebagai padanan untuk mendemonstrasikan konsep API yang setara. Komentar pada tiap blok menandai nama padanan di kotlinx-datetime:

kotlin
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter

// Catatan: file ini mendemonstrasikan konsep API kotlinx-datetime
// dengan menggunakan java.time sebagai padanan yang dapat dikompilasi.
// Pada proyek Kotlin Multiplatform nyata, kita akan mengimpor
// kotlinx.datetime.* sebagai pengganti java.time.*

fun main() {
    // 1. Instant kotlinx-datetime: titik waktu UTC, sama konsepnya
    //    dengan java.time.Instant.
    val instan: Instant = Instant.parse("2026-01-15T08:30:00Z")
    println("=== Instant ===")
    println("instant         : $instan")

    // 2. LocalDate kotlinx-datetime: tanggal tanpa zona, serupa
    //    dengan java.time.LocalDate.
    val tanggal: LocalDate = LocalDate.of(2026, 1, 15)
    println()
    println("=== LocalDate ===")
    println("tanggal         : $tanggal")
    println("tahun/bulan/hr : ${tanggal.year}/${tanggal.monthValue}/${tanggal.dayOfMonth}")

    // 3. LocalDateTime kotlinx-datetime: tanggal + jam tanpa zona.
    val jadwal: LocalDateTime = LocalDateTime.of(2026, 1, 15, 8, 30)
    println()
    println("=== LocalDateTime ===")
    println("jadwal          : $jadwal")

    // 4. Konversi antara LocalDate <-> Instant pada zona tertentu.
    //    Pada kotlinx-datetime: LocalDate.atStartOfDayIn(tz)
    //    Di sini dipakai padanan java.time.
    val zonaJakarta: ZoneId = ZoneId.of("Asia/Jakarta")
    val instanAwalHari: Instant = tanggal.atStartOfDay(zonaJakarta).toInstant()
    println()
    println("=== Konversi LocalDate -> Instant ===")
    println("instanAwalHari  : $instanAwalHari")

    // 5. Konversi Instant -> LocalDate pada zona tertentu.
    //    Pada kotlinx-datetime: instant.toLocalDateTime(tz).date
    val ldtJakarta = instan.atZone(zonaJakarta)
    val tanggalJakarta: LocalDate = ldtJakarta.toLocalDate()
    println()
    println("=== Konversi Instant -> LocalDate ===")
    println("tanggalJakarta  : $tanggalJakarta")

    // 6. Format ISO-8601 (sama dengan java.time untuk sebagian besar tipe)
    val fmtIso: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE
    println()
    println("=== Format ISO ===")
    println("ISO LocalDate   : ${fmtIso.format(tanggal)}")
    println("toString instant: ${instan.toString()}")
}

Output yang Dihasilkan:

text
=== Instant ===
instant         : 2026-01-15T08:30:00Z

=== LocalDate ===
tanggal         : 2026-01-15
tahun/bulan/hr : 2026/1/15

=== LocalDateTime ===
jadwal          : 2026-01-15T08:30

=== Konversi LocalDate -> Instant ===
instanAwalHari  : 2026-01-14T17:00:00Z

=== Konversi Instant -> LocalDate ===
tanggalJakarta  : 2026-01-15

=== Format ISO ===
ISO LocalDate   : 2026-01-15
toString instant: 2026-01-15T08:30:00Z

Mari kita telaah baris-baris kode di atas:

  1. Baris 13 (Instant.parse("2026-01-15T08:30:00Z")): padanan langsung kotlinx.datetime.Instant.parse(...). Constructor dan parser ISO-8601 identik di kedua library, sehingga kode pertukaran data tetap kompatibel.
  2. Baris 19 (LocalDate.of(2026, 1, 15)): padanan kotlinx.datetime.LocalDate(2026, 1, 15) — di kotlinx-datetime, LocalDate dipakai sebagai constructor function, bukan method statis of.
  3. Baris 31 (tanggal.atStartOfDay(zonaJakarta).toInstant()): padanan kotlinx.datetime.LocalDate.atStartOfDayIn(TimeZone). Pada JVM, ZoneId dan TimeZone kotlinx-datetime saling terhubung; konversi tidak mengubah offset.
  4. Baris 38 (instan.atZone(zonaJakarta).toLocalDate()): padanan kotlinx.datetime.Instant.toLocalDateTime(TimeZone).date. Pola ini menampilkan tanggal lokal dari instan UTC.
  5. Baris 45 (fmtIso.format(tanggal)): padanan kotlinx.datetime.LocalDate.toString(). kotlinx-datetime sengaja tidak menyediakan DateTimeFormatter dengan pola custom di semua target; formatting kustom didelegasikan ke java.time saat berjalan di JVM.

Kapan Memilih kotlinx-datetime vs java.time #

SkenarioPilihan
Aplikasi server JVM murnijava.time (lebih lengkap)
Aplikasi Android dengan codebase KMPkotlinx-datetime (lintas target)
Logika domain lintas-platform (model data)kotlinx-datetime
Tampilan zona kompleks (DST, zone rules)java.time di sisi JVM
Format custom (dd MMM yyyy)java.time.DateTimeFormatter

Strategi yang umum dipakai di proyek KMP: simpan tipe data sebagai kotlinx-datetime.Instant dan kotlinx-datetime.LocalDate di modul commonMain. Di modul jvmMain, konversi ke java.time saat diperlukan (mis. untuk formatter kompleks atau interaksi dengan library Java).

Referensi Lanjutan #

  • kotlinx-datetime — halaman GitHub kotlinx-datetime untuk abstraksi tanggal-jam lintas platform
  • Kotlin Multiplatform — dokumentasi Kotlin Multiplatform untuk pustaka lintas platform seperti kotlinx-datetime

Selanjutnya #

Setelah memahami lanskap library tanggal, kita akan masuk ke pemakaian konkret tipe LocalDate dan LocalTime untuk merepresentasikan tanggal dan jam tanpa zona. Bab LocalDate dan LocalTime akan membahas konstruksi, parsing, dan akses properti harian.