# HG changeset patch # User dl # Date 1528483120 25200 # Node ID cb0efe0cc20ef0db0593d87f016dbfb7e61af7ee # Parent 30d5bca69eae7b4ddd46d1434cff593a8fe0b916 8204375: Add TimeUnit#convert(Duration) Reviewed-by: martin, scolebourne, plevart, rriggs diff -r 30d5bca69eae -r cb0efe0cc20e src/java.base/share/classes/java/util/concurrent/TimeUnit.java --- a/src/java.base/share/classes/java/util/concurrent/TimeUnit.java Fri Jun 08 11:04:58 2018 -0700 +++ b/src/java.base/share/classes/java/util/concurrent/TimeUnit.java Fri Jun 08 11:38:40 2018 -0700 @@ -35,6 +35,7 @@ package java.util.concurrent; +import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.Objects; @@ -192,6 +193,50 @@ } /** + * Converts the given time duration to this unit. + * + *

For any TimeUnit {@code unit}, + * {@code unit.convert(Duration.ofNanos(n))} + * is equivalent to + * {@code unit.convert(n, NANOSECONDS)}, and + * {@code unit.convert(Duration.of(n, unit.toChronoUnit()))} + * is equivalent to {@code n} (in the absence of overflow). + * + * @param duration the time duration + * @return the converted duration in this unit, + * or {@code Long.MIN_VALUE} if conversion would negatively overflow, + * or {@code Long.MAX_VALUE} if it would positively overflow. + * @throws NullPointerException if {@code duration} is null + * @see Duration#of(long,TemporalUnit) + * @since 11 + */ + public long convert(Duration duration) { + long secs = duration.getSeconds(); + int nano = duration.getNano(); + if (secs < 0 && nano > 0) { + // use representation compatible with integer division + secs++; + nano -= SECOND_SCALE; + } + final long s, nanoVal; + // Optimize for the common case - NANOSECONDS without overflow + if (this == NANOSECONDS) + nanoVal = nano; + else if ((s = scale) < SECOND_SCALE) + nanoVal = nano / s; + else if (this == SECONDS) + return secs; + else + return secs / secRatio; + long val = secs * secRatio + nanoVal; + return ((secs < maxSecs && secs > -maxSecs) || + (secs == maxSecs && val > 0) || + (secs == -maxSecs && val < 0)) + ? val + : (secs > 0) ? Long.MAX_VALUE : Long.MIN_VALUE; + } + + /** * Equivalent to * {@link #convert(long, TimeUnit) NANOSECONDS.convert(duration, this)}. * @param duration the duration @@ -221,10 +266,8 @@ */ public long toMicros(long duration) { long s, m; - if ((s = scale) == MICRO_SCALE) - return duration; - else if (s < MICRO_SCALE) - return duration / microRatio; + if ((s = scale) <= MICRO_SCALE) + return (s == MICRO_SCALE) ? duration : duration / microRatio; else if (duration > (m = maxMicros)) return Long.MAX_VALUE; else if (duration < -m) @@ -243,10 +286,8 @@ */ public long toMillis(long duration) { long s, m; - if ((s = scale) == MILLI_SCALE) - return duration; - else if (s < MILLI_SCALE) - return duration / milliRatio; + if ((s = scale) <= MILLI_SCALE) + return (s == MILLI_SCALE) ? duration : duration / milliRatio; else if (duration > (m = maxMillis)) return Long.MAX_VALUE; else if (duration < -m) @@ -265,10 +306,8 @@ */ public long toSeconds(long duration) { long s, m; - if ((s = scale) == SECOND_SCALE) - return duration; - else if (s < SECOND_SCALE) - return duration / secRatio; + if ((s = scale) <= SECOND_SCALE) + return (s == SECOND_SCALE) ? duration : duration / secRatio; else if (duration > (m = maxSecs)) return Long.MAX_VALUE; else if (duration < -m) diff -r 30d5bca69eae -r cb0efe0cc20e test/jdk/java/util/concurrent/tck/TimeUnit8Test.java --- a/test/jdk/java/util/concurrent/tck/TimeUnit8Test.java Fri Jun 08 11:04:58 2018 -0700 +++ b/test/jdk/java/util/concurrent/tck/TimeUnit8Test.java Fri Jun 08 11:38:40 2018 -0700 @@ -40,8 +40,12 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import java.time.Duration; import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; +import java.util.stream.LongStream; import junit.framework.Test; import junit.framework.TestSuite; @@ -100,4 +104,85 @@ } } + /** + * convert(Duration) roundtrips with Duration.ofXXXX and Duration.of(long, ChronoUnit) + */ + public void testConvertDuration_roundtripDurationOf() { + long n = ThreadLocalRandom.current().nextLong(); + + assertEquals(n, NANOSECONDS.convert(Duration.ofNanos(n))); + assertEquals(n, NANOSECONDS.convert(Duration.of(n, ChronoUnit.NANOS))); + assertEquals(n, MILLISECONDS.convert(Duration.ofMillis(n))); + assertEquals(n, MILLISECONDS.convert(Duration.of(n, ChronoUnit.MILLIS))); + assertEquals(n, SECONDS.convert(Duration.ofSeconds(n))); + assertEquals(n, SECONDS.convert(Duration.of(n, ChronoUnit.SECONDS))); + n /= 60; + assertEquals(n, MINUTES.convert(Duration.ofMinutes(n))); + assertEquals(n, MINUTES.convert(Duration.of(n, ChronoUnit.MINUTES))); + n /= 60; + assertEquals(n, HOURS.convert(Duration.ofHours(n))); + assertEquals(n, HOURS.convert(Duration.of(n, ChronoUnit.HOURS))); + n /= 24; + assertEquals(n, DAYS.convert(Duration.ofDays(n))); + assertEquals(n, DAYS.convert(Duration.of(n, ChronoUnit.DAYS))); + } + + /** + * convert(Duration.ofNanos(n)) agrees with convert(n, NANOSECONDS) + */ + public void testConvertDuration_roundtripDurationOfNanos() { + // Test values near unit transitions and near overflow. + LongStream.concat( + Arrays.stream(TimeUnit.values()).mapToLong(u -> u.toNanos(1)), + LongStream.of(Long.MAX_VALUE, Long.MIN_VALUE)) + .flatMap(n -> LongStream.of(n, n + 1, n - 1)) + .flatMap(n -> LongStream.of(n, n + 1_000_000_000, n - 1_000_000_000)) + .flatMap(n -> LongStream.of(n, -n)) + // .peek(System.err::println) + .forEach(n -> Arrays.stream(TimeUnit.values()).forEach( + u -> assertEquals(u.convert(n, NANOSECONDS), + u.convert(Duration.ofNanos(n))))); + } + + /** + * convert(Duration) doesn't misbehave near Long.MAX_VALUE and Long.MIN_VALUE. + */ + public void testConvertDuration_nearOverflow() { + ChronoUnit NANOS = ChronoUnit.NANOS; + Duration maxDuration = Duration.ofSeconds(Long.MAX_VALUE, 999_999_999); + Duration minDuration = Duration.ofSeconds(Long.MIN_VALUE, 0); + + for (TimeUnit u : TimeUnit.values()) { + ChronoUnit cu = u.toChronoUnit(); + long r; + if (u.toNanos(1) > SECONDS.toNanos(1)) { + r = u.toNanos(1) / SECONDS.toNanos(1); + + assertThrows(ArithmeticException.class, + () -> Duration.of(Long.MAX_VALUE, cu), + () -> Duration.of(Long.MIN_VALUE, cu)); + } else { + r = 1; + + Duration max = Duration.of(Long.MAX_VALUE, cu); + Duration min = Duration.of(Long.MIN_VALUE, cu); + assertEquals(Long.MAX_VALUE, u.convert(max)); + assertEquals(Long.MAX_VALUE - 1, u.convert(max.minus(1, NANOS))); + assertEquals(Long.MAX_VALUE - 1, u.convert(max.minus(1, cu))); + assertEquals(Long.MIN_VALUE, u.convert(min)); + assertEquals(Long.MIN_VALUE + 1, u.convert(min.plus(1, NANOS))); + assertEquals(Long.MIN_VALUE + 1, u.convert(min.plus(1, cu))); + assertEquals(Long.MAX_VALUE, u.convert(max.plus(1, NANOS))); + if (u != SECONDS) { + assertEquals(Long.MAX_VALUE, u.convert(max.plus(1, cu))); + assertEquals(Long.MIN_VALUE, u.convert(min.minus(1, NANOS))); + assertEquals(Long.MIN_VALUE, u.convert(min.minus(1, cu))); + } + } + + assertEquals(Long.MAX_VALUE / r, u.convert(maxDuration)); + assertEquals(Long.MIN_VALUE / r, u.convert(minDuration)); + } + } + }