Skip to content

Commit b9813cb

Browse files
committed
Test coverage using API from Java, entry in README, more correct overflow handling
1 parent 855ff24 commit b9813cb

File tree

4 files changed

+95
-30
lines changed

4 files changed

+95
-30
lines changed

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,34 @@ public class StreamConvertersExample {
253253
}
254254
}
255255
```
256+
257+
## Converters between `scala.concurrent.duration.FiniteDuration` and `java.time.Duration`
258+
259+
Interconversion between Java's standard `java.time.Duration` type
260+
and the `scala.concurrent.duration.FiniteDuration` types. The Java `Duration` does
261+
not contain a time unit, so when converting from `FiniteDuration` the time unit used
262+
to create it is lost.
263+
264+
For the opposite conversion a `Duration` can potentially express a larger time span than
265+
a `FiniteDuration`, for such cases an exception is thrown.
266+
267+
Example of conversions from the Java type ways:
268+
269+
```scala
270+
import scala.concurrent.duration._
271+
import scala.compat.java8.DurationConverters
272+
273+
val javaDuration: java.time.Duration = 5.seconds.toJava
274+
val finiteDuration: FiniteDuration = javaDuration.toScala
275+
```
276+
277+
From Java:
278+
```java
279+
import scala.compat.java8.DurationConverters;
280+
import scala.concurrent.duration.FiniteDuration;
281+
282+
DurationConverters.toScala(Duration.of(5, ChronoUnit.SECONDS));
283+
DurationConverters.toJava(FiniteDuration.create(5, TimeUnit.SECONDS));
284+
```
285+
286+

src/main/scala/scala/compat/java8/DurationConverters.scala

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,33 @@ object DurationConverters {
2323
* unit of nanoseconds.
2424
*
2525
* @throws IllegalArgumentException If the given Java Duration is out of bounds of what can be expressed with the
26-
* Scala Durations.
26+
* Scala FiniteDuration.
2727
*/
28-
final def toScala(duration: java.time.Duration): scala.concurrent.duration.Duration = {
29-
if (duration.getNano == 0) {
30-
if (duration.getSeconds == 0) ScalaDuration.Zero
31-
else FiniteDuration(duration.getSeconds, TimeUnit.SECONDS)
28+
final def toScala(duration: java.time.Duration): scala.concurrent.duration.FiniteDuration = {
29+
val originalSeconds = duration.getSeconds
30+
val originalNanos = duration.getNano
31+
if (originalNanos == 0) {
32+
if (originalSeconds == 0) ScalaDuration.Zero
33+
else FiniteDuration(originalSeconds, TimeUnit.SECONDS)
34+
} else if (originalSeconds == 0) {
35+
FiniteDuration(originalNanos, TimeUnit.NANOSECONDS)
3236
} else {
33-
FiniteDuration(
34-
duration.getSeconds * 1000000000 + duration.getNano,
35-
TimeUnit.NANOSECONDS
36-
)
37+
try {
38+
val secondsAsNanos = Math.multiplyExact(originalSeconds, 1000000000)
39+
val totalNanos = secondsAsNanos + originalNanos
40+
if ((totalNanos < 0 && secondsAsNanos < 0) || (totalNanos > 0 && secondsAsNanos > 0)) FiniteDuration(totalNanos, TimeUnit.NANOSECONDS)
41+
else throw new ArithmeticException()
42+
} catch {
43+
case _: ArithmeticException => throw new IllegalArgumentException(s"Java duration $duration cannot be expressed as a Scala duration")
44+
}
3745
}
3846
}
3947

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

65+
implicit final class DurationOps(val duration: java.time.Duration) extends AnyVal {
66+
/**
67+
* See [[DurationConverters.toScala]]
68+
*/
69+
def toScala: scala.concurrent.duration.FiniteDuration = DurationConverters.toScala(duration)
70+
}
71+
72+
implicit final class FiniteDurationops(val duration: scala.concurrent.duration.FiniteDuration) extends AnyVal {
73+
/**
74+
* See [[DurationConverters.toJava]]
75+
*/
76+
def toJava: java.time.Duration = DurationConverters.toJava(duration)
77+
}
78+
6179
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (C) 2012-2018 Lightbend Inc. <http://www.lightbend.com>
3+
*/
4+
package scala.compat.java8;
5+
6+
import org.junit.Test;
7+
import scala.concurrent.duration.FiniteDuration;
8+
import scala.runtime.java8.*;
9+
10+
import java.time.Duration;
11+
import java.time.temporal.ChronoUnit;
12+
import java.util.concurrent.TimeUnit;
13+
14+
public class DurationConvertersJavaTest {
15+
16+
@Test
17+
public void apiAccessibleFromJava() {
18+
DurationConverters.toScala(Duration.of(5, ChronoUnit.SECONDS));
19+
DurationConverters.toJava(FiniteDuration.create(5, TimeUnit.SECONDS));
20+
}
21+
22+
}

src/test/scala/scala/compat/java8/DurationConvertersTest.scala

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
*/
44
package scala.compat.java8
55

6-
import org.junit.Test
7-
import org.junit.Assert._
86
import java.time.{Duration => JavaDuration}
97

8+
import org.junit.Assert._
9+
import org.junit.Test
10+
1011
import scala.util.Try
1112

1213
class DurationConvertersTest {
@@ -25,7 +26,7 @@ class DurationConvertersTest {
2526
1000000001L -> (1,1),
2627
Long.MaxValue -> (9223372036L, 854775807)
2728
).foreach { case (n, (expSecs, expNanos)) =>
28-
val result = toJava(n.nanos)
29+
val result = n.nanos.toJava
2930
assertEquals(s"toJava($n nanos) -> $expSecs s)", expSecs, result.getSeconds)
3031
assertEquals(s"toJava($n nanos) -> $expNanos n)", expNanos, result.getNano)
3132
}
@@ -40,7 +41,7 @@ class DurationConvertersTest {
4041
1L -> (0L, 1000000),
4142
9223372036854L -> (9223372036L, 854000000)
4243
).foreach { case (n, (expSecs, expNanos)) =>
43-
val result = toJava(n.millis)
44+
val result = n.millis.toJava
4445
assertEquals(s"toJava($n millis) -> $expSecs s)", expSecs, result.getSeconds)
4546
assertEquals(s"toJava($n millis) -> $expNanos n)", expNanos, result.getNano)
4647
}
@@ -55,7 +56,7 @@ class DurationConvertersTest {
5556
1L -> (0L, 1000),
5657
9223372036854775L -> (9223372036L, 854775000)
5758
).foreach { case (n, (expSecs, expNanos)) =>
58-
val result = toJava(n.micros)
59+
val result = n.micros.toJava
5960
assertEquals(s"toJava($n micros) -> $expSecs s)", expSecs, result.getSeconds)
6061
assertEquals(s"toJava($n micros) -> $expNanos n)", expNanos, result.getNano)
6162
}
@@ -70,7 +71,7 @@ class DurationConvertersTest {
7071
1L -> (1L, 0),
7172
9223372036L -> (9223372036L, 0)
7273
).foreach { case (n, (expSecs, expNanos)) =>
73-
val result = toJava(n.seconds)
74+
val result = n.seconds.toJava
7475
assertEquals(expSecs, result.getSeconds)
7576
assertEquals(expNanos, result.getNano)
7677
}
@@ -87,19 +88,12 @@ class DurationConvertersTest {
8788

8889
@Test
8990
def javaNanosPartToScalaDuration(): Unit = {
90-
Seq[Long](Long.MinValue + 1L, -1L, 0L, 1L, Long.MaxValue).foreach { n =>
91+
val nanosPerSecond = 1000000000L
92+
Seq[Long](-nanosPerSecond - 1L, 0L, 1L, nanosPerSecond - 1L).foreach { n =>
9193
assertEquals(n, toScala(JavaDuration.ofNanos(n)).toNanos)
9294
}
9395
}
9496

95-
@Test
96-
def unsupportedScalaDurationThrows(): Unit = {
97-
Seq(Duration.Inf, Duration.MinusInf, Duration.Undefined).foreach { d =>
98-
val res = Try { toJava(d) }
99-
assertTrue(s"Expected exception for $d but got success", res.isFailure)
100-
}
101-
}
102-
10397
@Test
10498
def unsupportedJavaDurationThrows(): Unit = {
10599
Seq(JavaDuration.ofSeconds(-9223372037L), JavaDuration.ofSeconds(9223372037L)).foreach { d =>

0 commit comments

Comments
 (0)