mirror of
https://github.com/corda/corda.git
synced 2025-06-22 00:57:21 +00:00
Cleaned up TimeWindow and added a bit more docs.
This commit is contained in:
@ -21,7 +21,6 @@ import java.math.BigDecimal
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.time.Duration
|
||||
import java.time.temporal.Temporal
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.Future
|
||||
@ -95,9 +94,6 @@ fun <A> ListenableFuture<out A>.toObservable(): Observable<A> {
|
||||
}
|
||||
}
|
||||
|
||||
// Simple infix function to add back null safety that the JDK lacks: timeA until timeB
|
||||
infix fun Temporal.until(endExclusive: Temporal): Duration = Duration.between(this, endExclusive)
|
||||
|
||||
/** Returns the index of the given item or throws [IllegalArgumentException] if not found. */
|
||||
fun <T> List<T>.indexOfOrThrow(item: T): Int {
|
||||
val i = indexOf(item)
|
||||
|
@ -17,7 +17,6 @@ import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.security.PublicKey
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.jar.JarInputStream
|
||||
|
||||
@ -328,63 +327,6 @@ data class AuthenticatedObject<out T : Any>(
|
||||
)
|
||||
// DOCEND 6
|
||||
|
||||
/**
|
||||
* A time-window is required for validation/notarization purposes.
|
||||
* If present in a transaction, contains a time that was verified by the uniqueness service. The true time must be
|
||||
* between (fromTime, untilTime).
|
||||
* Usually, a time-window is required to have both sides set (fromTime, untilTime).
|
||||
* However, some apps may require that a time-window has a start [Instant] (fromTime), but no end [Instant] (untilTime) and vice versa.
|
||||
* TODO: Consider refactoring using TimeWindow abstraction like TimeWindow.From, TimeWindow.Until, TimeWindow.Between.
|
||||
*/
|
||||
@CordaSerializable
|
||||
class TimeWindow private constructor(
|
||||
/** The time at which this transaction is said to have occurred is after this moment. */
|
||||
val fromTime: Instant?,
|
||||
/** The time at which this transaction is said to have occurred is before this moment. */
|
||||
val untilTime: Instant?
|
||||
) {
|
||||
companion object {
|
||||
/** Use when the left-side [fromTime] of a [TimeWindow] is only required and we don't need an end instant (untilTime). */
|
||||
@JvmStatic
|
||||
fun fromOnly(fromTime: Instant) = TimeWindow(fromTime, null)
|
||||
|
||||
/** Use when the right-side [untilTime] of a [TimeWindow] is only required and we don't need a start instant (fromTime). */
|
||||
@JvmStatic
|
||||
fun untilOnly(untilTime: Instant) = TimeWindow(null, untilTime)
|
||||
|
||||
/** Use when both sides of a [TimeWindow] must be set ([fromTime], [untilTime]). */
|
||||
@JvmStatic
|
||||
fun between(fromTime: Instant, untilTime: Instant): TimeWindow {
|
||||
require(fromTime < untilTime) { "fromTime should be earlier than untilTime" }
|
||||
return TimeWindow(fromTime, untilTime)
|
||||
}
|
||||
|
||||
/** Use when we have a start time and a period of validity. */
|
||||
@JvmStatic
|
||||
fun fromStartAndDuration(fromTime: Instant, duration: Duration): TimeWindow = between(fromTime, fromTime + duration)
|
||||
|
||||
/**
|
||||
* When we need to create a [TimeWindow] based on a specific time [Instant] and some tolerance in both sides of this instant.
|
||||
* The result will be the following time-window: ([time] - [tolerance], [time] + [tolerance]).
|
||||
*/
|
||||
@JvmStatic
|
||||
fun withTolerance(time: Instant, tolerance: Duration) = between(time - tolerance, time + tolerance)
|
||||
}
|
||||
|
||||
/** The midpoint is calculated as fromTime + (untilTime - fromTime)/2. Note that it can only be computed if both sides are set. */
|
||||
val midpoint: Instant get() = fromTime!! + Duration.between(fromTime, untilTime!!).dividedBy(2)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is TimeWindow) return false
|
||||
return (fromTime == other.fromTime && untilTime == other.untilTime)
|
||||
}
|
||||
|
||||
override fun hashCode() = 31 * (fromTime?.hashCode() ?: 0) + (untilTime?.hashCode() ?: 0)
|
||||
|
||||
override fun toString() = "TimeWindow(fromTime=$fromTime, untilTime=$untilTime)"
|
||||
}
|
||||
|
||||
// DOCSTART 5
|
||||
/**
|
||||
* Implemented by a program that implements business logic on the shared ledger. All participants run this code for
|
||||
|
87
core/src/main/kotlin/net/corda/core/contracts/TimeWindow.kt
Normal file
87
core/src/main/kotlin/net/corda/core/contracts/TimeWindow.kt
Normal file
@ -0,0 +1,87 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.until
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* A time-window is required for validation/notarization purposes. If present in a transaction, contains a time that was
|
||||
* verified by the uniqueness service. The true time must be in the time interval `[fromTime, untilTime)`.
|
||||
*
|
||||
* Usually a time-window is required to have both sides defined. However some apps may require a time-window which is
|
||||
* open-ended on one of the two sides.
|
||||
*/
|
||||
@CordaSerializable
|
||||
abstract class TimeWindow {
|
||||
companion object {
|
||||
/** Creates a [TimeWindow] with null [untilTime], i.e. the time interval `[fromTime, ∞]`. [midpoint] will return null. */
|
||||
@JvmStatic
|
||||
fun fromOnly(fromTime: Instant): TimeWindow = From(fromTime)
|
||||
|
||||
/** Creates a [TimeWindow] with null [fromTime], i.e. the time interval `[∞, untilTime)`. [midpoint] will return null. */
|
||||
@JvmStatic
|
||||
fun untilOnly(untilTime: Instant): TimeWindow = Until(untilTime)
|
||||
|
||||
/**
|
||||
* Creates a [TimeWindow] with the time interval `[fromTime, untilTime)`. [midpoint] will return
|
||||
* `fromTime + (untilTime - fromTime) / 2`.
|
||||
* @throws IllegalArgumentException If [fromTime] ≥ [untilTime]
|
||||
*/
|
||||
@JvmStatic
|
||||
fun between(fromTime: Instant, untilTime: Instant): TimeWindow = Between(fromTime, untilTime)
|
||||
|
||||
/**
|
||||
* Creates a [TimeWindow] with the time interval `[fromTime, fromTime + duration)`. [midpoint] will return
|
||||
* `fromTime + duration / 2`
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromStartAndDuration(fromTime: Instant, duration: Duration): TimeWindow = between(fromTime, fromTime + duration)
|
||||
|
||||
/**
|
||||
* Creates a [TimeWindow] which is centered around [instant] with the given [tolerance] on both sides, i.e the
|
||||
* time interval `[instant - tolerance, instant + tolerance)`. [midpoint] will return [instant].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun withTolerance(instant: Instant, tolerance: Duration) = between(instant - tolerance, instant + tolerance)
|
||||
}
|
||||
|
||||
/** Returns the inclusive lower-bound of this [TimeWindow]'s interval, with null implying infinity. */
|
||||
abstract val fromTime: Instant?
|
||||
|
||||
/** Returns the exclusive upper-bound of this [TimeWindow]'s interval, with null implying infinity. */
|
||||
abstract val untilTime: Instant?
|
||||
|
||||
/**
|
||||
* Returns the midpoint of [fromTime] and [untilTime] if both are non-null, calculated as
|
||||
* `fromTime + (untilTime - fromTime)/2`, otherwise returns null.
|
||||
*/
|
||||
abstract val midpoint: Instant?
|
||||
|
||||
/** Returns true iff the given [instant] is within the time interval of this [TimeWindow]. */
|
||||
abstract operator fun contains(instant: Instant): Boolean
|
||||
|
||||
private data class From(override val fromTime: Instant) : TimeWindow() {
|
||||
override val untilTime: Instant? get() = null
|
||||
override val midpoint: Instant? get() = null
|
||||
override fun contains(instant: Instant): Boolean = instant >= fromTime
|
||||
override fun toString(): String = "[$fromTime, ∞]"
|
||||
}
|
||||
|
||||
private data class Until(override val untilTime: Instant) : TimeWindow() {
|
||||
override val fromTime: Instant? get() = null
|
||||
override val midpoint: Instant? get() = null
|
||||
override fun contains(instant: Instant): Boolean = instant < untilTime
|
||||
override fun toString(): String = "[∞, $untilTime)"
|
||||
}
|
||||
|
||||
private data class Between(override val fromTime: Instant, override val untilTime: Instant) : TimeWindow() {
|
||||
init {
|
||||
require(fromTime < untilTime) { "fromTime must be earlier than untilTime" }
|
||||
}
|
||||
override val midpoint: Instant get() = fromTime + (fromTime until untilTime) / 2
|
||||
override fun contains(instant: Instant): Boolean = instant >= fromTime && instant < untilTime
|
||||
override fun toString(): String = "[$fromTime, $untilTime)"
|
||||
}
|
||||
}
|
@ -7,9 +7,16 @@ import java.nio.charset.Charset
|
||||
import java.nio.charset.StandardCharsets.UTF_8
|
||||
import java.nio.file.*
|
||||
import java.nio.file.attribute.FileAttribute
|
||||
import java.time.Duration
|
||||
import java.time.temporal.Temporal
|
||||
import java.util.stream.Stream
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
infix fun Temporal.until(endExclusive: Temporal): Duration = Duration.between(this, endExclusive)
|
||||
|
||||
operator fun Duration.div(divider: Long): Duration = dividedBy(divider)
|
||||
operator fun Duration.times(multiplicand: Long): Duration = multipliedBy(multiplicand)
|
||||
|
||||
/**
|
||||
* Allows you to write code like: Paths.get("someDir") / "subdir" / "filename" but using the Paths API to avoid platform
|
||||
* separator problems.
|
||||
|
@ -7,16 +7,5 @@ import java.time.Clock
|
||||
* Checks if the current instant provided by the input clock falls within the provided time-window.
|
||||
*/
|
||||
class TimeWindowChecker(val clock: Clock = Clock.systemUTC()) {
|
||||
fun isValid(timeWindow: TimeWindow): Boolean {
|
||||
val fromTime = timeWindow.fromTime
|
||||
val untilTime = timeWindow.untilTime
|
||||
|
||||
val now = clock.instant()
|
||||
|
||||
// We don't need to test for (fromTime == null && untilTime == null) or backwards bounds because the TimeWindow
|
||||
// constructor already checks that.
|
||||
if (fromTime != null && now < fromTime) return false
|
||||
if (untilTime != null && now > untilTime) return false
|
||||
return true
|
||||
}
|
||||
fun isValid(timeWindow: TimeWindow): Boolean = clock.instant() in timeWindow
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.utilities.millis
|
||||
import net.corda.core.utilities.minutes
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneOffset.UTC
|
||||
|
||||
class TimeWindowTest {
|
||||
private val now = Instant.now()
|
||||
|
||||
@Test
|
||||
fun fromOnly() {
|
||||
val timeWindow = TimeWindow.fromOnly(now)
|
||||
assertThat(timeWindow.fromTime).isEqualTo(now)
|
||||
assertThat(timeWindow.untilTime).isNull()
|
||||
assertThat(timeWindow.midpoint).isNull()
|
||||
assertThat(timeWindow.contains(now - 1.millis)).isFalse()
|
||||
assertThat(timeWindow.contains(now)).isTrue()
|
||||
assertThat(timeWindow.contains(now + 1.millis)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun untilOnly() {
|
||||
val timeWindow = TimeWindow.untilOnly(now)
|
||||
assertThat(timeWindow.fromTime).isNull()
|
||||
assertThat(timeWindow.untilTime).isEqualTo(now)
|
||||
assertThat(timeWindow.midpoint).isNull()
|
||||
assertThat(timeWindow.contains(now - 1.millis)).isTrue()
|
||||
assertThat(timeWindow.contains(now)).isFalse()
|
||||
assertThat(timeWindow.contains(now + 1.millis)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun between() {
|
||||
val today = LocalDate.now()
|
||||
val fromTime = today.atTime(12, 0).toInstant(UTC)
|
||||
val untilTime = today.atTime(12, 30).toInstant(UTC)
|
||||
val timeWindow = TimeWindow.between(fromTime, untilTime)
|
||||
assertThat(timeWindow.fromTime).isEqualTo(fromTime)
|
||||
assertThat(timeWindow.untilTime).isEqualTo(untilTime)
|
||||
assertThat(timeWindow.midpoint).isEqualTo(today.atTime(12, 15).toInstant(UTC))
|
||||
assertThat(timeWindow.contains(fromTime - 1.millis)).isFalse()
|
||||
assertThat(timeWindow.contains(fromTime)).isTrue()
|
||||
assertThat(timeWindow.contains(fromTime + 1.millis)).isTrue()
|
||||
assertThat(timeWindow.contains(untilTime)).isFalse()
|
||||
assertThat(timeWindow.contains(untilTime + 1.millis)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fromStartAndDuration() {
|
||||
val timeWindow = TimeWindow.fromStartAndDuration(now, 10.minutes)
|
||||
assertThat(timeWindow.fromTime).isEqualTo(now)
|
||||
assertThat(timeWindow.untilTime).isEqualTo(now + 10.minutes)
|
||||
assertThat(timeWindow.midpoint).isEqualTo(now + 5.minutes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun withTolerance() {
|
||||
val timeWindow = TimeWindow.withTolerance(now, 10.minutes)
|
||||
assertThat(timeWindow.fromTime).isEqualTo(now - 10.minutes)
|
||||
assertThat(timeWindow.untilTime).isEqualTo(now + 10.minutes)
|
||||
assertThat(timeWindow.midpoint).isEqualTo(now)
|
||||
}
|
||||
}
|
@ -5,12 +5,12 @@ import net.corda.core.utilities.seconds
|
||||
import org.junit.Test
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.ZoneOffset
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class TimeWindowCheckerTests {
|
||||
val clock = Clock.fixed(Instant.now(), ZoneId.systemDefault())
|
||||
val clock: Clock = Clock.fixed(Instant.now(), ZoneOffset.UTC)
|
||||
val timeWindowChecker = TimeWindowChecker(clock)
|
||||
|
||||
@Test
|
||||
|
Reference in New Issue
Block a user