summaryrefslogtreecommitdiff
path: root/src/library
diff options
context:
space:
mode:
authorRoland <rk@rkuhn.info>2012-09-07 16:04:52 +0200
committerRoland <rk@rkuhn.info>2012-09-07 16:04:52 +0200
commit7a833f94296849c0e2a6cbacb7e513599c831f38 (patch)
treeca34eeab89215ce3b44db39583bd012baaa7a7ac /src/library
parent9da135880d0b78b440f4ce62dd8b7dc156571d71 (diff)
downloadscala-7a833f94296849c0e2a6cbacb7e513599c831f38.tar.gz
scala-7a833f94296849c0e2a6cbacb7e513599c831f38.tar.bz2
scala-7a833f94296849c0e2a6cbacb7e513599c831f38.zip
second round of Duration cleanup
- make Duration behave consistent with Double's non-finite semantics - add ScalaDoc - add complete test suite - change overflow protection impl after review comments - clean up code
Diffstat (limited to 'src/library')
-rw-r--r--src/library/scala/concurrent/util/Duration.scala522
1 files changed, 425 insertions, 97 deletions
diff --git a/src/library/scala/concurrent/util/Duration.scala b/src/library/scala/concurrent/util/Duration.scala
index c5b7e8328d..c0eccec4db 100644
--- a/src/library/scala/concurrent/util/Duration.scala
+++ b/src/library/scala/concurrent/util/Duration.scala
@@ -13,32 +13,108 @@ import TimeUnit._
import java.lang.{ Double => JDouble, Long => JLong }
import language.implicitConversions
+/**
+ * This class stores a deadline, as obtained via `Deadline.now` or the
+ * duration DSL:
+ *
+ * {{{
+ * import scala.concurrent.util.duration._
+ * 3.seconds.fromNow
+ * }}}
+ *
+ * Its main purpose is to manage repeated attempts to achieve something (like
+ * awaiting a condition) by offering the methods `hasTimeLeft` and `timeLeft`. All
+ * durations are measured according to `System.nanoTime` aka wall-time; this
+ * does not take into account changes to the system clock (such as leap
+ * seconds).
+ */
case class Deadline private (time: Duration) extends Ordered[Deadline] {
+ /**
+ * Return a deadline advanced (i.e. moved into the future) by the given duration.
+ */
def +(other: Duration): Deadline = copy(time = time + other)
+ /**
+ * Return a deadline moved backwards (i.e. towards the past) by the given duration.
+ */
def -(other: Duration): Deadline = copy(time = time - other)
+ /**
+ * Calculate time difference between this and the other deadline, where the result is directed (i.e. may be negative).
+ */
def -(other: Deadline): Duration = time - other.time
+ /**
+ * Calculate time difference between this duration and now; the result is negative if the deadline has passed.
+ *
+ * '''''Note that on some systems this operation is costly because it entails a system call.'''''
+ * Check `System.nanoTime` for your platform.
+ */
def timeLeft: Duration = this - Deadline.now
+ /**
+ * Determine whether the deadline still lies in the future at the point where this method is called.
+ *
+ * '''''Note that on some systems this operation is costly because it entails a system call.'''''
+ * Check `System.nanoTime` for your platform.
+ */
def hasTimeLeft(): Boolean = !isOverdue()
+ /**
+ * Determine whether the deadline lies in the past at the point where this method is called.
+ *
+ * '''''Note that on some systems this operation is costly because it entails a system call.'''''
+ * Check `System.nanoTime` for your platform.
+ */
def isOverdue(): Boolean = (time.toNanos - System.nanoTime()) < 0
+ /**
+ * The natural ordering for deadline is determined by the natural order of the underlying (finite) duration.
+ */
def compare(other: Deadline) = time compare other.time
}
object Deadline {
+ /**
+ * Construct a deadline due exactly at the point where this method is called. Useful for then
+ * advancing it to obtain a future deadline, or for sampling the current time exactly once and
+ * then comparing it to multiple deadlines (using subtraction).
+ */
def now: Deadline = Deadline(Duration(System.nanoTime, NANOSECONDS))
+ /**
+ * The natural ordering for deadline is determined by the natural order of the underlying (finite) duration.
+ */
implicit object DeadlineIsOrdered extends Ordering[Deadline] {
def compare(a: Deadline, b: Deadline) = a compare b
}
}
-// TODO: "Inf", "PlusInf", "MinusInf", where did these names come from?
-// TODO: Duration.create(n, DAYS) == Duration(Long.MaxValue, NANOSECONDS) forall (n: Double) >= 106752d
object Duration {
+ /**
+ * This implicit conversion allows the use of a Deadline in place of a Duration, which will
+ * insert the time left until the deadline in its place.
+ */
implicit def timeLeft(implicit d: Deadline): Duration = d.timeLeft
- def apply(length: Double, unit: TimeUnit): FiniteDuration = fromNanos(unit.toNanos(1) * length)
- def apply(length: Long, unit: TimeUnit): FiniteDuration = new FiniteDuration(length, unit)
- def apply(length: Long, unit: String): FiniteDuration = new FiniteDuration(length, Duration.timeUnit(unit))
+ /**
+ * Construct a Duration from the given length and unit. Observe that nanosecond precision may be lost if
+ *
+ * - the unit is NANOSECONDS
+ * - and the length has an absolute value greater than 2^53
+ *
+ * Will throw an exception if the length was finite but the resulting duration cannot be expressed
+ * as a [[FiniteDuration]]. Infinite inputs (and NaN) are converted into [[Duration.Inf]],
+ * [[Duration.MinusInf]] and [[Duration.Undefined]], respectively.
+ */
+ def apply(length: Double, unit: TimeUnit): Duration = fromNanos(unit.toNanos(1) * length)
+ /**
+ * Construct a finite duration from the given length and time unit. The unit given is retained
+ * throughout calculations as long as possible, so that it can be retrieved later.
+ */
+ def apply(length: Long, unit: TimeUnit): FiniteDuration = new FiniteDuration(length, unit)
+ /**
+ * Construct a finite duration from the given length and time unit, where the latter is
+ * looked up in a list of string representation. Valid choices are:
+ *
+ * `d, day, h, hour, min, minute, s, sec, second, ms, milli, millisecond, µs, micro, microsecond, ns, nano, nanosecond`
+ * and their pluralized forms (for every but the first mentioned form of each unit, i.e. no "ds", but "days").
+ */
+ def apply(length: Long, unit: String): FiniteDuration = new FiniteDuration(length, Duration.timeUnit(unit))
/**
* Parse String into Duration. Format is `"<length><unit>"`, where
@@ -88,131 +164,217 @@ object Duration {
protected[util] val timeUnit: Map[String, TimeUnit] =
timeUnitLabels flatMap { case (unit, names) => expandLabels(names) map (_ -> unit) } toMap
+ /**
+ * Extract length and time unit out of a string, where the format must match the description for [[Duration$.apply(String):Duration apply(String)]].
+ * The extractor will not match for malformed strings or non-finite durations.
+ */
def unapply(s: String): Option[(Long, TimeUnit)] =
( try Some(apply(s)) catch { case _: RuntimeException => None } ) flatMap unapply
+ /**
+ * Extract length and time unit out of a duration, if it is finite.
+ */
def unapply(d: Duration): Option[(Long, TimeUnit)] =
if (d.isFinite) Some((d.length, d.unit)) else None
- def fromNanos(nanos: Double): FiniteDuration = {
- if (nanos > Long.MaxValue || nanos < Long.MinValue)
+ /**
+ * Construct a possibly infinite or undefined Duration from the given number of nanoseconds.
+ *
+ * - `Double.PositiveInfinity` is mapped to [[Duration.Inf]]
+ * - `Double.NegativeInfinity` is mapped to [[Duration.MinusInf]]
+ * - `Double.NaN` is mapped to [[Duration.Undefined]]
+ * - `-0d` is mapped to [[Duration.Zero]] (exactly like `0d`)
+ *
+ * The semantics of the resulting Duration objects matches the semantics of their Double
+ * counterparts with respect to arithmetic operations.
+ */
+ def fromNanos(nanos: Double): Duration = {
+ if (nanos.isInfinite)
+ if (nanos > 0) Inf else MinusInf
+ else if (nanos.isNaN)
+ Undefined
+ else if (nanos > Long.MaxValue || nanos < Long.MinValue)
throw new IllegalArgumentException("trying to construct too large duration with " + nanos + "ns")
- fromNanos((nanos + 0.5).toLong)
+ else
+ fromNanos((nanos + 0.5).toLong)
}
+ private val µs_per_ns = 1000L
+ private val ms_per_ns = µs_per_ns * 1000
+ private val s_per_ns = ms_per_ns * 1000
+ private val min_per_ns = s_per_ns * 60
+ private val h_per_ns = min_per_ns * 60
+ private val d_per_ns = h_per_ns * 24
+
+ /**
+ * Construct a finite duration from the given number of nanoseconds. This will throw an
+ * exception for `Long.MinValue` since that would lead to inconsistent behavior afterwards
+ * (cannot be negated). The result will have the coarsest possible time unit which can
+ * exactly express this duration.
+ */
def fromNanos(nanos: Long): FiniteDuration = {
- if (nanos % 86400000000000L == 0) {
- Duration(nanos / 86400000000000L, DAYS)
- } else if (nanos % 3600000000000L == 0) {
- Duration(nanos / 3600000000000L, HOURS)
- } else if (nanos % 60000000000L == 0) {
- Duration(nanos / 60000000000L, MINUTES)
- } else if (nanos % 1000000000L == 0) {
- Duration(nanos / 1000000000L, SECONDS)
- } else if (nanos % 1000000L == 0) {
- Duration(nanos / 1000000L, MILLISECONDS)
- } else if (nanos % 1000L == 0) {
- Duration(nanos / 1000L, MICROSECONDS)
- } else {
- Duration(nanos, NANOSECONDS)
- }
+ if (nanos % d_per_ns == 0) Duration(nanos / d_per_ns, DAYS)
+ else if (nanos % h_per_ns == 0) Duration(nanos / h_per_ns, HOURS)
+ else if (nanos % min_per_ns == 0) Duration(nanos / min_per_ns, MINUTES)
+ else if (nanos % s_per_ns == 0) Duration(nanos / s_per_ns, SECONDS)
+ else if (nanos % ms_per_ns == 0) Duration(nanos / ms_per_ns, MILLISECONDS)
+ else if (nanos % µs_per_ns == 0) Duration(nanos / µs_per_ns, MICROSECONDS)
+ else Duration(nanos, NANOSECONDS)
}
/**
- * Parse TimeUnit from string representation.
+ * Preconstructed value of `0.days`.
*/
+ // unit as coarse as possible to keep (_ + Zero) sane unit-wise
+ val Zero: FiniteDuration = new FiniteDuration(0, DAYS)
- val Zero: FiniteDuration = new FiniteDuration(0, NANOSECONDS)
-
- object Undefined extends Infinite {
- private def fail(what: String) = throw new IllegalArgumentException(s"cannot $what Undefined duration")
-
+ /**
+ * The Undefined value corresponds closely to Double.NaN:
+ *
+ * - it is the result of otherwise invalid operations
+ * - it does not equal itself (according to `equals()`)
+ * - it compares greater than any other Duration apart from itself (for which `compare` returns 0)
+ *
+ * The particular comparison semantics mirror those of Double.NaN.
+ *
+ * '''''Use `eq` when checking an input of a method against this value.'''''
+ */
+ val Undefined: Infinite = new Infinite {
override def toString = "Duration.Undefined"
- override def equals(other: Any) = this eq other.asInstanceOf[AnyRef]
- override def +(other: Duration): Duration = fail("add")
- override def -(other: Duration): Duration = fail("subtract")
- override def *(factor: Double): Duration = fail("multiply")
- override def /(factor: Double): Duration = fail("divide")
- override def /(other: Duration): Double = fail("divide")
- def compare(other: Duration) = fail("compare")
- def unary_- : Duration = fail("negate")
+ override def equals(other: Any) = false
+ override def +(other: Duration): Duration = this
+ override def -(other: Duration): Duration = this
+ override def *(factor: Double): Duration = this
+ override def /(factor: Double): Duration = this
+ override def /(other: Duration): Double = Double.NaN
+ def compare(other: Duration) = if (other eq this) 0 else 1
+ def unary_- : Duration = this
+ def toUnit(unit: TimeUnit): Double = Double.NaN
}
sealed abstract class Infinite extends Duration {
def +(other: Duration): Duration = other match {
- case x: Infinite if x ne this => throw new IllegalArgumentException("illegal addition of infinities")
+ case x if x eq Undefined => Undefined
+ case x: Infinite if x ne this => Undefined
case _ => this
}
- // Is this really intended to throw if the argument is "this" but otherwise return this?
- def -(other: Duration): Duration =
- if (other ne this) this
- else throw new IllegalArgumentException("illegal subtraction of infinities")
-
- private def mult(f: Double) =
- if (f == 0d) fail("multiply by zero")
- else if (f < 0d) -this
+ def -(other: Duration): Duration = other match {
+ case x if x eq Undefined => Undefined
+ case x: Infinite if x eq this => Undefined
+ case _ => this
+ }
+
+ def *(factor: Double): Duration =
+ if (factor == 0d || factor.isNaN) Undefined
+ else if (factor < 0d) -this
+ else this
+ def /(factor: Double): Duration =
+ if (factor == 0d || factor.isNaN || factor.isInfinite) Undefined
+ else if (factor < 0d) -this
else this
- def *(factor: Double): Duration = mult(factor)
- def /(factor: Double): Duration = mult(factor)
def /(other: Duration): Double = other match {
- case _: Infinite => throw new IllegalArgumentException("illegal division of infinities")
- // maybe questionable but pragmatic: Inf / 0 => Inf
+ case _: Infinite => Double.NaN
case x => Double.PositiveInfinity * (if ((this > Zero) ^ (other >= Zero)) -1 else 1)
}
final def isFinite() = false
private def fail(what: String) = throw new IllegalArgumentException(s"$what not allowed on infinite Durations")
- def length: Long = fail("length")
- def toNanos: Long = fail("toNanos")
- def toMicros: Long = fail("toMicros")
- def toMillis: Long = fail("toMillis")
- def toSeconds: Long = fail("toSeconds")
- def toMinutes: Long = fail("toMinutes")
- def toHours: Long = fail("toHours")
- def toDays: Long = fail("toDays")
-
- def unit: TimeUnit = fail("unit")
- def toUnit(unit: TimeUnit): Double = fail("toUnit")
+ final def length: Long = fail("length")
+ final def unit: TimeUnit = fail("unit")
+ final def toNanos: Long = fail("toNanos")
+ final def toMicros: Long = fail("toMicros")
+ final def toMillis: Long = fail("toMillis")
+ final def toSeconds: Long = fail("toSeconds")
+ final def toMinutes: Long = fail("toMinutes")
+ final def toHours: Long = fail("toHours")
+ final def toDays: Long = fail("toDays")
}
/**
- * Infinite duration: greater than any other and not equal to any other,
- * including itself.
+ * Infinite duration: greater than any other (apart from Undefined) and not equal to any other
+ * but itself. This value closely corresponds to Double.PositiveInfinity,
+ * matching its semantics in arithmetic operations.
*/
val Inf: Infinite = new Infinite {
override def toString = "Duration.Inf"
- def compare(other: Duration) = if (other eq this) 0 else 1
+ def compare(other: Duration) = other match {
+ case x if x eq Undefined => -1
+ case x if x eq this => 0
+ case _ => 1
+ }
def unary_- : Duration = MinusInf
+ def toUnit(unit: TimeUnit): Double = Double.PositiveInfinity
}
/**
- * Infinite negative duration: lesser than any other and not equal to any other,
- * including itself.
+ * Infinite duration: less than any other and not equal to any other
+ * but itself. This value closely corresponds to Double.NegativeInfinity,
+ * matching its semantics in arithmetic operations.
*/
val MinusInf: Infinite = new Infinite {
override def toString = "Duration.MinusInf"
- def compare(other: Duration) = if (other eq this) 0 else -1
+ def compare(other: Duration) = other match {
+ case x if x eq this => 0
+ case _ => -1
+ }
def unary_- : Duration = Inf
+ def toUnit(unit: TimeUnit): Double = Double.NegativeInfinity
}
// Java Factories
- def create(length: Long, unit: TimeUnit): FiniteDuration = apply(length, unit)
- def create(length: Double, unit: TimeUnit): FiniteDuration = apply(length, unit)
- def create(length: Long, unit: String): FiniteDuration = apply(length, unit)
- def create(s: String): Duration = apply(s)
+ /**
+ * Construct a finite duration from the given length and time unit. The unit given is retained
+ * throughout calculations as long as possible, so that it can be retrieved later.
+ */
+ def create(length: Long, unit: TimeUnit): FiniteDuration = apply(length, unit)
+ /**
+ * Construct a Duration from the given length and unit. Observe that nanosecond precision may be lost if
+ *
+ * - the unit is NANOSECONDS
+ * - and the length has an absolute value greater than 2^53
+ *
+ * Will throw an exception if the length was finite but the resulting duration cannot be expressed
+ * as a [[FiniteDuration]]. Infinite inputs (and NaN) are converted into [[Duration.Inf]],
+ * [[Duration.MinusInf]] and [[Duration.Undefined]], respectively.
+ */
+ def create(length: Double, unit: TimeUnit): Duration = apply(length, unit)
+ /**
+ * Construct a finite duration from the given length and time unit, where the latter is
+ * looked up in a list of string representation. Valid choices are:
+ *
+ * `d, day, h, hour, min, minute, s, sec, second, ms, milli, millisecond, µs, micro, microsecond, ns, nano, nanosecond`
+ * and their pluralized forms (for every but the first mentioned form of each unit, i.e. no "ds", but "days").
+ */
+ def create(length: Long, unit: String): FiniteDuration = apply(length, unit)
+ /**
+ * Parse String into Duration. Format is `"<length><unit>"`, where
+ * whitespace is allowed before, between and after the parts. Infinities are
+ * designated by `"Inf"`, `"PlusInf"`, `"+Inf"` and `"-Inf"` or `"MinusInf"`.
+ * Throws exception if format is not parseable.
+ */
+ def create(s: String): Duration = apply(s)
+
+ /**
+ * The natural ordering of durations matches the natural ordering for Double, including non-finite values.
+ */
implicit object DurationIsOrdered extends Ordering[Duration] {
def compare(a: Duration, b: Duration) = a compare b
}
}
/**
- * Utility for working with java.util.concurrent.TimeUnit durations.
+ * <h2>Utility for working with java.util.concurrent.TimeUnit durations.</h2>
+ *
+ * '''''This class is not meant as a general purpose representation of time, it is
+ * optimized for the needs of `scala.concurrent`.'''''
+ *
+ * <h2>Basic Usage</h2>
*
* <p/>
* Examples:
- * <pre>
+ * {{{
* import scala.concurrent.util.Duration
* import java.util.concurrent.TimeUnit
*
@@ -222,57 +384,196 @@ object Duration {
* duration.toNanos
* duration < 1.second
* duration <= Duration.Inf
- * </pre>
+ * }}}
+ *
+ * '''''Invoking inexpressible conversions (like calling `toSeconds` on an infinite duration) will throw an IllegalArgumentException.'''''
*
* <p/>
* Implicits are also provided for Int, Long and Double. Example usage:
- * <pre>
+ * {{{
* import scala.concurrent.util.Duration._
*
* val duration = 100 millis
- * </pre>
+ * }}}
+ *
+ * '''''The DSL provided by the implicit conversions always allows construction of finite durations, even for infinite Double inputs; use Duration.Inf instead.'''''
*
* Extractors, parsing and arithmetic are also included:
- * <pre>
+ * {{{
* val d = Duration("1.2 µs")
* val Duration(length, unit) = 5 millis
* val d2 = d * 2.5
* val d3 = d2 + 1.millisecond
- * </pre>
+ * }}}
+ *
+ * <h2>Handling of Time Units</h2>
+ *
+ * Calculations performed on finite durations always retain the more precise unit of either operand, no matter
+ * whether a coarser unit would be able to exactly express the same duration. This means that Duration can be
+ * used as a lossless container for a (length, unit) pair if it is constructed using the corresponding methods
+ * and no arithmetic is performed on it; adding/subtracting durations should in that case be done with care.
+ *
+ * <h2>Correspondence to Double Semantics</h2>
+ *
+ * The semantics of arithmetic operations on Duration are two-fold:
+ *
+ * - exact addition/subtraction with nanosecond resolution for finite durations, independent of the summands' magnitude
+ * - isomorphic to `java.lang.Double` when it comes to infinite or undefined values
+ *
+ * The conversion between Duration and Double is done using [[Duration.toUnit]] (with unit NANOSECONDS)
+ * and [[Duration$.fromNanos(Double):Duration Duration.fromNanos(Double)]].
+ *
+ * <h2>Ordering</h2>
+ *
+ * The default ordering is consistent with the ordering of Double numbers, which means that Undefined is
+ * considered greater than all other durations, including [[Duration.Inf]].
+ *
+ * @define exc Invoking this method on a non-finite duration will result in an IllegalArgumentException.
*/
-abstract class Duration extends Serializable with Ordered[Duration] {
+sealed abstract class Duration extends Serializable with Ordered[Duration] {
+ /**
+ * Obtain the length of this Duration measured in the unit obtained by the `unit` method. $exc
+ */
def length: Long
+ /**
+ * Obtain the time unit in which the length of this duration is measured. $exc
+ */
def unit: TimeUnit
+ /**
+ * Return the length of this duration measured in whole nanoseconds, rounding towards zero. $exc
+ */
def toNanos: Long
+ /**
+ * Return the length of this duration measured in whole microseconds, rounding towards zero. $exc
+ */
def toMicros: Long
+ /**
+ * Return the length of this duration measured in whole milliseconds, rounding towards zero. $exc
+ */
def toMillis: Long
+ /**
+ * Return the length of this duration measured in whole seconds, rounding towards zero. $exc
+ */
def toSeconds: Long
+ /**
+ * Return the length of this duration measured in whole minutes, rounding towards zero. $exc
+ */
def toMinutes: Long
+ /**
+ * Return the length of this duration measured in whole hours, rounding towards zero. $exc
+ */
def toHours: Long
+ /**
+ * Return the length of this duration measured in whole days, rounding towards zero. $exc
+ */
def toDays: Long
+ /**
+ * Return the number of nanoseconds as floating point number, scaled down to the given unit.
+ * The result may not precisely represent this duration due to the Double datatype's inherent
+ * limitations (mantissa size effectively 53 bits). Non-finite durations are represented as
+ * - [[Duration.Undefined]] is mapped to Double.NaN
+ * - [[Duration.Inf]] is mapped to Double.PositiveInfinity
+ * - [[Duration.MinusInf]] is mapped to Double.NegativeInfinity
+ */
def toUnit(unit: TimeUnit): Double
+ /**
+ * Return the sum of that duration and this. In case of an overflow, an IllegalArgumentException
+ * is thrown: the range of a finite duration is +-(2^63-1)ns, and no conversion to infinite
+ * durations takes place. When involving non-finite summands the semantics match those
+ * of Double.
+ */
def +(other: Duration): Duration
+ /**
+ * Return the difference of that duration and this. In case of an overflow, an IllegalArgumentException
+ * is thrown: the range of a finite duration is +-(2^63-1)ns, and no conversion to infinite
+ * durations takes place. When involving non-finite summands the semantics match those
+ * of Double.
+ */
def -(other: Duration): Duration
+ /**
+ * Return this duration multiplied by the scalar factor. In case of an overflow, an IllegalArgumentException
+ * is thrown: the range of a finite duration is +-(2^63-1)ns, and no conversion to infinite
+ * durations takes place. When involving non-finite factors the semantics match those
+ * of Double.
+ */
def *(factor: Double): Duration
+ /**
+ * Return this duration divided by the scalar factor. In case of an overflow, an IllegalArgumentException
+ * is thrown: the range of a finite duration is +-(2^63-1)ns, and no conversion to infinite
+ * durations takes place. When involving non-finite factors the semantics match those
+ * of Double.
+ */
def /(factor: Double): Duration
+ /**
+ * Return the quotient of this and that duration as floating-point number. The semantics are
+ * determined by Double as if calculating the quotient of the nanosecond lengths of both factors.
+ */
def /(other: Duration): Double
+ /**
+ * Negate this duration. The only two values which are mapped to themselves are [[Duration.Zero]] and [[Duration.Undefined]].
+ */
def unary_- : Duration
+ /**
+ * This method returns whether this duration is finite, which is not the same as
+ * `!isInfinite` for Double because this method also returns `false` for [[Duration.Undefined]].
+ */
def isFinite(): Boolean
+ /**
+ * Return the smaller of this and that duration as determined by the natural ordering.
+ */
def min(other: Duration): Duration = if (this < other) this else other
+ /**
+ * Return the larger of this and that duration as determined by the natural ordering.
+ */
def max(other: Duration): Duration = if (this > other) this else other
+ /**
+ * Construct a [[Deadline]] from this duration by adding it to the current instant `Duration.now`.
+ */
def fromNow: Deadline = Deadline.now + this
// Java API
+
+ /**
+ * Return this duration divided by the scalar factor. In case of an overflow, an IllegalArgumentException
+ * is thrown: the range of a finite duration is +-(2^63-1)ns, and no conversion to infinite
+ * durations takes place. When involving non-finite factors the semantics match those
+ * of Double.
+ */
def div(factor: Double) = this / factor
+ /**
+ * Return the quotient of this and that duration as floating-point number. The semantics are
+ * determined by Double as if calculating the quotient of the nanosecond lengths of both factors.
+ */
def div(other: Duration) = this / other
def gt(other: Duration) = this > other
def gteq(other: Duration) = this >= other
def lt(other: Duration) = this < other
def lteq(other: Duration) = this <= other
+ /**
+ * Return the difference of that duration and this. In case of an overflow, an IllegalArgumentException
+ * is thrown: the range of a finite duration is +-(2^63-1)ns, and no conversion to infinite
+ * durations takes place. When involving non-finite summands the semantics match those
+ * of Double.
+ */
def minus(other: Duration) = this - other
+ /**
+ * Return this duration multiplied by the scalar factor. In case of an overflow, an IllegalArgumentException
+ * is thrown: the range of a finite duration is +-(2^63-1)ns, and no conversion to infinite
+ * durations takes place. When involving non-finite factors the semantics match those
+ * of Double.
+ */
def mul(factor: Double) = this * factor
+ /**
+ * Negate this duration. The only two values which are mapped to themselves are [[Duration.Zero]] and [[Duration.Undefined]].
+ */
def neg() = -this
+ /**
+ * Return the sum of that duration and this. In case of an overflow, an IllegalArgumentException
+ * is thrown: the range of a finite duration is +-(2^63-1)ns, and no conversion to infinite
+ * durations takes place. When involving non-finite summands the semantics match those
+ * of Double.
+ */
def plus(other: Duration) = this + other
}
@@ -285,6 +586,10 @@ object FiniteDuration {
def apply(length: Long, unit: String) = new FiniteDuration(length, Duration.timeUnit(unit))
}
+/**
+ * This class represents a finite duration. Its addition and subtraction operators are overloaded to retain
+ * this guarantee statically. The range of this class is limited to +-(2^63-1)ns, which is roughly 292 years.
+ */
class FiniteDuration(val length: Long, val unit: TimeUnit) extends Duration {
import Duration._
@@ -293,7 +598,7 @@ class FiniteDuration(val length: Long, val unit: TimeUnit) extends Duration {
* sorted so that the first cases should be most-used ones, because enum
* is checked one after the other.
*/
- case NANOSECONDS ⇒ true
+ case NANOSECONDS ⇒ length != Long.MinValue
case MICROSECONDS ⇒ length <= 9223372036854775L && length >= -9223372036854775L
case MILLISECONDS ⇒ length <= 9223372036854L && length >= -9223372036854L
case SECONDS ⇒ length <= 9223372036L && length >= -9223372036L
@@ -301,9 +606,9 @@ class FiniteDuration(val length: Long, val unit: TimeUnit) extends Duration {
case HOURS ⇒ length <= 2562047L && length >= -2562047L
case DAYS ⇒ length <= 106751L && length >= -106751L
case _ ⇒
- val v = unit.convert(length, DAYS)
+ val v = DAYS.convert(length, unit)
v <= 106751L && v >= -106751L
- }, "Duration is limited to 2^63ns (ca. 292 years)")
+ }, "Duration is limited to +-(2^63-1)ns (ca. 292 years)")
def toNanos = unit.toNanos(length)
def toMicros = unit.toMicros(length)
@@ -322,30 +627,49 @@ class FiniteDuration(val length: Long, val unit: TimeUnit) extends Duration {
case _ => -(other compare this)
}
- private def add(a: Long, b: Long): Long = {
- val c = a + b
- // check if the signs of the top bit of both summands differ from the sum
- if (((a ^ c) & (b ^ c)) < 0) throw new IllegalArgumentException("integer overflow")
- else c
+ // see https://www.securecoding.cert.org/confluence/display/java/NUM00-J.+Detect+or+prevent+integer+overflow
+ private def safeAdd(a: Long, b: Long): Long = {
+ if ((b > 0) && (a > Long.MaxValue - b) ||
+ (b < 0) && (a < Long.MinValue - b)) throw new IllegalArgumentException("integer overflow")
+ a + b
+ }
+ private def add(otherLength: Long, otherUnit: TimeUnit): FiniteDuration = {
+ val commonUnit = if (otherUnit.convert(1, unit) == 0) unit else otherUnit
+ val totalLength = safeAdd(commonUnit.convert(length, unit), commonUnit.convert(otherLength, otherUnit))
+ new FiniteDuration(totalLength, commonUnit)
}
+
def +(other: Duration) = other match {
- case x: FiniteDuration => fromNanos(add(toNanos, x.toNanos))
+ case x: FiniteDuration => add(x.length, x.unit)
case _ => other
}
def -(other: Duration) = other match {
- case x: FiniteDuration => fromNanos(add(toNanos, -x.toNanos))
+ case x: FiniteDuration => add(-x.length, x.unit)
case _ => other
}
- def *(factor: Double) = fromNanos(toNanos.toDouble * factor)
-
- def /(factor: Double) = fromNanos(toNanos.toDouble / factor)
-
- def /(other: Duration) = if (other.isFinite) toNanos.toDouble / other.toNanos else 0
+ def *(factor: Double) =
+ if (!factor.isInfinite) fromNanos(toNanos * factor)
+ else if (factor.isNaN) Undefined
+ else if ((factor > 0) ^ (this < Zero)) Inf
+ else MinusInf
+
+ def /(factor: Double) =
+ if (!factor.isInfinite) fromNanos(toNanos / factor)
+ else if (factor.isNaN) Undefined
+ else Zero
+
+ // if this is made a constant, then scalac will elide the conditional and always return +0.0, SI-6331
+ private def minusZero = -0d
+ def /(other: Duration): Double =
+ if (other.isFinite) toNanos.toDouble / other.toNanos
+ else if (other eq Undefined) Double.NaN
+ else if ((length < 0) ^ (other > Zero)) 0d
+ else minusZero
// overridden methods taking FiniteDurations, so that you can calculate while statically staying finite
- def +(other: FiniteDuration) = fromNanos(add(toNanos, other.toNanos))
- def -(other: FiniteDuration) = fromNanos(add(toNanos, -other.toNanos))
+ def +(other: FiniteDuration) = add(other.length, other.unit)
+ def -(other: FiniteDuration) = add(-other.length, other.unit)
def plus(other: FiniteDuration) = this + other
def minus(other: FiniteDuration) = this - other
override def div(factor: Double) = this / factor
@@ -432,5 +756,9 @@ final class DurationLong(val n: Long) extends AnyVal with DurationConversions {
}
final class DurationDouble(val d: Double) extends AnyVal with DurationConversions {
- override protected def durationIn(unit: TimeUnit): FiniteDuration = Duration(d, unit)
+ override protected def durationIn(unit: TimeUnit): FiniteDuration =
+ Duration(d, unit) match {
+ case f: FiniteDuration => f
+ case _ => throw new IllegalArgumentException("Duration DSL not applicable to " + d)
+ }
}