Why does Kotlin Multiplatform need kotlinx-datetime, and what is the difference between Instant, LocalDateTime, and using a TimeZone?
answer
- java.time is JVM-only => need kotlinx-datetime
- Instant = absolute UTC moment (Clock.System.now())
- LocalDateTime = wall clock, NO zone
- TimeZone bridges: toLocalDateTime / toInstant
- store Instant, display LocalDateTime
basics
~10 sjava.time only exists on the JVM, so KMP provides kotlinx-datetime. Instant is an exact moment in time; LocalDateTime is wall-clock date and time with no zone; a TimeZone converts between them.
solid answer
~40 s`java.time` is JDK-only, absent on Native/JS/Wasm, so kotlinx-datetime supplies cross-platform types. **`Instant`** is an absolute point on the timeline (UTC, from `Clock.System.now()`), independent of any zone. **`LocalDateTime`** is a 'wall clock' value (year-month-day + time) with **no** offset — ambiguous about the actual instant until you attach a zone. **`LocalDate`** is just a calendar date. A **`TimeZone`** (e.g. `TimeZone.of("Europe/Berlin")` or `TimeZone.currentSystemDefault()`) bridges them: `instant.toLocalDateTime(zone)` and `localDateTime.toInstant(zone)`. Arithmetic uses `DateTimePeriod`/`DateTimeUnit` with `plus`/`minus` and `Instant.until`. The golden rule: **store and compare `Instant`; render `LocalDateTime` only for display**. On the JVM, kotlinx-datetime mostly delegates to `java.time`; on Native it carries its own IANA tz database, so behaviour is consistent. As of recent versions, `kotlin.time.Instant`/`Clock` moved into the stdlib and kotlinx-datetime aligns with them.
code
kotlin · 8 linesimport kotlinx.datetime.*
fun nextMidnight(zone: TimeZone): Instant {
val now = Clock.System.now()
val today = now.toLocalDateTime(zone).date
val midnight = LocalDateTime(today.plus(1, DateTimeUnit.DAY), LocalTime(0, 0))
return midnight.toInstant(zone) // unambiguous absolute moment
}go deeper
Knows java.time is JVM-only and kotlinx-datetime replaces it; can name Instant and LocalDate.
Distinguishes Instant from LocalDateTime and uses TimeZone to convert between them.
Articulates the store-Instant/display-LocalDateTime rule, DST-aware arithmetic, and platform tz-database differences.
Designs time handling across services/clients, accounts for the kotlin.time.Instant stdlib migration, and reasons about DST/leap edge cases and serialization of time types.
## Why the library exists The familiar `java.time.*` API (`Instant`, `LocalDateTime`, `ZoneId`) lives in the **JDK** and therefore only exists on the **JVM/Android** targets. Kotlin/Native (iOS, etc.), Kotlin/JS, and Kotlin/Wasm have no JDK, so `commonMain` cannot use it. **kotlinx-datetime** provides equivalent types compiled for every target. ## The core types - **`Instant`** — an exact, absolute moment on the global timeline, conceptually UTC. You get 'now' from `Clock.System.now()`. Two `Instant`s can be compared and subtracted unambiguously. This is the type you persist and compare. - **`LocalDateTime`** — a **wall-clock** value: a date plus a time-of-day, with **no** time zone or offset. `2026-06-22T09:00` is ambiguous — it is a different instant in Berlin than in Tokyo — so it is a *display/input* type, not a timeline point. - **`LocalDate`** / **`LocalTime`** — just the calendar date, or just the clock time. - **`TimeZone`** — a named region rule (`TimeZone.of("Europe/Berlin")`, `TimeZone.UTC`, `TimeZone.currentSystemDefault()`) that encodes offsets and DST. It is the bridge between `Instant` and `LocalDateTime`. ## Converting between them ```kotlin import kotlinx.datetime.* val now: Instant = Clock.System.now() // absolute val zone = TimeZone.of("Europe/Berlin") val wall: LocalDateTime = now.toLocalDateTime(zone) // for display val back: Instant = wall.toInstant(zone) // back to absolute ``` ## Arithmetic Use **`DateTimeUnit`** and **`DateTimePeriod`** with `plus`/`minus`, and `Instant.until` for differences: ```kotlin val tomorrow = now.plus(1, DateTimeUnit.DAY, zone) // calendar-aware val diffDays = now.until(tomorrow, DateTimeUnit.DAY, zone) ``` Date arithmetic needs a zone because adding 'one day' across a DST boundary is not always 24 hours. ## The golden rules - **Persist, sort, and compare with `Instant`** — it is unambiguous. - **Use `LocalDateTime` only at the edges** (showing to a user, parsing user input), always paired with an explicit `TimeZone`. - **Never assume a `LocalDateTime` is UTC**; it has no zone at all. ## Platform behaviour On JVM, kotlinx-datetime largely delegates to `java.time`; on Native it bundles its own IANA time-zone database, giving consistent results across targets. Note an evolution: `kotlin.time.Instant` and `Clock` were promoted into the **standard library**, and kotlinx-datetime now aligns with the stdlib types — so newer code may see `Instant` coming from `kotlin.time`. ## Why it matters The Instant-vs-LocalDateTime distinction is the most common time bug source. Knowing kotlinx-datetime's model lets shared code handle time correctly on iOS and the browser exactly as on the JVM.
- If you store a LocalDateTime in a database and read it back, what can go wrong?It carries no zone, so the absolute moment is ambiguous and DST shifts can change it. Persist an Instant (or Instant + explicit zone) instead, and convert to LocalDateTime only for display.
- Why does adding 'one day' to an Instant require a TimeZone?Calendar day length varies with DST transitions; 'one day later' at the same wall-clock time may be 23 or 25 hours, so the operation is defined relative to a zone.
Instant is a photo's exact timestamp; LocalDateTime is what the wall clock in the room read — only knowing which room (TimeZone) reconciles the two.
saying these in an interview costs you the question
- Treating LocalDateTime as if it were UTC
- Persisting LocalDateTime and comparing it across zones
- Trying to use java.time.Instant directly in commonMain
- Adding days to an Instant without a TimeZone and expecting DST correctness
- Assuming time-zone data is identical and reflection-based on all platforms