summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/library/scala/concurrent/util/Duration.scala67
-rw-r--r--test/files/jvm/duration-java.check46
-rw-r--r--test/files/jvm/duration-java/Test.java12
-rw-r--r--test/files/jvm/duration-tck.scala107
4 files changed, 199 insertions, 33 deletions
diff --git a/src/library/scala/concurrent/util/Duration.scala b/src/library/scala/concurrent/util/Duration.scala
index bf0f3006f1..c5b7e8328d 100644
--- a/src/library/scala/concurrent/util/Duration.scala
+++ b/src/library/scala/concurrent/util/Duration.scala
@@ -10,20 +10,25 @@ package scala.concurrent.util
import java.util.concurrent.TimeUnit
import TimeUnit._
-import java.lang.{ Double => JDouble }
+import java.lang.{ Double => JDouble, Long => JLong }
import language.implicitConversions
-case class Deadline private (time: Duration) {
+case class Deadline private (time: Duration) extends Ordered[Deadline] {
def +(other: Duration): Deadline = copy(time = time + other)
def -(other: Duration): Deadline = copy(time = time - other)
def -(other: Deadline): Duration = time - other.time
def timeLeft: Duration = this - Deadline.now
def hasTimeLeft(): Boolean = !isOverdue()
def isOverdue(): Boolean = (time.toNanos - System.nanoTime()) < 0
+ def compare(other: Deadline) = time compare other.time
}
object Deadline {
def now: Deadline = Deadline(Duration(System.nanoTime, NANOSECONDS))
+
+ 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?
@@ -48,9 +53,12 @@ object Duration {
case "MinusInf" | "-Inf" => MinusInf
case _ =>
val unitName = s1.reverse takeWhile (_.isLetter) reverse;
- def length = JDouble.parseDouble(s1 dropRight unitName.length)
timeUnit get unitName match {
- case Some(unit) => Duration(length, unit)
+ case Some(unit) =>
+ val valueStr = s1 dropRight unitName.length
+ val valueD = JDouble.parseDouble(valueStr)
+ if (valueD <= 9007199254740992d && valueD >= -9007199254740992d) Duration(valueD, unit)
+ else Duration(JLong.parseLong(valueStr), unit)
case _ => sys.error("format error " + s)
}
}
@@ -86,8 +94,11 @@ object Duration {
def unapply(d: Duration): Option[(Long, TimeUnit)] =
if (d.isFinite) Some((d.length, d.unit)) else None
- def fromNanos(nanos: Double): FiniteDuration =
+ def fromNanos(nanos: Double): FiniteDuration = {
+ if (nanos > Long.MaxValue || nanos < Long.MinValue)
+ throw new IllegalArgumentException("trying to construct too large duration with " + nanos + "ns")
fromNanos((nanos + 0.5).toLong)
+ }
def fromNanos(nanos: Long): FiniteDuration = {
if (nanos % 86400000000000L == 0) {
@@ -137,8 +148,12 @@ object Duration {
if (other ne this) this
else throw new IllegalArgumentException("illegal subtraction of infinities")
- def *(factor: Double): Duration = this
- def /(factor: Double): Duration = this
+ private def mult(f: Double) =
+ if (f == 0d) fail("multiply by zero")
+ else if (f < 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
@@ -273,6 +288,23 @@ object FiniteDuration {
class FiniteDuration(val length: Long, val unit: TimeUnit) extends Duration {
import Duration._
+ require(unit match {
+ /*
+ * sorted so that the first cases should be most-used ones, because enum
+ * is checked one after the other.
+ */
+ case NANOSECONDS ⇒ true
+ case MICROSECONDS ⇒ length <= 9223372036854775L && length >= -9223372036854775L
+ case MILLISECONDS ⇒ length <= 9223372036854L && length >= -9223372036854L
+ case SECONDS ⇒ length <= 9223372036L && length >= -9223372036L
+ case MINUTES ⇒ length <= 153722867L && length >= -153722867L
+ case HOURS ⇒ length <= 2562047L && length >= -2562047L
+ case DAYS ⇒ length <= 106751L && length >= -106751L
+ case _ ⇒
+ val v = unit.convert(length, DAYS)
+ v <= 106751L && v >= -106751L
+ }, "Duration is limited to 2^63ns (ca. 292 years)")
+
def toNanos = unit.toNanos(length)
def toMicros = unit.toMicros(length)
def toMillis = unit.toMillis(length)
@@ -289,12 +321,19 @@ class FiniteDuration(val length: Long, val unit: TimeUnit) extends Duration {
case x: FiniteDuration => toNanos compare x.toNanos
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
+ }
def +(other: Duration) = other match {
- case x: FiniteDuration => fromNanos(toNanos + x.toNanos)
+ case x: FiniteDuration => fromNanos(add(toNanos, x.toNanos))
case _ => other
}
def -(other: Duration) = other match {
- case x: FiniteDuration => fromNanos(toNanos - x.toNanos)
+ case x: FiniteDuration => fromNanos(add(toNanos, -x.toNanos))
case _ => other
}
@@ -304,6 +343,16 @@ class FiniteDuration(val length: Long, val unit: TimeUnit) extends Duration {
def /(other: Duration) = if (other.isFinite) toNanos.toDouble / other.toNanos else 0
+ // 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 plus(other: FiniteDuration) = this + other
+ def minus(other: FiniteDuration) = this - other
+ override def div(factor: Double) = this / factor
+ override def mul(factor: Double) = this * factor
+ def min(other: FiniteDuration) = if (this < other) this else other
+ def max(other: FiniteDuration) = if (this > other) this else other
+
def unary_- = Duration(-length, unit)
final def isFinite() = true
diff --git a/test/files/jvm/duration-java.check b/test/files/jvm/duration-java.check
index 7ae257dcc0..49d06fbe93 100644
--- a/test/files/jvm/duration-java.check
+++ b/test/files/jvm/duration-java.check
@@ -201,7 +201,7 @@
6.0E7 seconds => 1000000 minutes
1.0E8 seconds => 100000000 seconds
1.0E9 seconds => 1000000000 seconds
- 1.0E12 seconds => 9223372036854775807 nanoseconds
+ 1.0E12 seconds => class java.lang.IllegalArgumentException
0.0 minutes => 0 days
1.0 minutes => 1 minute
7.0 minutes => 7 minutes
@@ -251,8 +251,8 @@
3.0E7 minutes => 500000 hours
6.0E7 minutes => 1000000 hours
1.0E8 minutes => 100000000 minutes
- 1.0E9 minutes => 9223372036854775807 nanoseconds
- 1.0E12 minutes => 9223372036854775807 nanoseconds
+ 1.0E9 minutes => class java.lang.IllegalArgumentException
+ 1.0E12 minutes => class java.lang.IllegalArgumentException
0.0 hours => 0 days
1.0 hours => 1 hour
7.0 hours => 7 hours
@@ -295,15 +295,15 @@
60000.0 hours => 2500 days
100000.0 hours => 100000 hours
1000000.0 hours => 1000000 hours
- 7000000.0 hours => 9223372036854775807 nanoseconds
- 1.0E7 hours => 9223372036854775807 nanoseconds
- 1.2E7 hours => 9223372036854775807 nanoseconds
- 2.4E7 hours => 9223372036854775807 nanoseconds
- 3.0E7 hours => 9223372036854775807 nanoseconds
- 6.0E7 hours => 9223372036854775807 nanoseconds
- 1.0E8 hours => 9223372036854775807 nanoseconds
- 1.0E9 hours => 9223372036854775807 nanoseconds
- 1.0E12 hours => 9223372036854775807 nanoseconds
+ 7000000.0 hours => class java.lang.IllegalArgumentException
+ 1.0E7 hours => class java.lang.IllegalArgumentException
+ 1.2E7 hours => class java.lang.IllegalArgumentException
+ 2.4E7 hours => class java.lang.IllegalArgumentException
+ 3.0E7 hours => class java.lang.IllegalArgumentException
+ 6.0E7 hours => class java.lang.IllegalArgumentException
+ 1.0E8 hours => class java.lang.IllegalArgumentException
+ 1.0E9 hours => class java.lang.IllegalArgumentException
+ 1.0E12 hours => class java.lang.IllegalArgumentException
0.0 days => 0 days
1.0 days => 1 day
7.0 days => 7 days
@@ -345,16 +345,18 @@
30000.0 days => 30000 days
60000.0 days => 60000 days
100000.0 days => 100000 days
- 1000000.0 days => 9223372036854775807 nanoseconds
- 7000000.0 days => 9223372036854775807 nanoseconds
- 1.0E7 days => 9223372036854775807 nanoseconds
- 1.2E7 days => 9223372036854775807 nanoseconds
- 2.4E7 days => 9223372036854775807 nanoseconds
- 3.0E7 days => 9223372036854775807 nanoseconds
- 6.0E7 days => 9223372036854775807 nanoseconds
- 1.0E8 days => 9223372036854775807 nanoseconds
- 1.0E9 days => 9223372036854775807 nanoseconds
- 1.0E12 days => 9223372036854775807 nanoseconds
+ 1000000.0 days => class java.lang.IllegalArgumentException
+ 7000000.0 days => class java.lang.IllegalArgumentException
+ 1.0E7 days => class java.lang.IllegalArgumentException
+ 1.2E7 days => class java.lang.IllegalArgumentException
+ 2.4E7 days => class java.lang.IllegalArgumentException
+ 3.0E7 days => class java.lang.IllegalArgumentException
+ 6.0E7 days => class java.lang.IllegalArgumentException
+ 1.0E8 days => class java.lang.IllegalArgumentException
+ 1.0E9 days => class java.lang.IllegalArgumentException
+ 1.0E12 days => class java.lang.IllegalArgumentException
+10000000000000001 nanoseconds => 10000000000000001 nanoseconds
+10000000000000002 nanoseconds => 10000000000000002 nanoseconds
Inf => Duration.Inf
-Inf => Duration.MinusInf
+Inf => Duration.Inf
diff --git a/test/files/jvm/duration-java/Test.java b/test/files/jvm/duration-java/Test.java
index 02feb522b8..1c53ccb266 100644
--- a/test/files/jvm/duration-java/Test.java
+++ b/test/files/jvm/duration-java/Test.java
@@ -26,10 +26,18 @@ public class Test {
for (TimeUnit t : TimeUnit.values()) {
for (Double n: makeNumbers()) {
String s = "" + n + " " + t.toString().toLowerCase();
- Duration d = Duration.create(n, t);
- p(String.format("%25s => %s", s, d));
+ String result;
+ try {
+ Duration d = Duration.create(n, t);
+ result = d.toString();
+ } catch(Exception e) {
+ result = e.getClass().toString();
+ }
+ p(String.format("%25s => %s", s, result));
}
}
+ for (String s: new String[] {"10000000000000001 nanoseconds", "10000000000000002 nanoseconds"})
+ p(String.format("%25s => %s", s, Duration.create(s)));
for (String s: Arrays.asList("Inf", "-Inf", "+Inf", "PlusInf", "MinusInf")) {
Duration d = Duration.create(s);
p(String.format("%25s => %s", s, d));
diff --git a/test/files/jvm/duration-tck.scala b/test/files/jvm/duration-tck.scala
new file mode 100644
index 0000000000..679712aa59
--- /dev/null
+++ b/test/files/jvm/duration-tck.scala
@@ -0,0 +1,107 @@
+/**
+ * Copyright (C) 2012 Typesafe Inc. <http://www.typesafe.com>
+ */
+
+import scala.concurrent.util._
+import duration._
+import scala.reflect._
+import java.util.concurrent.TimeUnit._
+
+object Test extends App {
+
+ implicit class Assert(val left: Any) extends AnyVal {
+ def =!=(right: Any) = assert(left == right, s"$left was not equal to $right")
+ }
+
+ def intercept[T <: Exception : ClassTag](code: => Unit) =
+ try { code; assert(false, "did not throw expected exception " + classTag[T]) }
+ catch {
+ case ex: Exception => if (classTag[T].runtimeClass isAssignableFrom ex.getClass) () else throw ex
+ }
+
+ { // test field ops
+ val zero = 0 seconds
+ val one = 1 second
+ val two = one + one
+ val three = 3 * one
+ (0 * one) =!= (zero)
+ (2 * one) =!= (two)
+ (three - two) =!= (one)
+ (three / 3) =!= (one)
+ (two / one) =!= (2)
+ (one + zero) =!= (one)
+ (one / 1000000) =!= (1.micro)
+ }
+
+ { // test infinities
+ val one = 1.second
+ val inf = Duration.Inf
+ val minf = Duration.MinusInf
+ (-inf) =!= (minf)
+ intercept[IllegalArgumentException] { minf + inf }
+ intercept[IllegalArgumentException] { inf - inf }
+ intercept[IllegalArgumentException] { inf + minf }
+ intercept[IllegalArgumentException] { minf - minf }
+ (inf + inf) =!= (inf)
+ (inf - minf) =!= (inf)
+ (minf - inf) =!= (minf)
+ (minf + minf) =!= (minf)
+ assert(inf == inf)
+ assert(minf == minf)
+ inf.compareTo(inf) =!= (0)
+ inf.compareTo(one) =!= (1)
+ minf.compareTo(minf) =!= (0)
+ minf.compareTo(one) =!= (-1)
+ assert(inf != minf)
+ assert(minf != inf)
+ assert(one != inf)
+ assert(minf != one)
+ inf =!= (minf * -1d)
+ inf =!= (minf / -1d)
+ }
+
+ { // test overflow protection
+ for (unit ← Seq(DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS)) {
+ val x = unit.convert(Long.MaxValue, NANOSECONDS)
+ val dur = Duration(x, unit)
+ val mdur = Duration(-x, unit)
+ -mdur =!= (dur)
+ intercept[IllegalArgumentException] { Duration(x + 10000000d, unit) }
+ intercept[IllegalArgumentException] { Duration(-x - 10000000d, unit) }
+ if (unit != NANOSECONDS) {
+ intercept[IllegalArgumentException] { Duration(x + 1, unit) }
+ intercept[IllegalArgumentException] { Duration(-x - 1, unit) }
+ }
+ intercept[IllegalArgumentException] { dur + 1.day }
+ intercept[IllegalArgumentException] { mdur - 1.day }
+ intercept[IllegalArgumentException] { dur * 1.1 }
+ intercept[IllegalArgumentException] { mdur * 1.1 }
+ intercept[IllegalArgumentException] { dur * 2.1 }
+ intercept[IllegalArgumentException] { mdur * 2.1 }
+ intercept[IllegalArgumentException] { dur / 0.9 }
+ intercept[IllegalArgumentException] { mdur / 0.9 }
+ intercept[IllegalArgumentException] { dur / 0.4 }
+ intercept[IllegalArgumentException] { mdur / 0.4 }
+ Duration(x + unit.toString.toLowerCase)
+ Duration("-" + x + unit.toString.toLowerCase)
+ intercept[IllegalArgumentException] { Duration("%.0f".format(x + 10000000d) + unit.toString.toLowerCase) }
+ intercept[IllegalArgumentException] { Duration("-%.0f".format(x + 10000000d) + unit.toString.toLowerCase) }
+ }
+ }
+
+ { // test Deadline
+ val dead = 2.seconds.fromNow
+ val dead2 = 2 seconds fromNow
+ // view bounds vs. very local type inference vs. operator precedence: sigh
+ assert(dead.timeLeft > (1 second: Duration))
+ assert(dead2.timeLeft > (1 second: Duration))
+ Thread.sleep(1.second.toMillis)
+ assert(dead.timeLeft < (1 second: Duration))
+ assert(dead2.timeLeft < (1 second: Duration))
+ }
+
+ { // check statically retaining finite-ness
+ val d: FiniteDuration = 1.second * 2 / 1.4 mul 1.1 div 2.1 plus 3.seconds minus 1.millisecond min 1.second max 1.second
+ }
+
+}