Skip to content

Commit

Permalink
Test coverage using API from Java, entry in README, more correct over…
Browse files Browse the repository at this point in the history
…flow handling
  • Loading branch information
johanandren committed Apr 30, 2018
1 parent 855ff24 commit b9813cb
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 30 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,34 @@ public class StreamConvertersExample {
}
}
```

## Converters between `scala.concurrent.duration.FiniteDuration` and `java.time.Duration`

Interconversion between Java's standard `java.time.Duration` type
and the `scala.concurrent.duration.FiniteDuration` types. The Java `Duration` does
not contain a time unit, so when converting from `FiniteDuration` the time unit used
to create it is lost.

For the opposite conversion a `Duration` can potentially express a larger time span than
a `FiniteDuration`, for such cases an exception is thrown.

Example of conversions from the Java type ways:

```scala
import scala.concurrent.duration._
import scala.compat.java8.DurationConverters

val javaDuration: java.time.Duration = 5.seconds.toJava
val finiteDuration: FiniteDuration = javaDuration.toScala
```

From Java:
```java
import scala.compat.java8.DurationConverters;
import scala.concurrent.duration.FiniteDuration;

DurationConverters.toScala(Duration.of(5, ChronoUnit.SECONDS));
DurationConverters.toJava(FiniteDuration.create(5, TimeUnit.SECONDS));
```


48 changes: 33 additions & 15 deletions src/main/scala/scala/compat/java8/DurationConverters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,33 @@ object DurationConverters {
* unit of nanoseconds.
*
* @throws IllegalArgumentException If the given Java Duration is out of bounds of what can be expressed with the
* Scala Durations.
* Scala FiniteDuration.
*/
final def toScala(duration: java.time.Duration): scala.concurrent.duration.Duration = {
if (duration.getNano == 0) {
if (duration.getSeconds == 0) ScalaDuration.Zero
else FiniteDuration(duration.getSeconds, TimeUnit.SECONDS)
final def toScala(duration: java.time.Duration): scala.concurrent.duration.FiniteDuration = {
val originalSeconds = duration.getSeconds
val originalNanos = duration.getNano
if (originalNanos == 0) {
if (originalSeconds == 0) ScalaDuration.Zero
else FiniteDuration(originalSeconds, TimeUnit.SECONDS)
} else if (originalSeconds == 0) {
FiniteDuration(originalNanos, TimeUnit.NANOSECONDS)
} else {
FiniteDuration(
duration.getSeconds * 1000000000 + duration.getNano,
TimeUnit.NANOSECONDS
)
try {
val secondsAsNanos = Math.multiplyExact(originalSeconds, 1000000000)
val totalNanos = secondsAsNanos + originalNanos
if ((totalNanos < 0 && secondsAsNanos < 0) || (totalNanos > 0 && secondsAsNanos > 0)) FiniteDuration(totalNanos, TimeUnit.NANOSECONDS)
else throw new ArithmeticException()
} catch {
case _: ArithmeticException => throw new IllegalArgumentException(s"Java duration $duration cannot be expressed as a Scala duration")
}
}
}

/**
* Transform a Scala duration into a Java duration. Note that the Scala duration keeps the time unit it was created
* Transform a Scala FiniteDuration into a Java duration. Note that the Scala duration keeps the time unit it was created
* with while a Java duration always is a pair of seconds and nanos, so the unit it lost.
*
* @throws IllegalArgumentException If the Scala duration express a amount of time for the time unit that
* a Java Duration can not express (infinite durations and undefined durations)
*/
final def toJava(duration: scala.concurrent.duration.Duration): java.time.Duration = {
require(duration.isFinite(), s"Got [$duration] but only finite Scala durations can be expressed as a Java Durations")
final def toJava(duration: scala.concurrent.duration.FiniteDuration): java.time.Duration = {
if (duration.length == 0) JavaDuration.ZERO
else duration.unit match {
case TimeUnit.NANOSECONDS => JavaDuration.ofNanos(duration.length)
Expand All @@ -58,4 +62,18 @@ object DurationConverters {
}
}

implicit final class DurationOps(val duration: java.time.Duration) extends AnyVal {
/**
* See [[DurationConverters.toScala]]
*/
def toScala: scala.concurrent.duration.FiniteDuration = DurationConverters.toScala(duration)
}

implicit final class FiniteDurationops(val duration: scala.concurrent.duration.FiniteDuration) extends AnyVal {
/**
* See [[DurationConverters.toJava]]
*/
def toJava: java.time.Duration = DurationConverters.toJava(duration)
}

}
22 changes: 22 additions & 0 deletions src/test/java/scala/compat/java8/DurationConvertersJavaTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (C) 2012-2018 Lightbend Inc. <http://www.lightbend.com>
*/
package scala.compat.java8;

import org.junit.Test;
import scala.concurrent.duration.FiniteDuration;
import scala.runtime.java8.*;

import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

public class DurationConvertersJavaTest {

@Test
public void apiAccessibleFromJava() {
DurationConverters.toScala(Duration.of(5, ChronoUnit.SECONDS));
DurationConverters.toJava(FiniteDuration.create(5, TimeUnit.SECONDS));
}

}
24 changes: 9 additions & 15 deletions src/test/scala/scala/compat/java8/DurationConvertersTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
*/
package scala.compat.java8

import org.junit.Test
import org.junit.Assert._
import java.time.{Duration => JavaDuration}

import org.junit.Assert._
import org.junit.Test

import scala.util.Try

class DurationConvertersTest {
Expand All @@ -25,7 +26,7 @@ class DurationConvertersTest {
1000000001L -> (1,1),
Long.MaxValue -> (9223372036L, 854775807)
).foreach { case (n, (expSecs, expNanos)) =>
val result = toJava(n.nanos)
val result = n.nanos.toJava
assertEquals(s"toJava($n nanos) -> $expSecs s)", expSecs, result.getSeconds)
assertEquals(s"toJava($n nanos) -> $expNanos n)", expNanos, result.getNano)
}
Expand All @@ -40,7 +41,7 @@ class DurationConvertersTest {
1L -> (0L, 1000000),
9223372036854L -> (9223372036L, 854000000)
).foreach { case (n, (expSecs, expNanos)) =>
val result = toJava(n.millis)
val result = n.millis.toJava
assertEquals(s"toJava($n millis) -> $expSecs s)", expSecs, result.getSeconds)
assertEquals(s"toJava($n millis) -> $expNanos n)", expNanos, result.getNano)
}
Expand All @@ -55,7 +56,7 @@ class DurationConvertersTest {
1L -> (0L, 1000),
9223372036854775L -> (9223372036L, 854775000)
).foreach { case (n, (expSecs, expNanos)) =>
val result = toJava(n.micros)
val result = n.micros.toJava
assertEquals(s"toJava($n micros) -> $expSecs s)", expSecs, result.getSeconds)
assertEquals(s"toJava($n micros) -> $expNanos n)", expNanos, result.getNano)
}
Expand All @@ -70,7 +71,7 @@ class DurationConvertersTest {
1L -> (1L, 0),
9223372036L -> (9223372036L, 0)
).foreach { case (n, (expSecs, expNanos)) =>
val result = toJava(n.seconds)
val result = n.seconds.toJava
assertEquals(expSecs, result.getSeconds)
assertEquals(expNanos, result.getNano)
}
Expand All @@ -87,19 +88,12 @@ class DurationConvertersTest {

@Test
def javaNanosPartToScalaDuration(): Unit = {
Seq[Long](Long.MinValue + 1L, -1L, 0L, 1L, Long.MaxValue).foreach { n =>
val nanosPerSecond = 1000000000L
Seq[Long](-nanosPerSecond - 1L, 0L, 1L, nanosPerSecond - 1L).foreach { n =>
assertEquals(n, toScala(JavaDuration.ofNanos(n)).toNanos)
}
}

@Test
def unsupportedScalaDurationThrows(): Unit = {
Seq(Duration.Inf, Duration.MinusInf, Duration.Undefined).foreach { d =>
val res = Try { toJava(d) }
assertTrue(s"Expected exception for $d but got success", res.isFailure)
}
}

@Test
def unsupportedJavaDurationThrows(): Unit = {
Seq(JavaDuration.ofSeconds(-9223372037L), JavaDuration.ofSeconds(9223372037L)).foreach { d =>
Expand Down

0 comments on commit b9813cb

Please sign in to comment.