Datetime

Datetime #

Tanggal dan waktu adalah salah satu domain paling sulit dalam pemrograman — zona waktu yang membingungkan, daylight saving time yang mengubah offset secara tiba-tiba, perbedaan antara waktu lokal dan waktu universal, serta format yang beragam di setiap region. Java mengalami evolusi panjang dalam hal ini: java.util.Date dan Calendar yang terkenal buruk, lalu java.time (JSR-310) yang jauh lebih baik di Java 8. Kotlin hadir dengan kotlinx-datetime — library resmi JetBrains yang membawa API modern, multiplatform (JVM, JS, Native), dan idiomatik Kotlin. Artikel ini membahas seluruh ekosistem datetime di Kotlin: dari tipe-tipe dasar, operasi aritmatika, formatting, parsing, hingga penanganan zona waktu yang benar — termasuk jebakan umum yang membuat bug datetime sulit dilacak.

Setup kotlinx-datetime #

kotlinx-datetime adalah library terpisah yang perlu ditambahkan sebagai dependensi.

// build.gradle.kts
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
}

// Untuk Kotlin Multiplatform
kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
        }
    }
}

Semua tipe utama ada di package kotlinx.datetime:

import kotlinx.datetime.*

Hierarki Tipe Datetime #

Sebelum menulis kode, penting memahami perbedaan antar tipe — kesalahan memilih tipe adalah sumber bug datetime yang paling umum.

flowchart TD
    A["Tipe Datetime"] --> B["Tanpa Zona Waktu\n(Local)"]
    A --> C["Dengan Zona Waktu\n(Absolute)"]

    B --> D["LocalDate\nHanya tanggal\n2024-03-15"]
    B --> E["LocalTime\nHanya waktu\n14:30:00"]
    B --> F["LocalDateTime\nTanggal + waktu\n2024-03-15T14:30:00\nTanpa info timezone!"]

    C --> G["Instant\nTitik waktu absolut\nMillisecond sejak Unix epoch\nSama di seluruh dunia"]
    C --> H["ZonedDateTime\n(via java.time di JVM)\nInstant + TimeZone"]
TipeRepresentasiKapan Digunakan
LocalDateTanggal saja: 2024-03-15Ulang tahun, deadline, jadwal
LocalTimeWaktu saja: 14:30:00Jam buka toko, alarm harian
LocalDateTimeTanggal + waktu tanpa TZEvent lokal, input form
InstantTitik absolut waktuLog, timestamp database, API
TimeZoneRepresentasi zona waktuKonversi antar zona

LocalDate — Tanggal Saja #

import kotlinx.datetime.*

// Membuat LocalDate
val hari = LocalDate(2024, 3, 15)         // tahun, bulan, hari
val hariBulan = LocalDate(2024, Month.MARCH, 15)  // dengan enum Month

// Tanggal hari ini — butuh zona waktu!
val hariIni: LocalDate = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date

// Parsing dari String
val parsed = LocalDate.parse("2024-03-15")    // ISO 8601
val parsed2 = LocalDate.parse("15/03/2024", LocalDate.Format { dayOfMonth(); char('/'); monthNumber(); char('/'); year() })

// Property
println(hari.year)         // 2024
println(hari.month)        // MARCH
println(hari.monthNumber)  // 3
println(hari.dayOfMonth)   // 15
println(hari.dayOfWeek)    // FRIDAY
println(hari.dayOfYear)    // 75

// Operasi — menambah/mengurangi periode
val besok = hari.plus(1, DateTimeUnit.DAY)
val mingguDepan = hari.plus(1, DateTimeUnit.WEEK)
val bulanDepan = hari.plus(1, DateTimeUnit.MONTH)
val tahunDepan = hari.plus(1, DateTimeUnit.YEAR)

val kemarin = hari.minus(1, DateTimeUnit.DAY)

// Selisih antara dua tanggal
val mulai = LocalDate(2024, 1, 1)
val akhir = LocalDate(2024, 12, 31)
val selisihHari = mulai.until(akhir, DateTimeUnit.DAY)    // 365
val selisihBulan = mulai.until(akhir, DateTimeUnit.MONTH) // 11

// Perbandingan
println(hari > LocalDate(2024, 1, 1))    // true
println(hari < LocalDate(2025, 1, 1))    // true
println(hari == LocalDate(2024, 3, 15))  // true

// Range tanggal
val rentang = LocalDate(2024, 1, 1)..LocalDate(2024, 12, 31)
println(hari in rentang)   // true

Kasus Penggunaan LocalDate #

// Hitung umur dari tanggal lahir
fun hitungUmur(tanggalLahir: LocalDate): Int {
    val hariIni = Clock.System.todayIn(TimeZone.currentSystemDefault())
    var umur = hariIni.year - tanggalLahir.year
    if (hariIni.monthNumber < tanggalLahir.monthNumber ||
        (hariIni.monthNumber == tanggalLahir.monthNumber &&
         hariIni.dayOfMonth < tanggalLahir.dayOfMonth)) {
        umur--
    }
    return umur
}

val lahir = LocalDate(1995, 8, 17)
println("Umur: ${hitungUmur(lahir)} tahun")

// Cek apakah tanggal akhir pekan
fun LocalDate.isWeekend(): Boolean =
    dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY

fun LocalDate.isWeekday(): Boolean = !isWeekend()

// Hitung hari kerja antara dua tanggal
fun hitungHariKerja(mulai: LocalDate, akhir: LocalDate): Int {
    var hariKerja = 0
    var tanggal = mulai
    while (tanggal <= akhir) {
        if (tanggal.isWeekday()) hariKerja++
        tanggal = tanggal.plus(1, DateTimeUnit.DAY)
    }
    return hariKerja
}

// Hari pertama dan terakhir bulan
fun LocalDate.hariPertamaBulan() = LocalDate(year, monthNumber, 1)
fun LocalDate.hariTerakhirBulan() = plus(1, DateTimeUnit.MONTH)
    .hariPertamaBulan()
    .minus(1, DateTimeUnit.DAY)

val maret = LocalDate(2024, 3, 15)
println(maret.hariPertamaBulan())   // 2024-03-01
println(maret.hariTerakhirBulan())  // 2024-03-31

LocalTime — Waktu Saja #

// Membuat LocalTime
val waktu = LocalTime(14, 30, 0)           // jam, menit, detik
val waktuMs = LocalTime(14, 30, 0, 500_000_000)  // dengan nanosecond

// Parsing
val parsed = LocalTime.parse("14:30:00")
val parsed2 = LocalTime.parse("14:30:00.500")  // dengan fraksional detik

// Property
println(waktu.hour)         // 14
println(waktu.minute)       // 30
println(waktu.second)       // 0
println(waktu.nanosecond)   // 0

// Perbandingan
println(LocalTime(9, 0) < LocalTime(17, 0))    // true (jam kerja)
println(LocalTime(12, 0) == LocalTime(12, 0))  // true

// Konversi ke nanosecond dari tengah malam
val nsFromMidnight = waktu.toNanosecondOfDay()  // 52_200_000_000_000 ns

// Format waktu
fun LocalTime.formatWIB(): String = "%02d:%02d".format(hour, minute)
println(waktu.formatWIB())   // "14:30"

// Jam buka/tutup toko
data class JamOperasional(val buka: LocalTime, val tutup: LocalTime) {
    fun sedangBuka(waktuSekarang: LocalTime): Boolean =
        waktuSekarang in buka..tutup
}

val jamToko = JamOperasional(LocalTime(9, 0), LocalTime(21, 0))
println(jamToko.sedangBuka(LocalTime(14, 30)))   // true
println(jamToko.sedangBuka(LocalTime(22, 0)))    // false

LocalDateTime — Tanggal dan Waktu Lokal #

LocalDateTime menggabungkan tanggal dan waktu tapi tidak menyimpan informasi zona waktu. Ini bukan titik waktu absolut — dua orang di zona berbeda yang punya LocalDateTime yang sama tidak sedang berada di momen yang sama.

// Membuat LocalDateTime
val dt = LocalDateTime(2024, 3, 15, 14, 30, 0)
val dt2 = LocalDateTime(
    date = LocalDate(2024, 3, 15),
    time = LocalTime(14, 30, 0)
)

// Parsing
val parsed = LocalDateTime.parse("2024-03-15T14:30:00")
val parsed2 = LocalDateTime.parse("2024-03-15T14:30:00.500")

// Property
println(dt.date)    // 2024-03-15
println(dt.time)    // 14:30
println(dt.year)    // 2024
println(dt.month)   // MARCH
println(dt.hour)    // 14

// Operasi
val dtBesok = dt.plus(1, DateTimeUnit.DAY)
val dtPlusJam = dt.plus(2, DateTimeUnit.HOUR)

// Konversi ke Instant — butuh timezone!
val tz = TimeZone.of("Asia/Jakarta")
val instant: Instant = dt.toInstant(tz)

// Dari Instant ke LocalDateTime
val dtKembali: LocalDateTime = instant.toLocalDateTime(tz)

// ANTI-PATTERN: menyimpan LocalDateTime di database sebagai representasi waktu absolut
// LocalDateTime "2024-03-15T14:30:00" di Jakarta ≠ di London!
// BENAR: simpan Instant ke database

// LocalDateTime berguna untuk:
// - Input form tanggal-waktu dari pengguna
// - Event yang memang lokal (konser di kota tertentu)
// - Template jadwal (setiap Senin pukul 09:00, tanpa peduli timezone)

Instant — Titik Waktu Absolut #

Instant merepresentasikan titik waktu yang spesifik di garis waktu universal — sama di seluruh dunia. Ini adalah tipe yang harus digunakan untuk menyimpan timestamp.

// Instant sekarang
val sekarang: Instant = Clock.System.now()
println(sekarang)   // 2024-03-15T07:30:00Z (UTC)

// Dari Unix epoch milliseconds (dari database atau API)
val dariEpochMs = Instant.fromEpochMilliseconds(1_710_487_800_000L)
val dariEpochSec = Instant.fromEpochSeconds(1_710_487_800L)

// Ke Unix epoch
val epochMs: Long = sekarang.toEpochMilliseconds()
val epochSec: Long = sekarang.epochSeconds

// Parsing dari ISO 8601
val parsed = Instant.parse("2024-03-15T07:30:00Z")
val parsedOffset = Instant.parse("2024-03-15T14:30:00+07:00")

// Operasi — tambah/kurang Duration
val satuJamLalu: Instant = sekarang.minus(1.hours)
val besok: Instant = sekarang.plus(24.hours)
val semingguLagi: Instant = sekarang.plus(7.days)

// Selisih antara dua Instant menghasilkan Duration
val mulai = Instant.parse("2024-03-15T08:00:00Z")
val akhir = Instant.parse("2024-03-15T17:30:00Z")
val durasi: Duration = akhir - mulai
println(durasi)   // 9h 30m

// Perbandingan
println(mulai < akhir)    // true
println(sekarang > mulai) // true (asumsi sekarang setelah mulai)

// Konversi ke zona waktu lokal
val tzJakarta = TimeZone.of("Asia/Jakarta")
val waktuJakarta: LocalDateTime = sekarang.toLocalDateTime(tzJakarta)

val tzLondon = TimeZone.of("Europe/London")
val waktuLondon: LocalDateTime = sekarang.toLocalDateTime(tzLondon)

// Instant yang sama, waktu lokal berbeda
println(waktuJakarta)   // 14:30 (WIB = UTC+7)
println(waktuLondon)    // 07:30 (saat tidak daylight saving)

Clock — Abstraksi untuk Testability #

// ANTI-PATTERN: Clock.System.now() langsung di business logic
class PesananService {
    fun buatPesanan(items: List<Item>): Pesanan {
        return Pesanan(
            items = items,
            dibuat = Clock.System.now()   // tidak bisa di-test!
        )
    }
}

// BENAR: inject Clock sebagai dependency
class PesananService(private val clock: Clock = Clock.System) {
    fun buatPesanan(items: List<Item>): Pesanan {
        return Pesanan(
            items = items,
            dibuat = clock.now()
        )
    }
}

// Test dengan waktu yang dikontrol
@Test
fun `pesanan harus punya timestamp yang benar`() {
    val waktuTetap = Instant.parse("2024-03-15T10:00:00Z")
    val clockPalsu = object : Clock {
        override fun now() = waktuTetap
    }
    
    val service = PesananService(clock = clockPalsu)
    val pesanan = service.buatPesanan(listOf(itemDummy))
    
    assertEquals(waktuTetap, pesanan.dibuat)
}

TimeZone — Zona Waktu #

// Zona waktu berdasarkan ID IANA
val tzJakarta = TimeZone.of("Asia/Jakarta")     // WIB: UTC+7
val tzMakassar = TimeZone.of("Asia/Makassar")  // WITA: UTC+8
val tzJayapura = TimeZone.of("Asia/Jayapura")  // WIT: UTC+9
val tzUTC = TimeZone.UTC
val tzLokal = TimeZone.currentSystemDefault()   // timezone sistem

// Daftar semua timezone yang tersedia
val semuaTZ = TimeZone.availableZoneIds
println("${semuaTZ.size} timezone tersedia")

// Konversi Instant ↔ LocalDateTime
val sekarang = Clock.System.now()
val localJakarta = sekarang.toLocalDateTime(tzJakarta)
val localMakassar = sekarang.toLocalDateTime(tzMakassar)
val localJayapura = sekarang.toLocalDateTime(tzJayapura)

println("WIB:  $localJakarta")
println("WITA: $localMakassar")
println("WIT:  $localJayapura")

// UTC offset saat ini
val offsetSekarang = tzJakarta.offsetAt(sekarang)
println("Offset Jakarta: $offsetSekarang")   // +07:00

// Konversi antara dua timezone
fun konversiTimezone(
    waktu: LocalDateTime,
    dari: TimeZone,
    ke: TimeZone
): LocalDateTime {
    return waktu.toInstant(dari).toLocalDateTime(ke)
}

val meetingJakarta = LocalDateTime(2024, 3, 15, 14, 0)
val meetingLondon = konversiTimezone(
    meetingJakarta,
    TimeZone.of("Asia/Jakarta"),
    TimeZone.of("Europe/London")
)
println("Meeting Jakarta: $meetingJakarta → London: $meetingLondon")
// Meeting Jakarta: 2024-03-15T14:00 → London: 2024-03-15T07:00 (UTC+0)

Formatting dan Parsing #

import kotlinx.datetime.format.*

// Format bawaan — toString() menghasilkan ISO 8601
val tgl = LocalDate(2024, 3, 15)
println(tgl)              // 2024-03-15
println(tgl.toString())   // 2024-03-15

val dt = LocalDateTime(2024, 3, 15, 14, 30, 0)
println(dt)   // 2024-03-15T14:30

val instant = Instant.parse("2024-03-15T07:30:00Z")
println(instant)   // 2024-03-15T07:30:00Z

// Format kustom dengan LocalDate.Format builder
val formatIndonesia = LocalDate.Format {
    dayOfMonth()
    char(' ')
    monthName(MonthNames.ENGLISH_FULL)   // atau buat custom MonthNames
    char(' ')
    year()
}

println(tgl.format(formatIndonesia))   // "15 March 2024"

// Format angka saja
val formatAngka = LocalDate.Format {
    dayOfMonth()
    char('/')
    monthNumber()
    char('/')
    year()
}
println(tgl.format(formatAngka))       // "15/03/2024"

// Nama bulan dalam Bahasa Indonesia
val namaBulanID = MonthNames(
    "Januari", "Februari", "Maret", "April", "Mei", "Juni",
    "Juli", "Agustus", "September", "Oktober", "November", "Desember"
)

val namaHariID = DayOfWeekNames(
    "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"
)

val formatLengkapID = LocalDate.Format {
    dayOfWeek(namaHariID)
    chars(", ")
    dayOfMonth()
    char(' ')
    monthName(namaBulanID)
    char(' ')
    year()
}

println(tgl.format(formatLengkapID))   // "Jumat, 15 Maret 2024"

// Format LocalDateTime
val formatDTID = LocalDateTime.Format {
    dayOfMonth()
    char('/')
    monthNumber()
    char('/')
    year()
    chars(" pukul ")
    hour()
    char(':')
    minute()
}

println(dt.format(formatDTID))   // "15/03/2024 pukul 14:30"

// Parsing dengan format kustom
val tglParsed = LocalDate.parse("15/03/2024", formatAngka)
println(tglParsed)   // 2024-03-15

Interoperabilitas dengan java.time (JVM) #

Jika proyek sudah menggunakan java.time, kotlinx-datetime menyediakan konversi yang mudah.

// Konversi kotlinx-datetime → java.time
val instant: Instant = Clock.System.now()
val javaInstant: java.time.Instant = instant.toJavaInstant()

val localDate: LocalDate = LocalDate(2024, 3, 15)
val javaLocalDate: java.time.LocalDate = localDate.toJavaLocalDate()

val localDT: LocalDateTime = LocalDateTime(2024, 3, 15, 14, 30)
val javaLocalDT: java.time.LocalDateTime = localDT.toJavaLocalDateTime()

// Konversi java.time → kotlinx-datetime
val backToKotlin: Instant = javaInstant.toKotlinInstant()
val backToDate: LocalDate = javaLocalDate.toKotlinLocalDate()

// Berguna saat bekerja dengan library Java yang memakai java.time
// misalnya JDBC, Hibernate, atau library lain
fun simpanKeDatabase(pesanan: Pesanan, koneksi: java.sql.Connection) {
    val stmt = koneksi.prepareStatement("INSERT INTO pesanan (dibuat) VALUES (?)")
    stmt.setObject(1, pesanan.dibuat.toJavaInstant()
        .atOffset(java.time.ZoneOffset.UTC))
    stmt.execute()
}

Pola Idiomatik dalam Kode Produksi #

Timestamp di Database #

// ANTI-PATTERN: simpan LocalDateTime di database
data class Transaksi(
    val id: Long,
    val jumlah: Double,
    val waktu: LocalDateTime   // timezone mana? Tidak jelas!
)

// BENAR: simpan Instant untuk timestamp absolut
data class Transaksi(
    val id: Long,
    val jumlah: Double,
    val dibuat: Instant,        // absolut, tidak ambigu
    val diupdate: Instant
)

// Repository layer — konversi dari/ke epoch millisecond
fun bacaTransaksi(row: ResultSet): Transaksi = Transaksi(
    id = row.getLong("id"),
    jumlah = row.getDouble("jumlah"),
    dibuat = Instant.fromEpochMilliseconds(row.getLong("dibuat_ms")),
    diupdate = Instant.fromEpochMilliseconds(row.getLong("diupdate_ms"))
)

fun simpanTransaksi(t: Transaksi, stmt: PreparedStatement) {
    stmt.setLong(3, t.dibuat.toEpochMilliseconds())
    stmt.setLong(4, t.diupdate.toEpochMilliseconds())
}

Period — Rentang Kalender yang Sadar Bulan #

// DatePeriod untuk representasi periode kalender (tahun, bulan, hari)
// Berbeda dari Duration yang linear — 1 bulan bisa 28, 29, 30, atau 31 hari

val periode = DatePeriod(years = 1, months = 6, days = 15)

val mulai = LocalDate(2024, 1, 1)
val akhir = mulai.plus(periode)
println(akhir)   // 2024-07-16

// DateTimePeriod — periode yang mencakup waktu juga
val periodeWaktu = DateTimePeriod(months = 3, hours = 2)

// Hitung period antara dua tanggal
val ulangTahun = LocalDate(1995, 8, 17)
val hariIni = LocalDate(2024, 3, 15)
val umur = ulangTahun.periodUntil(hariIni)
println("${umur.years} tahun ${umur.months} bulan ${umur.days} hari")

Validasi Tanggal #

// Parsing dengan error handling
fun parseTanggal(input: String): LocalDate? = try {
    LocalDate.parse(input)
} catch (e: IllegalArgumentException) {
    null
}

// Validasi format Indonesia DD/MM/YYYY
fun parseTanggalIndonesia(input: String): LocalDate? {
    val format = LocalDate.Format {
        dayOfMonth(); char('/'); monthNumber(); char('/'); year()
    }
    return try {
        LocalDate.parse(input, format)
    } catch (e: IllegalArgumentException) {
        null
    }
}

parseTanggalIndonesia("15/03/2024")   // 2024-03-15
parseTanggalIndonesia("31/02/2024")   // null (31 Februari tidak ada)
parseTanggalIndonesia("abc")           // null

// Validasi rentang tanggal
fun validasiRentang(mulai: LocalDate, akhir: LocalDate): Boolean {
    return mulai <= akhir
}

fun validasiTidakMasaLalu(tgl: LocalDate): Boolean {
    val hariIni = Clock.System.todayIn(TimeZone.currentSystemDefault())
    return tgl >= hariIni
}

Jebakan Umum yang Harus Dihindari #

// JEBAKAN 1: Menyimpan LocalDateTime sebagai timestamp
// LocalDateTime tidak punya informasi timezone — bisa salah saat DST berubah
// GUNAKAN Instant untuk semua timestamp yang disimpan

// JEBAKAN 2: Membuat LocalDate dari "sekarang" tanpa timezone
// ANTI-PATTERN:
// val hariIni = LocalDate.now()   // API ini tidak ada di kotlinx-datetime! (sengaja)
// BENAR:
val hariIni = Clock.System.todayIn(TimeZone.currentSystemDefault())

// JEBAKAN 3: Aritmatika bulan yang tidak konsisten
val akhirJanuari = LocalDate(2024, 1, 31)
val bulanDepan = akhirJanuari.plus(1, DateTimeUnit.MONTH)
println(bulanDepan)   // 2024-02-29 (tahun kabisat) atau 2024-02-28
// Kotlinx-datetime menangani ini dengan benar — hasil adalah hari terakhir bulan tujuan

// JEBAKAN 4: Mengabaikan DST saat konversi
// Europe/London punya DST — offset berubah antara GMT+0 dan BST+1
val london = TimeZone.of("Europe/London")
val winter = LocalDateTime(2024, 1, 15, 12, 0).toInstant(london)
val summer = LocalDateTime(2024, 7, 15, 12, 0).toInstant(london)
println(winter.epochSeconds - summer.epochSeconds)
// Beda 3600 detik (1 jam) meski LocalDateTime sama karena DST!

// JEBAKAN 5: Parsing tanpa format eksplisit untuk format non-ISO
// ANTI-PATTERN:
// LocalDate.parse("15-03-2024")   // IllegalArgumentException! Bukan ISO 8601
// BENAR:
val fmt = LocalDate.Format { dayOfMonth(); char('-'); monthNumber(); char('-'); year() }
LocalDate.parse("15-03-2024", fmt)   // OK

Ringkasan #

  • Pilih tipe yang tepat: LocalDate untuk tanggal saja, LocalTime untuk waktu saja, LocalDateTime untuk input form lokal, dan Instant untuk semua timestamp yang disimpan di database atau dikirim lewat API.
  • Instant adalah pilihan aman untuk timestamp — merepresentasikan titik waktu absolut yang sama di seluruh dunia, tidak ambigu tentang timezone.
  • Selalu butuh timezone untuk konversi antara Instant dan LocalDateTime. Gunakan ID IANA seperti "Asia/Jakarta", bukan offset statis "+07:00" — offset statis tidak memperhitungkan DST.
  • Inject Clock sebagai dependency agar kode yang bergantung pada waktu sekarang mudah diuji. Gunakan Clock.System di produksi dan implementasi kustom di test.
  • LocalDateTime bukan timestamp absolut — dua orang di zona berbeda dengan LocalDateTime yang sama tidak sedang di momen yang sama. Jangan simpan LocalDateTime ke database sebagai timestamp.
  • DatePeriod berbeda dari DurationDatePeriod(months = 1) bisa berarti 28, 29, 30, atau 31 hari tergantung bulan awal. Gunakan DatePeriod untuk aritmatika kalender, Duration untuk rentang waktu linear.
  • Parsing dan formatting menggunakan DSL builder yang type-safe: LocalDate.Format { dayOfMonth(); char('/'); monthNumber(); char('/'); year() }. Buat MonthNames kustom untuk nama bulan dalam Bahasa Indonesia.
  • Clock.System.todayIn(timezone) adalah cara yang benar mendapatkan “hari ini” — selalu sertakan timezone secara eksplisit.
  • Interoperabilitas dengan java.time tersedia via extension functions .toJavaInstant(), .toJavaLocalDate(), .toKotlinInstant() — berguna saat bekerja dengan library Java.
  • Hindari aritmatika tanggal manual (hitung sendiri jumlah hari per bulan, tahun kabisat) — biarkan kotlinx-datetime yang menanganinya dengan benar, termasuk kasus edge seperti 31 Januari + 1 bulan.

← Sebelumnya: Duration   Berikutnya: Reflection →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact