8152083: Optimize TimeUnit conversions
authordl
Mon, 28 Mar 2016 08:53:39 -0700
changeset 36732 7e6686d3f98a
parent 36731 e4ad29a03b76
child 36733 2bc03c4db607
8152083: Optimize TimeUnit conversions Reviewed-by: martin, shade, forax
jdk/src/java.base/share/classes/java/util/concurrent/TimeUnit.java
jdk/test/java/util/concurrent/tck/TimeUnit8Test.java
jdk/test/java/util/concurrent/tck/TimeUnitTest.java
--- a/jdk/src/java.base/share/classes/java/util/concurrent/TimeUnit.java	Mon Mar 28 17:12:48 2016 +0300
+++ b/jdk/src/java.base/share/classes/java/util/concurrent/TimeUnit.java	Mon Mar 28 08:53:39 2016 -0700
@@ -75,137 +75,94 @@
     /**
      * Time unit representing one thousandth of a microsecond.
      */
-    NANOSECONDS {
-        public long toNanos(long d)   { return d; }
-        public long toMicros(long d)  { return d/(C1/C0); }
-        public long toMillis(long d)  { return d/(C2/C0); }
-        public long toSeconds(long d) { return d/(C3/C0); }
-        public long toMinutes(long d) { return d/(C4/C0); }
-        public long toHours(long d)   { return d/(C5/C0); }
-        public long toDays(long d)    { return d/(C6/C0); }
-        public long convert(long d, TimeUnit u) { return u.toNanos(d); }
-        int excessNanos(long d, long m) { return (int)(d - (m*C2)); }
-    },
-
+    NANOSECONDS(TimeUnit.NANO_SCALE),
     /**
      * Time unit representing one thousandth of a millisecond.
      */
-    MICROSECONDS {
-        public long toNanos(long d)   { return x(d, C1/C0, MAX/(C1/C0)); }
-        public long toMicros(long d)  { return d; }
-        public long toMillis(long d)  { return d/(C2/C1); }
-        public long toSeconds(long d) { return d/(C3/C1); }
-        public long toMinutes(long d) { return d/(C4/C1); }
-        public long toHours(long d)   { return d/(C5/C1); }
-        public long toDays(long d)    { return d/(C6/C1); }
-        public long convert(long d, TimeUnit u) { return u.toMicros(d); }
-        int excessNanos(long d, long m) { return (int)((d*C1) - (m*C2)); }
-    },
-
+    MICROSECONDS(TimeUnit.MICRO_SCALE),
     /**
      * Time unit representing one thousandth of a second.
      */
-    MILLISECONDS {
-        public long toNanos(long d)   { return x(d, C2/C0, MAX/(C2/C0)); }
-        public long toMicros(long d)  { return x(d, C2/C1, MAX/(C2/C1)); }
-        public long toMillis(long d)  { return d; }
-        public long toSeconds(long d) { return d/(C3/C2); }
-        public long toMinutes(long d) { return d/(C4/C2); }
-        public long toHours(long d)   { return d/(C5/C2); }
-        public long toDays(long d)    { return d/(C6/C2); }
-        public long convert(long d, TimeUnit u) { return u.toMillis(d); }
-        int excessNanos(long d, long m) { return 0; }
-    },
-
+    MILLISECONDS(TimeUnit.MILLI_SCALE),
     /**
      * Time unit representing one second.
      */
-    SECONDS {
-        public long toNanos(long d)   { return x(d, C3/C0, MAX/(C3/C0)); }
-        public long toMicros(long d)  { return x(d, C3/C1, MAX/(C3/C1)); }
-        public long toMillis(long d)  { return x(d, C3/C2, MAX/(C3/C2)); }
-        public long toSeconds(long d) { return d; }
-        public long toMinutes(long d) { return d/(C4/C3); }
-        public long toHours(long d)   { return d/(C5/C3); }
-        public long toDays(long d)    { return d/(C6/C3); }
-        public long convert(long d, TimeUnit u) { return u.toSeconds(d); }
-        int excessNanos(long d, long m) { return 0; }
-    },
-
+    SECONDS(TimeUnit.SECOND_SCALE),
     /**
      * Time unit representing sixty seconds.
      * @since 1.6
      */
-    MINUTES {
-        public long toNanos(long d)   { return x(d, C4/C0, MAX/(C4/C0)); }
-        public long toMicros(long d)  { return x(d, C4/C1, MAX/(C4/C1)); }
-        public long toMillis(long d)  { return x(d, C4/C2, MAX/(C4/C2)); }
-        public long toSeconds(long d) { return x(d, C4/C3, MAX/(C4/C3)); }
-        public long toMinutes(long d) { return d; }
-        public long toHours(long d)   { return d/(C5/C4); }
-        public long toDays(long d)    { return d/(C6/C4); }
-        public long convert(long d, TimeUnit u) { return u.toMinutes(d); }
-        int excessNanos(long d, long m) { return 0; }
-    },
-
+    MINUTES(TimeUnit.MINUTE_SCALE),
     /**
      * Time unit representing sixty minutes.
      * @since 1.6
      */
-    HOURS {
-        public long toNanos(long d)   { return x(d, C5/C0, MAX/(C5/C0)); }
-        public long toMicros(long d)  { return x(d, C5/C1, MAX/(C5/C1)); }
-        public long toMillis(long d)  { return x(d, C5/C2, MAX/(C5/C2)); }
-        public long toSeconds(long d) { return x(d, C5/C3, MAX/(C5/C3)); }
-        public long toMinutes(long d) { return x(d, C5/C4, MAX/(C5/C4)); }
-        public long toHours(long d)   { return d; }
-        public long toDays(long d)    { return d/(C6/C5); }
-        public long convert(long d, TimeUnit u) { return u.toHours(d); }
-        int excessNanos(long d, long m) { return 0; }
-    },
-
+    HOURS(TimeUnit.HOUR_SCALE),
     /**
      * Time unit representing twenty four hours.
      * @since 1.6
      */
-    DAYS {
-        public long toNanos(long d)   { return x(d, C6/C0, MAX/(C6/C0)); }
-        public long toMicros(long d)  { return x(d, C6/C1, MAX/(C6/C1)); }
-        public long toMillis(long d)  { return x(d, C6/C2, MAX/(C6/C2)); }
-        public long toSeconds(long d) { return x(d, C6/C3, MAX/(C6/C3)); }
-        public long toMinutes(long d) { return x(d, C6/C4, MAX/(C6/C4)); }
-        public long toHours(long d)   { return x(d, C6/C5, MAX/(C6/C5)); }
-        public long toDays(long d)    { return d; }
-        public long convert(long d, TimeUnit u) { return u.toDays(d); }
-        int excessNanos(long d, long m) { return 0; }
-    };
+    DAYS(TimeUnit.DAY_SCALE);
+
+    // Scales as constants
+    private static final long NANO_SCALE   = 1L;
+    private static final long MICRO_SCALE  = 1000L * NANO_SCALE;
+    private static final long MILLI_SCALE  = 1000L * MICRO_SCALE;
+    private static final long SECOND_SCALE = 1000L * MILLI_SCALE;
+    private static final long MINUTE_SCALE = 60L * SECOND_SCALE;
+    private static final long HOUR_SCALE   = 60L * MINUTE_SCALE;
+    private static final long DAY_SCALE    = 24L * HOUR_SCALE;
+
+    /*
+     * Instances cache conversion ratios and saturation cutoffs for
+     * the units up through SECONDS. Other cases compute them, in
+     * method cvt.
+     */
 
-    // Handy constants for conversion methods
-    static final long C0 = 1L;
-    static final long C1 = C0 * 1000L;
-    static final long C2 = C1 * 1000L;
-    static final long C3 = C2 * 1000L;
-    static final long C4 = C3 * 60L;
-    static final long C5 = C4 * 60L;
-    static final long C6 = C5 * 24L;
+    private final long scale;
+    private final long maxNanos;
+    private final long maxMicros;
+    private final long maxMillis;
+    private final long maxSecs;
+    private final long microRatio;
+    private final int milliRatio;   // fits in 32 bits
+    private final int secRatio;     // fits in 32 bits
 
-    static final long MAX = Long.MAX_VALUE;
+    private TimeUnit(long s) {
+        this.scale = s;
+        this.maxNanos = Long.MAX_VALUE / s;
+        long ur = (s >= MICRO_SCALE) ? (s / MICRO_SCALE) : (MICRO_SCALE / s);
+        this.microRatio = ur;
+        this.maxMicros = Long.MAX_VALUE / ur;
+        long mr = (s >= MILLI_SCALE) ? (s / MILLI_SCALE) : (MILLI_SCALE / s);
+        this.milliRatio = (int)mr;
+        this.maxMillis = Long.MAX_VALUE / mr;
+        long sr = (s >= SECOND_SCALE) ? (s / SECOND_SCALE) : (SECOND_SCALE / s);
+        this.secRatio = (int)sr;
+        this.maxSecs = Long.MAX_VALUE / sr;
+    }
 
     /**
-     * Scale d by m, checking for overflow.
-     * This has a short name to make above code more readable.
+     * General conversion utility.
+     *
+     * @param d duration
+     * @param dst result unit scale
+     * @param src source unit scale
      */
-    static long x(long d, long m, long over) {
-        if (d > +over) return Long.MAX_VALUE;
-        if (d < -over) return Long.MIN_VALUE;
-        return d * m;
+    private static long cvt(long d, long dst, long src) {
+        long r, m;
+        if (src == dst)
+            return d;
+        else if (src < dst)
+            return d / (dst / src);
+        else if (d > (m = Long.MAX_VALUE / (r = src / dst)))
+            return Long.MAX_VALUE;
+        else if (d < -m)
+            return Long.MIN_VALUE;
+        else
+            return d * r;
     }
 
-    // To maintain full signature compatibility with 1.5, and to improve the
-    // clarity of the generated javadoc (see 6287639: Abstract methods in
-    // enum classes should not be listed as abstract), method convert
-    // etc. are not declared abstract but otherwise act as abstract methods.
-
     /**
      * Converts the given time duration in the given unit to this unit.
      * Conversions from finer to coarser granularities truncate, so
@@ -221,11 +178,17 @@
      * @param sourceDuration the time duration in the given {@code sourceUnit}
      * @param sourceUnit the unit of the {@code sourceDuration} argument
      * @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.
+     * or {@code Long.MIN_VALUE} if conversion would negatively overflow,
+     * or {@code Long.MAX_VALUE} if it would positively overflow.
      */
     public long convert(long sourceDuration, TimeUnit sourceUnit) {
-        throw new AbstractMethodError();
+        switch (this) {
+        case NANOSECONDS:  return sourceUnit.toNanos(sourceDuration);
+        case MICROSECONDS: return sourceUnit.toMicros(sourceDuration);
+        case MILLISECONDS: return sourceUnit.toMillis(sourceDuration);
+        case SECONDS:      return sourceUnit.toSeconds(sourceDuration);
+        default: return cvt(sourceDuration, scale, sourceUnit.scale);
+        }
     }
 
     /**
@@ -233,11 +196,19 @@
      * {@link #convert(long, TimeUnit) NANOSECONDS.convert(duration, this)}.
      * @param duration the duration
      * @return the converted duration,
-     * or {@code Long.MIN_VALUE} if conversion would negatively
-     * overflow, or {@code Long.MAX_VALUE} if it would positively overflow.
+     * or {@code Long.MIN_VALUE} if conversion would negatively overflow,
+     * or {@code Long.MAX_VALUE} if it would positively overflow.
      */
     public long toNanos(long duration) {
-        throw new AbstractMethodError();
+        long s, m;
+        if ((s = scale) == NANO_SCALE)
+            return duration;
+        else if (duration > (m = maxNanos))
+            return Long.MAX_VALUE;
+        else if (duration < -m)
+            return Long.MIN_VALUE;
+        else
+            return duration * s;
     }
 
     /**
@@ -245,11 +216,21 @@
      * {@link #convert(long, TimeUnit) MICROSECONDS.convert(duration, this)}.
      * @param duration the duration
      * @return the converted duration,
-     * or {@code Long.MIN_VALUE} if conversion would negatively
-     * overflow, or {@code Long.MAX_VALUE} if it would positively overflow.
+     * or {@code Long.MIN_VALUE} if conversion would negatively overflow,
+     * or {@code Long.MAX_VALUE} if it would positively overflow.
      */
     public long toMicros(long duration) {
-        throw new AbstractMethodError();
+        long s, m;
+        if ((s = scale) == MICRO_SCALE)
+            return duration;
+        else if (s < MICRO_SCALE)
+            return duration / microRatio;
+        else if (duration > (m = maxMicros))
+            return Long.MAX_VALUE;
+        else if (duration < -m)
+            return Long.MIN_VALUE;
+        else
+            return duration * microRatio;
     }
 
     /**
@@ -257,11 +238,21 @@
      * {@link #convert(long, TimeUnit) MILLISECONDS.convert(duration, this)}.
      * @param duration the duration
      * @return the converted duration,
-     * or {@code Long.MIN_VALUE} if conversion would negatively
-     * overflow, or {@code Long.MAX_VALUE} if it would positively overflow.
+     * or {@code Long.MIN_VALUE} if conversion would negatively overflow,
+     * or {@code Long.MAX_VALUE} if it would positively overflow.
      */
     public long toMillis(long duration) {
-        throw new AbstractMethodError();
+        long s, m;
+        if ((s = scale) == MILLI_SCALE)
+            return duration;
+        else if (s < MILLI_SCALE)
+            return duration / milliRatio;
+        else if (duration > (m = maxMillis))
+            return Long.MAX_VALUE;
+        else if (duration < -m)
+            return Long.MIN_VALUE;
+        else
+            return duration * milliRatio;
     }
 
     /**
@@ -269,11 +260,21 @@
      * {@link #convert(long, TimeUnit) SECONDS.convert(duration, this)}.
      * @param duration the duration
      * @return the converted duration,
-     * or {@code Long.MIN_VALUE} if conversion would negatively
-     * overflow, or {@code Long.MAX_VALUE} if it would positively overflow.
+     * or {@code Long.MIN_VALUE} if conversion would negatively overflow,
+     * or {@code Long.MAX_VALUE} if it would positively overflow.
      */
     public long toSeconds(long duration) {
-        throw new AbstractMethodError();
+        long s, m;
+        if ((s = scale) == SECOND_SCALE)
+            return duration;
+        else if (s < SECOND_SCALE)
+            return duration / secRatio;
+        else if (duration > (m = maxSecs))
+            return Long.MAX_VALUE;
+        else if (duration < -m)
+            return Long.MIN_VALUE;
+        else
+            return duration * secRatio;
     }
 
     /**
@@ -281,12 +282,12 @@
      * {@link #convert(long, TimeUnit) MINUTES.convert(duration, this)}.
      * @param duration the duration
      * @return the converted duration,
-     * or {@code Long.MIN_VALUE} if conversion would negatively
-     * overflow, or {@code Long.MAX_VALUE} if it would positively overflow.
+     * or {@code Long.MIN_VALUE} if conversion would negatively overflow,
+     * or {@code Long.MAX_VALUE} if it would positively overflow.
      * @since 1.6
      */
     public long toMinutes(long duration) {
-        throw new AbstractMethodError();
+        return cvt(duration, MINUTE_SCALE, scale);
     }
 
     /**
@@ -294,12 +295,12 @@
      * {@link #convert(long, TimeUnit) HOURS.convert(duration, this)}.
      * @param duration the duration
      * @return the converted duration,
-     * or {@code Long.MIN_VALUE} if conversion would negatively
-     * overflow, or {@code Long.MAX_VALUE} if it would positively overflow.
+     * or {@code Long.MIN_VALUE} if conversion would negatively overflow,
+     * or {@code Long.MAX_VALUE} if it would positively overflow.
      * @since 1.6
      */
     public long toHours(long duration) {
-        throw new AbstractMethodError();
+        return cvt(duration, HOUR_SCALE, scale);
     }
 
     /**
@@ -310,7 +311,7 @@
      * @since 1.6
      */
     public long toDays(long duration) {
-        throw new AbstractMethodError();
+        return cvt(duration, DAY_SCALE, scale);
     }
 
     /**
@@ -320,7 +321,15 @@
      * @param m the number of milliseconds
      * @return the number of nanoseconds
      */
-    abstract int excessNanos(long d, long m);
+    private int excessNanos(long d, long m) {
+        long s;
+        if ((s = scale) == NANO_SCALE)
+            return (int)(d - (m * MILLI_SCALE));
+        else if (s == MICRO_SCALE)
+            return (int)((d * 1000L) - (m * MILLI_SCALE));
+        else
+            return 0;
+    }
 
     /**
      * Performs a timed {@link Object#wait(long, int) Object.wait}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/concurrent/tck/TimeUnit8Test.java	Mon Mar 28 08:53:39 2016 -0700
@@ -0,0 +1,103 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * This file is available under and governed by the GNU General Public
+ * License version 2 only, as published by the Free Software Foundation.
+ * However, the following notice accompanied the original version of this
+ * file:
+ *
+ * Written by Doug Lea and Martin Buchholz with assistance from
+ * members of JCP JSR-166 Expert Group and released to the public
+ * domain, as explained at
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+import static java.util.concurrent.TimeUnit.DAYS;
+import static java.util.concurrent.TimeUnit.HOURS;
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.time.temporal.ChronoUnit;
+import java.util.concurrent.TimeUnit;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class TimeUnit8Test extends JSR166TestCase {
+    public static void main(String[] args) {
+        main(suite(), args);
+    }
+
+    public static Test suite() {
+        return new TestSuite(TimeUnit8Test.class);
+    }
+
+    /**
+     * tests for toChronoUnit.
+     */
+    public void testToChronoUnit() throws Exception {
+        assertSame(ChronoUnit.NANOS,   NANOSECONDS.toChronoUnit());
+        assertSame(ChronoUnit.MICROS,  MICROSECONDS.toChronoUnit());
+        assertSame(ChronoUnit.MILLIS,  MILLISECONDS.toChronoUnit());
+        assertSame(ChronoUnit.SECONDS, SECONDS.toChronoUnit());
+        assertSame(ChronoUnit.MINUTES, MINUTES.toChronoUnit());
+        assertSame(ChronoUnit.HOURS,   HOURS.toChronoUnit());
+        assertSame(ChronoUnit.DAYS,    DAYS.toChronoUnit());
+
+        // Every TimeUnit has a defined ChronoUnit equivalent
+        for (TimeUnit x : TimeUnit.values())
+            assertSame(x, TimeUnit.of(x.toChronoUnit()));
+    }
+
+    /**
+     * tests for TimeUnit.of(ChronoUnit).
+     */
+    public void testTimeUnitOf() throws Exception {
+        assertSame(NANOSECONDS,  TimeUnit.of(ChronoUnit.NANOS));
+        assertSame(MICROSECONDS, TimeUnit.of(ChronoUnit.MICROS));
+        assertSame(MILLISECONDS, TimeUnit.of(ChronoUnit.MILLIS));
+        assertSame(SECONDS,      TimeUnit.of(ChronoUnit.SECONDS));
+        assertSame(MINUTES,      TimeUnit.of(ChronoUnit.MINUTES));
+        assertSame(HOURS,        TimeUnit.of(ChronoUnit.HOURS));
+        assertSame(DAYS,         TimeUnit.of(ChronoUnit.DAYS));
+
+        assertThrows(NullPointerException.class,
+                     () -> TimeUnit.of((ChronoUnit)null));
+
+        // ChronoUnits either round trip to their TimeUnit
+        // equivalents, or throw IllegalArgumentException.
+        for (ChronoUnit cu : ChronoUnit.values()) {
+            final TimeUnit tu;
+            try {
+                tu = TimeUnit.of(cu);
+            } catch (IllegalArgumentException acceptable) {
+                continue;
+            }
+            assertSame(cu, tu.toChronoUnit());
+        }
+    }
+
+}
--- a/jdk/test/java/util/concurrent/tck/TimeUnitTest.java	Mon Mar 28 17:12:48 2016 +0300
+++ b/jdk/test/java/util/concurrent/tck/TimeUnitTest.java	Mon Mar 28 08:53:39 2016 -0700
@@ -41,7 +41,6 @@
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 
-import java.time.temporal.ChronoUnit;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -124,6 +123,15 @@
             assertEquals(t,
                          NANOSECONDS.convert(t, NANOSECONDS));
         }
+
+        for (TimeUnit x : TimeUnit.values()) {
+            long[] zs = {
+                0, 1, -1,
+                Integer.MAX_VALUE, Integer.MIN_VALUE,
+                Long.MAX_VALUE, Long.MIN_VALUE,
+            };
+            for (long z : zs) assertEquals(z, x.convert(z, x));
+        }
     }
 
     /**
@@ -308,6 +316,27 @@
                      NANOSECONDS.convert(Long.MAX_VALUE / 2, DAYS));
         assertEquals(Long.MIN_VALUE,
                      NANOSECONDS.convert(-Long.MAX_VALUE / 4, DAYS));
+
+        for (TimeUnit x : TimeUnit.values())
+            for (TimeUnit y : TimeUnit.values()) {
+                long ratio = x.toNanos(1) / y.toNanos(1);
+                if (ratio >= 1) {
+                    assertEquals(ratio, y.convert(1, x));
+                    assertEquals(1, x.convert(ratio, y));
+                    long max = Long.MAX_VALUE/ratio;
+                    assertEquals(max * ratio, y.convert(max, x));
+                    assertEquals(-max * ratio, y.convert(-max, x));
+                    assertEquals(max, x.convert(max * ratio, y));
+                    assertEquals(-max, x.convert(-max * ratio, y));
+                    if (max < Long.MAX_VALUE) {
+                        assertEquals(Long.MAX_VALUE, y.convert(max + 1, x));
+                        assertEquals(Long.MIN_VALUE, y.convert(-max - 1, x));
+                        assertEquals(Long.MIN_VALUE, y.convert(Long.MIN_VALUE + 1, x));
+                    }
+                    assertEquals(Long.MAX_VALUE, y.convert(Long.MAX_VALUE, x));
+                    assertEquals(Long.MIN_VALUE, y.convert(Long.MIN_VALUE, x));
+                }
+            }
     }
 
     /**
@@ -319,6 +348,146 @@
                      MILLISECONDS.toNanos(Long.MAX_VALUE / 2));
         assertEquals(Long.MIN_VALUE,
                      MILLISECONDS.toNanos(-Long.MAX_VALUE / 3));
+
+        for (TimeUnit x : TimeUnit.values()) {
+            long ratio = x.toNanos(1) / NANOSECONDS.toNanos(1);
+            if (ratio >= 1) {
+                long max = Long.MAX_VALUE/ratio;
+                for (long z : new long[] {0, 1, -1, max, -max})
+                    assertEquals(z * ratio, x.toNanos(z));
+                if (max < Long.MAX_VALUE) {
+                    assertEquals(Long.MAX_VALUE, x.toNanos(max + 1));
+                    assertEquals(Long.MIN_VALUE, x.toNanos(-max - 1));
+                    assertEquals(Long.MIN_VALUE, x.toNanos(Long.MIN_VALUE + 1));
+                }
+                assertEquals(Long.MAX_VALUE, x.toNanos(Long.MAX_VALUE));
+                assertEquals(Long.MIN_VALUE, x.toNanos(Long.MIN_VALUE));
+                if (max < Integer.MAX_VALUE) {
+                    assertEquals(Long.MAX_VALUE, x.toNanos(Integer.MAX_VALUE));
+                    assertEquals(Long.MIN_VALUE, x.toNanos(Integer.MIN_VALUE));
+                }
+            }
+        }
+    }
+
+    /**
+     * toMicros saturates positive too-large values to Long.MAX_VALUE
+     * and negative to LONG.MIN_VALUE
+     */
+    public void testToMicrosSaturate() {
+        for (TimeUnit x : TimeUnit.values()) {
+            long ratio = x.toNanos(1) / MICROSECONDS.toNanos(1);
+            if (ratio >= 1) {
+                long max = Long.MAX_VALUE/ratio;
+                for (long z : new long[] {0, 1, -1, max, -max})
+                    assertEquals(z * ratio, x.toMicros(z));
+                if (max < Long.MAX_VALUE) {
+                    assertEquals(Long.MAX_VALUE, x.toMicros(max + 1));
+                    assertEquals(Long.MIN_VALUE, x.toMicros(-max - 1));
+                    assertEquals(Long.MIN_VALUE, x.toMicros(Long.MIN_VALUE + 1));
+                }
+                assertEquals(Long.MAX_VALUE, x.toMicros(Long.MAX_VALUE));
+                assertEquals(Long.MIN_VALUE, x.toMicros(Long.MIN_VALUE));
+                if (max < Integer.MAX_VALUE) {
+                    assertEquals(Long.MAX_VALUE, x.toMicros(Integer.MAX_VALUE));
+                    assertEquals(Long.MIN_VALUE, x.toMicros(Integer.MIN_VALUE));
+                }
+            }
+        }
+    }
+
+    /**
+     * toMillis saturates positive too-large values to Long.MAX_VALUE
+     * and negative to LONG.MIN_VALUE
+     */
+    public void testToMillisSaturate() {
+        for (TimeUnit x : TimeUnit.values()) {
+            long ratio = x.toNanos(1) / MILLISECONDS.toNanos(1);
+            if (ratio >= 1) {
+                long max = Long.MAX_VALUE/ratio;
+                for (long z : new long[] {0, 1, -1, max, -max})
+                    assertEquals(z * ratio, x.toMillis(z));
+                if (max < Long.MAX_VALUE) {
+                    assertEquals(Long.MAX_VALUE, x.toMillis(max + 1));
+                    assertEquals(Long.MIN_VALUE, x.toMillis(-max - 1));
+                    assertEquals(Long.MIN_VALUE, x.toMillis(Long.MIN_VALUE + 1));
+                }
+                assertEquals(Long.MAX_VALUE, x.toMillis(Long.MAX_VALUE));
+                assertEquals(Long.MIN_VALUE, x.toMillis(Long.MIN_VALUE));
+                if (max < Integer.MAX_VALUE) {
+                    assertEquals(Long.MAX_VALUE, x.toMillis(Integer.MAX_VALUE));
+                    assertEquals(Long.MIN_VALUE, x.toMillis(Integer.MIN_VALUE));
+                }
+            }
+        }
+    }
+
+    /**
+     * toSeconds saturates positive too-large values to Long.MAX_VALUE
+     * and negative to LONG.MIN_VALUE
+     */
+    public void testToSecondsSaturate() {
+        for (TimeUnit x : TimeUnit.values()) {
+            long ratio = x.toNanos(1) / SECONDS.toNanos(1);
+            if (ratio >= 1) {
+                long max = Long.MAX_VALUE/ratio;
+                for (long z : new long[] {0, 1, -1, max, -max})
+                    assertEquals(z * ratio, x.toSeconds(z));
+                if (max < Long.MAX_VALUE) {
+                    assertEquals(Long.MAX_VALUE, x.toSeconds(max + 1));
+                    assertEquals(Long.MIN_VALUE, x.toSeconds(-max - 1));
+                    assertEquals(Long.MIN_VALUE, x.toSeconds(Long.MIN_VALUE + 1));
+                }
+                assertEquals(Long.MAX_VALUE, x.toSeconds(Long.MAX_VALUE));
+                assertEquals(Long.MIN_VALUE, x.toSeconds(Long.MIN_VALUE));
+                if (max < Integer.MAX_VALUE) {
+                    assertEquals(Long.MAX_VALUE, x.toSeconds(Integer.MAX_VALUE));
+                    assertEquals(Long.MIN_VALUE, x.toSeconds(Integer.MIN_VALUE));
+                }
+            }
+        }
+    }
+
+    /**
+     * toMinutes saturates positive too-large values to Long.MAX_VALUE
+     * and negative to LONG.MIN_VALUE
+     */
+    public void testToMinutesSaturate() {
+        for (TimeUnit x : TimeUnit.values()) {
+            long ratio = x.toNanos(1) / MINUTES.toNanos(1);
+            if (ratio > 1) {
+                long max = Long.MAX_VALUE/ratio;
+                for (long z : new long[] {0, 1, -1, max, -max})
+                    assertEquals(z * ratio, x.toMinutes(z));
+                assertEquals(Long.MAX_VALUE, x.toMinutes(max + 1));
+                assertEquals(Long.MIN_VALUE, x.toMinutes(-max - 1));
+                assertEquals(Long.MAX_VALUE, x.toMinutes(Long.MAX_VALUE));
+                assertEquals(Long.MIN_VALUE, x.toMinutes(Long.MIN_VALUE));
+                assertEquals(Long.MIN_VALUE, x.toMinutes(Long.MIN_VALUE + 1));
+            }
+        }
+    }
+
+    /**
+     * toHours saturates positive too-large values to Long.MAX_VALUE
+     * and negative to LONG.MIN_VALUE
+     */
+    public void testToHoursSaturate() {
+        for (TimeUnit x : TimeUnit.values()) {
+            long ratio = x.toNanos(1) / HOURS.toNanos(1);
+            if (ratio >= 1) {
+                long max = Long.MAX_VALUE/ratio;
+                for (long z : new long[] {0, 1, -1, max, -max})
+                    assertEquals(z * ratio, x.toHours(z));
+                if (max < Long.MAX_VALUE) {
+                    assertEquals(Long.MAX_VALUE, x.toHours(max + 1));
+                    assertEquals(Long.MIN_VALUE, x.toHours(-max - 1));
+                    assertEquals(Long.MIN_VALUE, x.toHours(Long.MIN_VALUE + 1));
+                }
+                assertEquals(Long.MAX_VALUE, x.toHours(Long.MAX_VALUE));
+                assertEquals(Long.MIN_VALUE, x.toHours(Long.MIN_VALUE));
+            }
+        }
     }
 
     /**
@@ -461,49 +630,4 @@
             assertSame(x, serialClone(x));
     }
 
-    /**
-     * tests for toChronoUnit.
-     */
-    public void testToChronoUnit() throws Exception {
-        assertSame(ChronoUnit.NANOS,   NANOSECONDS.toChronoUnit());
-        assertSame(ChronoUnit.MICROS,  MICROSECONDS.toChronoUnit());
-        assertSame(ChronoUnit.MILLIS,  MILLISECONDS.toChronoUnit());
-        assertSame(ChronoUnit.SECONDS, SECONDS.toChronoUnit());
-        assertSame(ChronoUnit.MINUTES, MINUTES.toChronoUnit());
-        assertSame(ChronoUnit.HOURS,   HOURS.toChronoUnit());
-        assertSame(ChronoUnit.DAYS,    DAYS.toChronoUnit());
-
-        // Every TimeUnit has a defined ChronoUnit equivalent
-        for (TimeUnit x : TimeUnit.values())
-            assertSame(x, TimeUnit.of(x.toChronoUnit()));
-    }
-
-    /**
-     * tests for TimeUnit.of(ChronoUnit).
-     */
-    public void testTimeUnitOf() throws Exception {
-        assertSame(NANOSECONDS,  TimeUnit.of(ChronoUnit.NANOS));
-        assertSame(MICROSECONDS, TimeUnit.of(ChronoUnit.MICROS));
-        assertSame(MILLISECONDS, TimeUnit.of(ChronoUnit.MILLIS));
-        assertSame(SECONDS,      TimeUnit.of(ChronoUnit.SECONDS));
-        assertSame(MINUTES,      TimeUnit.of(ChronoUnit.MINUTES));
-        assertSame(HOURS,        TimeUnit.of(ChronoUnit.HOURS));
-        assertSame(DAYS,         TimeUnit.of(ChronoUnit.DAYS));
-
-        assertThrows(NullPointerException.class,
-                     () -> TimeUnit.of((ChronoUnit)null));
-
-        // ChronoUnits either round trip to their TimeUnit
-        // equivalents, or throw IllegalArgumentException.
-        for (ChronoUnit cu : ChronoUnit.values()) {
-            final TimeUnit tu;
-            try {
-                tu = TimeUnit.of(cu);
-            } catch (IllegalArgumentException acceptable) {
-                continue;
-            }
-            assertSame(cu, tu.toChronoUnit());
-        }
-    }
-
 }