Skip to content
This repository has been archived by the owner on Aug 24, 2021. It is now read-only.

Commit

Permalink
feat: Generator for LocalDateTime (#223)
Browse files Browse the repository at this point in the history
Thanks to @mmiikkkkaa
  • Loading branch information
mmiikkkkaa authored Oct 15, 2020
1 parent a19305d commit 4422e1a
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 1 deletion.
3 changes: 3 additions & 0 deletions docs/generators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,6 @@ Java Time API

``Generator.localDates(min: LocalDate.MIN, max: LocalDate.MAX)``
Generates LocalDates, includes the samples: ``LocalDate.EPOCH`` (1970-01-01), ``min`` and ``max``

``Generator.localDateTimes(min: LocalDateTime.MIN, max: LocalDateTime.MAX)``
Generates LocalDateTimes, includes the samples: ``LocalDate.EPOCH.atStartOfDay`` (1970-01-01T00:00:00.000Z), ``min`` and ``max``
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ import com.github.jcornaz.kwik.generator.api.withSamples
import java.time.Duration
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.ZoneOffset
import java.time.temporal.ChronoField
import kotlin.random.Random

private const val MAX_NANOSECONDS = 999_999_999
private val MIN_DURATION = Duration.ofSeconds(Long.MIN_VALUE)
private val MAX_DURATION = Duration.ofSeconds(Long.MAX_VALUE, MAX_NANOSECONDS.toLong())
private val EPOCH: LocalDate = LocalDate.ofEpochDay(0) // can be replaced by LocalDate.EPOCH with Java 9
private val EPOCH_WITH_TIME: LocalDateTime = EPOCH.atStartOfDay()


/**
Expand Down Expand Up @@ -138,6 +141,41 @@ fun Generator.Companion.localDates(
}.withSamples(samples)
}

/**
* Returns a generator of [LocalDateTime] between [min] and [max] (inclusive)
*/
fun Generator.Companion.localDateTimes(
min: LocalDateTime = LocalDateTime.MIN,
max: LocalDateTime = LocalDateTime.MAX
): Generator<LocalDateTime> {
requireMaxEqualOrHigherThanMin(max, min)

val range = min..max

val samples = mutableListOf(min, max)

if (EPOCH_WITH_TIME in range && EPOCH_WITH_TIME !in samples) {
samples += EPOCH_WITH_TIME
}

return Generator { random: Random ->
val minSeconds = min.toEpochSecond(ZoneOffset.UTC)
val maxSeconds = max.toEpochSecond(ZoneOffset.UTC)

val seconds = random.nextLong(minSeconds, maxSeconds + 1)

val minNano =
if (seconds == minSeconds) min.nano
else 0
val maxNano =
if (seconds == maxSeconds) max.nano
else MAX_NANOSECONDS
val nanos = random.nextInt(minNano, maxNano + 1)

LocalDateTime.ofEpochSecond(seconds, nanos, ZoneOffset.UTC)
}.withSamples(samples)
}

private fun <T> requireMaxEqualOrHigherThanMin(max: Comparable<T>, min: T) {
require(max >= min) {
"Max must be equal or after min but min was $min and max was $max"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.github.jcornaz.kwik.generator.test.AbstractGeneratorTest
import java.time.Duration
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import kotlin.test.Test
import kotlin.test.assertFailsWith
Expand All @@ -14,6 +15,7 @@ import kotlin.test.assertTrue
private const val MAX_NANOSECONDS = 999_999_999L
// can be replaced by LocalDate.EPOCH with Java 9
private val EPOCH: LocalDate = LocalDate.ofEpochDay(0)
private val EPOCH_WITH_TIME: LocalDateTime = EPOCH.atStartOfDay()

class DurationGeneratorTest : AbstractGeneratorTest() {
override val generator: Generator<Duration> = Generator.durations(Duration.ZERO, Duration.ofDays(100))
Expand Down Expand Up @@ -224,7 +226,6 @@ class LocalTimeGeneratorTest : AbstractGeneratorTest() {
}
}


class LocalDateGeneratorTest : AbstractGeneratorTest() {

override val generator: Generator<LocalDate> = Generator.localDates()
Expand Down Expand Up @@ -289,3 +290,67 @@ class LocalDateGeneratorTest : AbstractGeneratorTest() {
assertTrue(Generator.localDates(min = min).randomSequence(0).take(50).any { it == min })
}
}

class LocalDateTimeGeneratorTest : AbstractGeneratorTest() {
override val generator: Generator<LocalDateTime> = Generator.localDateTimes()

@Test
fun `fail for invalid range`() {
assertFailsWith<IllegalArgumentException> {
Generator.localDateTimes(LocalDateTime.MAX, LocalDateTime.MIN)
}
}

@Test
fun `produce inside given range`() {
val min = EPOCH_WITH_TIME
val max = EPOCH_WITH_TIME.plusDays(2)
assertTrue(Generator.localDateTimes(min = min, max = max).randomSequence(0).take(50).all { it >= min && it <= max })
}

@Test
fun `do not produce epoch if not in range`() {
val min = EPOCH_WITH_TIME.plusHours(2)
assertTrue(Generator.localDateTimes(min = min).randomSequence(0).take(50).none { it == EPOCH_WITH_TIME })
}

@Test
fun `do not produce global max if not in range`() {
val max = LocalDateTime.MAX.minusHours(1)
assertTrue(Generator.localDateTimes(max = max).randomSequence(0).take(50).none { it == LocalDateTime.MAX })
}

@Test
fun `do not produce global min if not in range`() {
val min = LocalDateTime.MIN.plusHours(1)
assertTrue(Generator.localDateTimes(min = min).randomSequence(0).take(50).none { it == LocalDateTime.MIN })
}

@Test
fun `generate epoch`() {
assertTrue(Generator.localDateTimes().randomSequence(0).take(50).any { it == EPOCH_WITH_TIME })
}

@Test
fun `generate global max`() {
assertTrue(Generator.localDateTimes().randomSequence(0).take(50).any { it == LocalDateTime.MAX })
}


@Test
fun `generate global min`() {
assertTrue(Generator.localDateTimes().randomSequence(0).take(50).any { it == LocalDateTime.MIN })
}

@Test
fun `generate max`() {
val max = EPOCH_WITH_TIME.plusHours(1)
assertTrue(Generator.localDateTimes(max = max).randomSequence(0).take(50).any { it == max })
}

@Test
fun `generate min`() {
val min = EPOCH_WITH_TIME.plusHours(1)
assertTrue(Generator.localDateTimes(min = min).randomSequence(0).take(50).any { it == min })
}
}

0 comments on commit 4422e1a

Please sign in to comment.