Master Time with Kotlin's Stable Timing API: Beyond System.nanoTime()
Learn how to use type-safe Durations, measure execution time, and simplify unit testing with Kotlin 1.6+.
Managing time in software is a ubiquitous challenge. From measuring performance to scheduling events, accurate and robust timing is crucial. For years, many Kotlin developers relied on manual Long calculations with System.nanoTime(). While functional, this approach was prone to unit confusion and lacked type safety.
Enter the stable kotlin.time API (fully stable since Kotlin 1.6). This powerful toolset provides a type-safe, idiomatic, and highly readable way to handle durations. It eliminates the guesswork and boilerplate, allowing you to write cleaner, more maintainable code.
Quick Tip: To follow along with the examples below, you’ll want to include this import at the top of your file:
import kotlin.time.*
Why Ditch Manual Long Calculations?
Imagine you’re tracking the execution time of a critical network request using the “old” way:
fun fetchDataManually(): String {
val startTimeNanos = System.nanoTime()
// Simulate a network call
Thread.sleep(1500)
val endTimeNanos = System.nanoTime()
val durationNanos = endTimeNanos - startTimeNanos
val durationMillis = durationNanos / 1_000_000
println("Data fetched in $durationMillis ms")
return "Some data"
}The Pitfalls:
- Unit Confusion: Is the result in nanos, micros, or millis?
- Conversion Errors: It’s easy to miss a zero when dividing by
1,000,000. - No Type Safety: The compiler won’t stop you from adding a “millisecond long” to a “nanosecond long.”
Measuring Execution Time with Elegance
The Timing API offers two primary functions for measuring code block execution:
1. Measuring time only
measureTime { ... } returns a Duration object. It is simple, clean, and expressive.
fun processTransaction() {
val timeTaken: Duration = measureTime {
// Note: Use delay() if in a coroutine context to avoid blocking threads
Thread.sleep(750)
}
println("Transaction processed in $timeTaken") // Automatically prints as "750ms"
}2. Measuring time and value
Use measureTimedValue { ... } when you need both the execution time and the returned result of the code block.
val (report, duration) = measureTimedValue {
Thread.sleep(2000)
"Sales Report Q3"
}
println("Generated $report in $duration")The Power of the Duration Class
The Duration class is a value class. It behaves like a high-level object in your code but is compiled down to a primitive Long by the JVM. This gives you type safety with zero performance overhead from object allocation.
Creating and manipulating time
You can create durations using intuitive extension properties and perform standard arithmetic:
val timeout = 5.seconds
val total = 1.minutes + 30.seconds
val tripled = total * 3
val half = 10.minutes / 2Precise conversions and components
inWholeMilliseconds: Returns aLong(truncates fractional parts).inMilliseconds: Returns aDouble(keeps precision).toComponents { ... }: Excellent for breaking time down into days, hours, and minutes for human-readable UI displays.
Serialization with ISO 8601
For databases, logs, or external APIs, the Timing API supports the standard ISO 8601 duration format:
val isoString = 2.hours.toIsoString() // Returns "PT2H"
val duration = Duration.parseIsoString("PT2H")Advanced Usage: Time Marks and Testing
Sometimes code isn’t neatly wrapped in a block (like start/stop events in a lifecycle). In these cases, use Time Marks:
val source = TimeSource.Monotonic
val mark = source.markNow() // Captures the current moment
// ... some time passes or an event occurs ...
val elapsed = mark.elapsedNow()
println("Time since mark: $elapsed")Testing with TestTimeSource
Testing time-dependent logic used to mean using Thread.sleep() in your unit tests, making them slow and flaky. With TestTimeSource, you can "virtually" advance time:
val testSource = TestTimeSource()
val scheduler = MyScheduler(testSource)
testSource += 5.seconds // Time "jumps" forward instantly in your logic
// Now assert that your scheduled task triggered!Frequently Asked Questions (FAQs)
Can I use this with Kotlin Coroutines?
Absolutely. Modern coroutine functions like delay() now accept Duration objects directly. Note that while measureTime itself is not a suspending function, it can measure suspending code when invoked from within a suspend context.
Is it accurate enough for high-precision profiling?
It uses the best available platform timers (like System.nanoTime() on JVM). While highly accurate, remember that final precision is always subject to OS scheduling and hardware limitations.
Which version of Kotlin do I need?
While it has evolved, the kotlin.time API has been stable since Kotlin 1.6, making it ready for production in almost any modern Kotlin environment.
Join the Conversation
- Have you ever had a “unit mismatch” bug in your production code?
- Do you prefer
toComponentsortoIsoStringfor your data persistence? - How much time would
TestTimeSourcesave in your current unit test suite?

Comments
Post a Comment