diff options
author | Roland <rk@rkuhn.info> | 2012-09-07 16:04:52 +0200 |
---|---|---|
committer | Roland <rk@rkuhn.info> | 2012-09-07 16:04:52 +0200 |
commit | 7a833f94296849c0e2a6cbacb7e513599c831f38 (patch) | |
tree | ca34eeab89215ce3b44db39583bd012baaa7a7ac /src | |
parent | 9da135880d0b78b440f4ce62dd8b7dc156571d71 (diff) | |
download | scala-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')
-rw-r--r-- | src/library/scala/concurrent/util/Duration.scala | 522 |
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) + } } |