skip to content

Explain Duration.parse, parseOrNull, and toIsoString. What string formats are accepted and what are the failure modes?

level: seniorimportance: should knowfreq 28%

answer

  1. toIsoString -> 'PT1H30M'; zero is 'PT0S'
  2. parse is lenient: ISO + human '1h 30m'; throws on bad
  3. parseOrNull returns null instead of throwing
  4. parseIsoString(OrNull) accepts ONLY strict ISO
  5. No calendar months/years; M in time part = minutes

basics

~10 s

toIsoString turns a duration into an ISO-8601 string like PT1H23M45S. Duration.parse reads such strings back, plus Kotlin's own format like '1h 23m'. parse throws on bad input; parseOrNull returns null instead.

solid answer

~40 s

toIsoString() emits ISO-8601 duration syntax: a leading PT (or P...T) with components, e.g. 90.minutes.toIsoString() == "PT1H30M"; zero is "PT0S" and infinite produces a very large value, not a special token. Duration.parse(String) is lenient: it accepts both the ISO-8601 form (PT1H30M, P1DT2H) AND Kotlin's default human format ("1h 30m", "1d 5s"). On malformed input it throws IllegalArgumentException. Duration.parseOrNull(String) returns Duration? (null on failure) for non-throwing parsing. There is also parseIsoString / parseIsoStringOrNull which accept ONLY the strict ISO form and reject the human format. Note ISO-8601 weeks/months/years (the date part like P1Y or P1M month) are not meaningful for a fixed duration; kotlin.time works with days/hours/minutes/seconds. Round-trip toIsoString() -> parse is stable.

code

kotlin · 12 lines
kotlin
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes

fun main() {
    val d = 90.minutes
    val iso = d.toIsoString()                 // "PT1H30M"
    println(iso)
    println(Duration.parse(iso))              // 1h 30m
    println(Duration.parse("1h 30m"))         // 1h 30m (human form)
    println(Duration.parseOrNull("bad"))      // null
    println(Duration.parse(iso) == d)         // true (round-trip)
}

go deeper

for a junior

Knows toIsoString produces a string and parse reads one back.

for a middle

Knows parse throws while parseOrNull returns null, and recognizes the PT...H...M...S shape.

for a senior

Distinguishes lenient parse (ISO + human) from strict parseIsoString, handles failure modes, and knows round-trip stability.

for a principal

Designs an interchange/serialization policy (which form for JSON vs logs, which parser at which boundary) and knows the calendar-vs-fixed-span limitation.

## Formatting: toIsoString() `toIsoString()` renders a `Duration` as an **ISO-8601** duration string: ```kotlin import kotlin.time.Duration.Companion.minutes 90.minutes.toIsoString() // "PT1H30M" 0.minutes.toIsoString() // "PT0S" (25.hours).toIsoString() // "PT25H" (hours not rolled into days here) ``` Form: optional `P` date part (days) then `T` time part (`H`/`M`/`S`), e.g. `P1DT2H3M4S`. Fractional seconds appear as a decimal: `1.5.seconds.toIsoString()` -> `"PT1.5S"`. ## Parsing — two strictness levels ### Lenient: `Duration.parse(String): Duration` Accepts **both** notations: - ISO-8601: `"PT1H30M"`, `"P1DT2H"`, `"PT1.5S"`. - Kotlin **default/human** format (what `toString()` prints): `"1h 30m"`, `"1d 5s"`, `"1.5s"`. Throws `IllegalArgumentException` on malformed input. ### Non-throwing: `Duration.parseOrNull(String): Duration?` Same lenient grammar but returns `null` instead of throwing — ideal for user/config input. ### Strict ISO only: `parseIsoString` / `parseIsoStringOrNull` Accept **only** the ISO-8601 form and reject the human `"1h 30m"` style. Use these when you must guarantee interchange-standard input. ```kotlin import kotlin.time.Duration Duration.parse("PT1H30M") // 1h 30m Duration.parse("1h 30m") // 1h 30m (human form OK here) Duration.parseOrNull("oops") // null Duration.parseIsoString("PT1H30M") // 1h 30m // Duration.parseIsoString("1h 30m") -> throws (human form rejected) ``` ## Failure modes & gotchas - **parse vs parseOrNull**: choose `parseOrNull` at trust boundaries to avoid try/catch. - **IllegalArgumentException** message names the offending input; don't swallow it silently. - **No calendar months/years**: ISO `P1M`-as-month or `P1Y` are not representable as a fixed `Duration` — kotlin.time is days-and-below. (`M` in the *time* part means minutes.) - **Infinite**: `Duration.INFINITE.toIsoString()` yields a huge finite-looking value, not an `INF` token. - **Round-trip stability**: `Duration.parse(d.toIsoString()) == d` holds for finite durations. ## When to use which Emit `toIsoString()` for machine interchange/JSON; emit default `toString()` for logs/humans; parse back with `parseOrNull` (lenient) or `parseIsoStringOrNull` (strict) depending on what producers you must accept.

  • Which API would you use to reject the human '1h 30m' form and accept only ISO-8601?
    parseIsoString (throwing) or parseIsoStringOrNull (null-returning). Plain Duration.parse is lenient and accepts both forms.
  • Why can't kotlin.time parse 'P1Y' (one year) into a meaningful Duration?
    A year/month has no fixed length on a calendar, so it isn't a fixed time span. kotlin.time models days-and-below; calendar arithmetic belongs to kotlinx-datetime/java.time.

saying these in an interview costs you the question

  • Claiming Duration.parse only accepts ISO and rejects '1h 30m'
  • Thinking parseOrNull throws (it returns null)
  • Believing kotlin.time can represent calendar months/years as a Duration
  • Expecting an 'INF' token from toIsoString for INFINITE

context