Kotlin: Create a random date
Video: Kotlin: Create a random date by Taught by Celeste AI - AI Coding Coach
Kotlin: Generate a Random Date Between Two Bounds
Convert both endpoints to epoch days, draw a random integer in that range, convert back to
LocalDate. Six lines, fully reproducible if you seed the RNG.
A practical task: produce a random LocalDate between two bounds — say, "any day in 2024." Useful for seeding test databases, generating plausible dummy data, or building simulations.
The Copilot prompt
import java.time.LocalDate
// Generate a random LocalDate between startInclusive and endExclusive
fun randomDate(startInclusive: LocalDate, endExclusive: LocalDate): LocalDate {
Copilot completes:
import java.time.LocalDate
import java.util.concurrent.ThreadLocalRandom
fun randomDate(startInclusive: LocalDate, endExclusive: LocalDate): LocalDate {
val startEpochDay = startInclusive.toEpochDay()
val endEpochDay = endExclusive.toEpochDay()
val randomDay = ThreadLocalRandom.current().nextLong(startEpochDay, endEpochDay)
return LocalDate.ofEpochDay(randomDay)
}
Walkthrough
The trick: convert both LocalDate bounds to epoch days (number of days since 1970-01-01), pick a random integer in that range, convert back.
1. toEpochDay() — LocalDate exposes a fast conversion to "days since the Unix epoch." January 1, 1970 → 0; January 2, 1970 → 1; etc. June 15, 2024 → about 19890.
2. nextLong(start, end) — ThreadLocalRandom.current().nextLong(a, b) returns a random Long in [a, b) — inclusive lower, exclusive upper. That matches our function signature.
3. LocalDate.ofEpochDay(day) — the inverse of toEpochDay. Given a number of days since epoch, returns the corresponding LocalDate.
The whole thing is one round-trip through "days as numbers." Date math becomes integer math.
Why ThreadLocalRandom?
ThreadLocalRandom.current() is the preferred RNG in modern Java/Kotlin:
- Thread-local. No contention between threads (unlike the global
Randominstance). - Fast. Designed for high-throughput.
- Available since Java 7.
For Kotlin-style code, you could also use kotlin.random.Random:
import kotlin.random.Random
fun randomDate(startInclusive: LocalDate, endExclusive: LocalDate): LocalDate {
val day = Random.nextLong(startInclusive.toEpochDay(), endExclusive.toEpochDay())
return LocalDate.ofEpochDay(day)
}
Random.nextLong(a, b) has the same semantics. Choose based on context — ThreadLocalRandom if you're already in JVM/Java idioms; kotlin.random.Random for pure-Kotlin code or multiplatform.
Using the function
fun main() {
val start = LocalDate.of(2024, 1, 1)
val end = LocalDate.of(2025, 1, 1) // exclusive
repeat(5) {
println(randomDate(start, end))
}
}
Output (varies):
2024-07-23
2024-02-14
2024-11-08
2024-05-30
2024-09-17
Five random dates in 2024.
Reproducibility
For tests, you want the same "random" date every run. Seed an explicit Random:
import kotlin.random.Random
fun randomDate(startInclusive: LocalDate, endExclusive: LocalDate, rng: Random = Random): LocalDate {
val day = rng.nextLong(startInclusive.toEpochDay(), endExclusive.toEpochDay())
return LocalDate.ofEpochDay(day)
}
// In tests:
val rng = Random(seed = 42)
println(randomDate(start, end, rng)) // always the same
println(randomDate(start, end, rng)) // always the same second value
Default to the system RNG, but allow injection. Standard test-friendly Kotlin pattern.
Edge cases
Same start and end. endExclusive == startInclusive makes nextLong(x, x) throw IllegalArgumentException. Guard:
require(endExclusive.isAfter(startInclusive)) {
"endExclusive must be after startInclusive"
}
Reversed bounds. If the user passes start = 2024-12-31, end = 2024-01-01, nextLong errors. The require above catches it.
Large ranges. Even from year 1 to year 9999, the epoch-day range fits easily in a Long (≈3.65 million days). No overflow concerns.
Random ZonedDateTime
Same trick for time-zoned dates with random time-of-day:
import java.time.*
fun randomDateTime(start: LocalDateTime, end: LocalDateTime, zone: ZoneId): ZonedDateTime {
val startSec = start.atZone(zone).toEpochSecond()
val endSec = end.atZone(zone).toEpochSecond()
val randomSec = ThreadLocalRandom.current().nextLong(startSec, endSec)
return Instant.ofEpochSecond(randomSec).atZone(zone)
}
Same idea: round-trip through epoch seconds.
Random duration
For a random Duration between two bounds:
import java.time.Duration
fun randomDuration(min: Duration, max: Duration): Duration {
val nanos = ThreadLocalRandom.current().nextLong(min.toNanos(), max.toNanos())
return Duration.ofNanos(nanos)
}
The pattern: convert to a numeric type that captures the range, randomize, convert back.
Common mistakes
Inclusive vs exclusive confusion. The convention [start, end) (inclusive lower, exclusive upper) matches Kotlin/Java idioms. If you want both inclusive, use nextLong(start, end + 1).
Forgetting toEpochDay() is Long, not Int. Years past 5,879,000 AD overflow Int. Stick with Long.
Using Math.random() and casting. (Math.random() * (end - start)).toLong() + start works but is awkward. nextLong(a, b) is cleaner.
No reproducibility for tests. Hardcoded Random is unseeded → fresh seed every run → flaky tests. Inject the RNG.
Time-of-day assumed. A LocalDate has no time component. Tests that compare random times need LocalDateTime or Instant.
Common Copilot habits
When asked for "random date":
- Default: epoch-day technique with
ThreadLocalRandom. - "Use
kotlin.random.Random": switches to the Kotlin idiom. - "Make it deterministic for tests": Copilot adds an injected RNG parameter.
- "Generate N random dates in 2024": Copilot wraps the helper in a
List(n) { randomDate(...) }.
What's next
Episode 5: Capitalize the first letter of a string. A string-manipulation puzzle showing the right (and wrong) ways to do title-case conversion.
Recap
LocalDate.toEpochDay() ↔ LocalDate.ofEpochDay(day) makes date math integer math. ThreadLocalRandom.current().nextLong(start, end) for [start, end) random Long. Same trick for LocalDateTime/ZonedDateTime (epoch seconds) and Duration (nanos). For tests, inject a seeded Random. Guard against same-or-reversed bounds with require.
Next episode: capitalizing the first letter.