From 4e798047b802b0123bc1f700fd1620f390889a85 Mon Sep 17 00:00:00 2001 From: Roland Kuhn Date: Tue, 28 Dec 2010 23:50:08 +0100 Subject: [PATCH] Improve Duration classes - add more factories and implicits (for Double) - add extractors for deconstruction and string parsing - fix bug in infinite durations (comparisons were inverted) --- .../src/main/scala/akka/util/Duration.scala | 297 +++++++++++------- 1 file changed, 180 insertions(+), 117 deletions(-) diff --git a/akka-actor/src/main/scala/akka/util/Duration.scala b/akka-actor/src/main/scala/akka/util/Duration.scala index f694839fb3..d279249b30 100644 --- a/akka-actor/src/main/scala/akka/util/Duration.scala +++ b/akka-actor/src/main/scala/akka/util/Duration.scala @@ -5,16 +5,70 @@ package akka.util import java.util.concurrent.TimeUnit +import TimeUnit._ +import java.lang.{Long => JLong, Double => JDouble} object Duration { def apply(length: Long, unit: TimeUnit) : Duration = new FiniteDuration(length, unit) + def apply(length: Double, unit: TimeUnit) : Duration = new FiniteDuration(length, unit) def apply(length: Long, unit: String) : Duration = new FiniteDuration(length, timeUnit(unit)) + /** + * Construct a Duration by parsing a String. In case of a format error, a + * RuntimeException is thrown. See `unapply(String)` for more information. + */ + def apply(s : String) : Duration = unapply(s) getOrElse error("format error") + + /** + * Deconstruct a Duration into length and unit if it is finite. + */ + def unapply(d : Duration) : Option[(Long, TimeUnit)] = { + if (d.finite_?) { + Some((d.length, d.unit)) + } else { + None + } + } + + private val RE = ("""^\s*(\d+(?:\.\d+)?)\s*"""+ // length part + "(?:"+ // units are distinguished in separate match groups + "(h|hour|hours)|"+ + "(min|minute|minutes)|"+ + "(s|sec|second|seconds)|"+ + "(ms|milli|millis|millisecond|milliseconds)|"+ + "(µs|micro|micros|microsecond|microseconds)|"+ + "(ns|nano|nanos|nanosecond|nanoseconds)"+ + """)\s*$""").r // close the non-capturing group + private val REinf = """^\s*Inf\s*$""".r + private val REminf = """^\s*(?:-\s*|Minus)Inf\s*""".r + + /** + * Parse String, return None if no match. Format is `""`, where + * whitespace is allowed before, between and after the parts. Infinities are + * designated by `"Inf"` and `"-Inf"` or `"MinusInf"`. + */ + def unapply(s : String) : Option[Duration] = s match { + case RE(length, h, m, s, ms, mus, ns) => + if (h ne null) Some(Duration(3600 * JDouble.parseDouble(length), SECONDS)) else + if (m ne null) Some(Duration(60 * JDouble.parseDouble(length), SECONDS)) else + if (s ne null) Some(Duration(1 * JDouble.parseDouble(length), SECONDS)) else + if (ms ne null) Some(Duration(1 * JDouble.parseDouble(length), MILLISECONDS)) else + if (mus ne null) Some(Duration(1 * JDouble.parseDouble(length), MICROSECONDS)) else + if (ns ne null) Some(Duration(1 * JDouble.parseDouble(length), NANOSECONDS)) else + error("made some error in regex (should not be possible)") + case REinf() => Some(Inf) + case REminf() => Some(MinusInf) + case _ => None + } + + /** + * Parse TimeUnit from string representation. + */ def timeUnit(unit: String) = unit.toLowerCase match { - case "nanoseconds" | "nanos" | "nanosecond" | "nano" => TimeUnit.NANOSECONDS - case "microseconds" | "micros" | "microsecond" | "micro" => TimeUnit.MICROSECONDS - case "milliseconds" | "millis" | "millisecond" | "milli" => TimeUnit.MILLISECONDS - case _ => TimeUnit.SECONDS + case "nanoseconds" | "nanos" | "nanosecond" | "nano" => NANOSECONDS + case "microseconds" | "micros" | "microsecond" | "micro" => MICROSECONDS + case "milliseconds" | "millis" | "millisecond" | "milli" => MILLISECONDS + case _ => SECONDS } trait Infinite { @@ -29,30 +83,38 @@ object Duration { def finite_? = false - def length : Long = error("length not allowed on infinite Durations") - def unit : TimeUnit = error("unit not allowed on infinite Durations") - def toNanos : Long = error("toNanos not allowed on infinite Durations") - def toMicros : Long = error("toMicros not allowed on infinite Durations") - def toMillis : Long = error("toMillis not allowed on infinite Durations") - def toSeconds : Long = error("toSeconds not allowed on infinite Durations") + def length : Long = throw new IllegalArgumentException("length not allowed on infinite Durations") + def unit : TimeUnit = throw new IllegalArgumentException("unit not allowed on infinite Durations") + def toNanos : Long = throw new IllegalArgumentException("toNanos not allowed on infinite Durations") + def toMicros : Long = throw new IllegalArgumentException("toMicros not allowed on infinite Durations") + def toMillis : Long = throw new IllegalArgumentException("toMillis not allowed on infinite Durations") + def toSeconds : Long = throw new IllegalArgumentException("toSeconds not allowed on infinite Durations") } + /** + * Infinite duration: greater than any other and not equal to any other, + * including itself. + */ object Inf extends Duration with Infinite { override def toString = "Duration.Inf" - def >(other : Duration) = false - def >=(other : Duration) = false - def <(other : Duration) = true - def <=(other : Duration) = true - def unary_- : Duration = MinusInf - } - - object MinusInf extends Duration with Infinite { - override def toString = "Duration.MinusInf" def >(other : Duration) = true def >=(other : Duration) = true def <(other : Duration) = false def <=(other : Duration) = false def unary_- : Duration = MinusInf + } + + /** + * Infinite negative duration: lesser than any other and not equal to any other, + * including itself. + */ + object MinusInf extends Duration with Infinite { + override def toString = "Duration.MinusInf" + def >(other : Duration) = false + def >=(other : Duration) = false + def <(other : Duration) = true + def <=(other : Duration) = true + def unary_- : Duration = Inf } } @@ -66,7 +128,7 @@ object Duration { * import akka.util.Duration; * import java.util.concurrent.TimeUnit; * - * Duration duration = new Duration(100, TimeUnit.MILLISECONDS); + * Duration duration = new Duration(100, MILLISECONDS); * Duration duration = new Duration(5, "seconds"); * * duration.toNanos(); @@ -78,18 +140,28 @@ object Duration { * import akka.util.Duration * import java.util.concurrent.TimeUnit * - * val duration = Duration(100, TimeUnit.MILLISECONDS) + * val duration = Duration(100, MILLISECONDS) * val duration = Duration(100, "millis") * * duration.toNanos + * duration < 1.second + * duration <= Duration.Inf * * *

- * Implicits are also provided for Int and Long. Example usage: + * Implicits are also provided for Int, Long and Double. Example usage: *

  * import akka.util.duration._
  *
- * val duration = 100.millis
+ * val duration = 100 millis
+ * 
+ * + * Extractors, parsing and arithmetic are also included: + *
+ * val d = Duration("1.2 µs")
+ * val Duration(length, unit) = 5 millis
+ * val d2 = d * 2.5
+ * val d3 = d2 + 1.millisecond
  * 
*/ trait Duration { @@ -113,11 +185,26 @@ trait Duration { class FiniteDuration(val length: Long, val unit: TimeUnit) extends Duration { def this(length: Long, unit: String) = this(length, Duration.timeUnit(unit)) + def this(length: Double, unit: TimeUnit) = { + this(1, unit) + this * length + } + def toNanos = unit.toNanos(length) def toMicros = unit.toMicros(length) def toMillis = unit.toMillis(length) def toSeconds = unit.toSeconds(length) - override def toString = "Duration(" + length + ", " + unit + ")" + + override def toString = this match { + case Duration(1, SECONDS) => "1 second" + case Duration(x, SECONDS) => x+" seconds" + case Duration(1, MILLISECONDS) => "1 millisecond" + case Duration(x, MILLISECONDS) => x+" milliseconds" + case Duration(1, MICROSECONDS) => "1 microsecond" + case Duration(x, MICROSECONDS) => x+" microseconds" + case Duration(1, NANOSECONDS) => "1 nanosecond" + case Duration(x, NANOSECONDS) => x+" nanoseconds" + } def <(other : Duration) = { if (other.finite_?) { @@ -153,17 +240,17 @@ class FiniteDuration(val length: Long, val unit: TimeUnit) extends Duration { private def fromNanos(nanos : Long) : Duration = { if (nanos % 1000000000L == 0) { - Duration(nanos / 1000000000L, TimeUnit.SECONDS) + Duration(nanos / 1000000000L, SECONDS) } else if (nanos % 1000000L == 0) { - Duration(nanos / 1000000L, TimeUnit.MILLISECONDS) + Duration(nanos / 1000000L, MILLISECONDS) } else if (nanos % 1000L == 0) { - Duration(nanos / 1000L, TimeUnit.MICROSECONDS) + Duration(nanos / 1000L, MICROSECONDS) } else { - Duration(nanos, TimeUnit.NANOSECONDS) + Duration(nanos, NANOSECONDS) } } - private def fromNanos(nanos : Double) : Duration = fromNanos(nanos.asInstanceOf[Long]) + private def fromNanos(nanos : Double) : Duration = fromNanos((nanos + 0.5).asInstanceOf[Long]) def +(other : Duration) = { if (!other.finite_?) { @@ -198,61 +285,11 @@ class FiniteDuration(val length: Long, val unit: TimeUnit) extends Duration { override def hashCode = toNanos.asInstanceOf[Int] } -object Inf extends Duration { - override def toString = "Duration.Inf" - override def equals(other : Any) = false - - def >(other : Duration) = true - def >=(other : Duration) = true - def <(other : Duration) = false - def <=(other : Duration) = false - - def +(other : Duration) : Duration = this - def -(other : Duration) : Duration = this - def *(other : Double) : Duration = this - def /(other : Double) : Duration = this - - def unary_- : Duration = MinusInf - - def finite_? = false - - def length : Long = error("length not allowed on Inf") - def unit : TimeUnit = error("unit not allowed on Inf") - def toNanos : Long = error("toNanos not allowed on Inf") - def toMicros : Long = error("toMicros not allowed on Inf") - def toMillis : Long = error("toMillis not allowed on Inf") - def toSeconds : Long = error("toSeconds not allowed on Inf") -} - -object MinusInf extends Duration { - override def toString = "Duration.MinusInf" - override def equals(other : Any) = false - - def >(other : Duration) = false - def >=(other : Duration) = false - def <(other : Duration) = true - def <=(other : Duration) = true - - def +(other : Duration) : Duration = this - def -(other : Duration) : Duration = this - def *(other : Double) : Duration = this - def /(other : Double) : Duration = this - - def unary_- : Duration = Inf - - def finite_? = false - - def length : Long = error("length not allowed on MinusInf") - def unit : TimeUnit = error("unit not allowed on MinusInf") - def toNanos : Long = error("toNanos not allowed on MinusInf") - def toMicros : Long = error("toMicros not allowed on MinusInf") - def toMillis : Long = error("toMillis not allowed on MinusInf") - def toSeconds : Long = error("toSeconds not allowed on MinusInf") -} - package object duration { implicit def intToDurationInt(n: Int) = new DurationInt(n) implicit def longToDurationLong(n: Long) = new DurationLong(n) + implicit def doubleToDurationDouble(d: Double) = new DurationDouble(d) + implicit def pairIntToDuration(p : (Int, TimeUnit)) = Duration(p._1, p._2) implicit def pairLongToDuration(p : (Long, TimeUnit)) = Duration(p._1, p._2) implicit def durationToPair(d : Duration) = (d.length, d.unit) @@ -266,53 +303,79 @@ package object duration { } class DurationInt(n: Int) { - def nanoseconds = Duration(n, TimeUnit.NANOSECONDS) - def nanos = Duration(n, TimeUnit.NANOSECONDS) - def nanosecond = Duration(n, TimeUnit.NANOSECONDS) - def nano = Duration(n, TimeUnit.NANOSECONDS) + def nanoseconds = Duration(n, NANOSECONDS) + def nanos = Duration(n, NANOSECONDS) + def nanosecond = Duration(n, NANOSECONDS) + def nano = Duration(n, NANOSECONDS) - def microseconds = Duration(n, TimeUnit.MICROSECONDS) - def micros = Duration(n, TimeUnit.MICROSECONDS) - def microsecond = Duration(n, TimeUnit.MICROSECONDS) - def micro = Duration(n, TimeUnit.MICROSECONDS) + def microseconds = Duration(n, MICROSECONDS) + def micros = Duration(n, MICROSECONDS) + def microsecond = Duration(n, MICROSECONDS) + def micro = Duration(n, MICROSECONDS) - def milliseconds = Duration(n, TimeUnit.MILLISECONDS) - def millis = Duration(n, TimeUnit.MILLISECONDS) - def millisecond = Duration(n, TimeUnit.MILLISECONDS) - def milli = Duration(n, TimeUnit.MILLISECONDS) + def milliseconds = Duration(n, MILLISECONDS) + def millis = Duration(n, MILLISECONDS) + def millisecond = Duration(n, MILLISECONDS) + def milli = Duration(n, MILLISECONDS) - def seconds = Duration(n, TimeUnit.SECONDS) - def second = Duration(n, TimeUnit.SECONDS) + def seconds = Duration(n, SECONDS) + def second = Duration(n, SECONDS) - def minutes = Duration(60 * n, TimeUnit.SECONDS) - def minute = Duration(60 * n, TimeUnit.SECONDS) + def minutes = Duration(60 * n, SECONDS) + def minute = Duration(60 * n, SECONDS) - def hours = Duration(3600 * n, TimeUnit.SECONDS) - def hour = Duration(3600 * n, TimeUnit.SECONDS) + def hours = Duration(3600 * n, SECONDS) + def hour = Duration(3600 * n, SECONDS) } class DurationLong(n: Long) { - def nanoseconds = Duration(n, TimeUnit.NANOSECONDS) - def nanos = Duration(n, TimeUnit.NANOSECONDS) - def nanosecond = Duration(n, TimeUnit.NANOSECONDS) - def nano = Duration(n, TimeUnit.NANOSECONDS) + def nanoseconds = Duration(n, NANOSECONDS) + def nanos = Duration(n, NANOSECONDS) + def nanosecond = Duration(n, NANOSECONDS) + def nano = Duration(n, NANOSECONDS) - def microseconds = Duration(n, TimeUnit.MICROSECONDS) - def micros = Duration(n, TimeUnit.MICROSECONDS) - def microsecond = Duration(n, TimeUnit.MICROSECONDS) - def micro = Duration(n, TimeUnit.MICROSECONDS) + def microseconds = Duration(n, MICROSECONDS) + def micros = Duration(n, MICROSECONDS) + def microsecond = Duration(n, MICROSECONDS) + def micro = Duration(n, MICROSECONDS) - def milliseconds = Duration(n, TimeUnit.MILLISECONDS) - def millis = Duration(n, TimeUnit.MILLISECONDS) - def millisecond = Duration(n, TimeUnit.MILLISECONDS) - def milli = Duration(n, TimeUnit.MILLISECONDS) + def milliseconds = Duration(n, MILLISECONDS) + def millis = Duration(n, MILLISECONDS) + def millisecond = Duration(n, MILLISECONDS) + def milli = Duration(n, MILLISECONDS) - def seconds = Duration(n, TimeUnit.SECONDS) - def second = Duration(n, TimeUnit.SECONDS) + def seconds = Duration(n, SECONDS) + def second = Duration(n, SECONDS) - def minutes = Duration(60 * n, TimeUnit.SECONDS) - def minute = Duration(60 * n, TimeUnit.SECONDS) + def minutes = Duration(60 * n, SECONDS) + def minute = Duration(60 * n, SECONDS) - def hours = Duration(3600 * n, TimeUnit.SECONDS) - def hour = Duration(3600 * n, TimeUnit.SECONDS) + def hours = Duration(3600 * n, SECONDS) + def hour = Duration(3600 * n, SECONDS) +} + +class DurationDouble(d: Double) { + def nanoseconds = Duration(d, NANOSECONDS) + def nanos = Duration(d, NANOSECONDS) + def nanosecond = Duration(d, NANOSECONDS) + def nano = Duration(d, NANOSECONDS) + + def microseconds = Duration(d, MICROSECONDS) + def micros = Duration(d, MICROSECONDS) + def microsecond = Duration(d, MICROSECONDS) + def micro = Duration(d, MICROSECONDS) + + def milliseconds = Duration(d, MILLISECONDS) + def millis = Duration(d, MILLISECONDS) + def millisecond = Duration(d, MILLISECONDS) + def milli = Duration(d, MILLISECONDS) + + def seconds = Duration(d, SECONDS) + def second = Duration(d, SECONDS) + + def minutes = Duration(60 * d, SECONDS) + def minute = Duration(60 * d, SECONDS) + + def hours = Duration(3600 * d, SECONDS) + def hour = Duration(3600 * d, SECONDS) }