src/share/classes/java/time/Duration.java

Print this page

        

@@ -59,14 +59,18 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 package java.time;
 
+import static java.time.LocalTime.NANOS_PER_SECOND;
 import static java.time.LocalTime.SECONDS_PER_DAY;
-import static java.time.temporal.ChronoField.INSTANT_SECONDS;
+import static java.time.LocalTime.SECONDS_PER_HOUR;
+import static java.time.LocalTime.SECONDS_PER_MINUTE;
 import static java.time.temporal.ChronoField.NANO_OF_SECOND;
 import static java.time.temporal.ChronoUnit.DAYS;
+import static java.time.temporal.ChronoUnit.NANOS;
+import static java.time.temporal.ChronoUnit.SECONDS;
 
 import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
 import java.io.InvalidObjectException;

@@ -77,30 +81,37 @@
 import java.math.RoundingMode;
 import java.time.format.DateTimeParseException;
 import java.time.temporal.ChronoField;
 import java.time.temporal.ChronoUnit;
 import java.time.temporal.Temporal;
-import java.time.temporal.TemporalAccessor;
-import java.time.temporal.TemporalAdder;
-import java.time.temporal.TemporalSubtractor;
+import java.time.temporal.TemporalAmount;
 import java.time.temporal.TemporalUnit;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
- * A duration between two instants on the time-line.
+ * A time-based amount of time, such as '34.5 seconds'.
  * <p>
- * This class models a duration of time and is not tied to any instant.
- * The model is of a directed duration, meaning that the duration may be negative.
+ * This class models a quantity or amount of time in terms of seconds and nanoseconds.
+ * It can be accessed using other duration-based units, such as minutes and hours.
+ * In addition, the {@link ChronoUnit#DAYS DAYS} unit can be used and is treated as
+ * exactly equal to 24 hours, thus ignoring daylight savings effects.
+ * See {@link Period} for the date-based equivalent to this class.
  * <p>
  * A physical duration could be of infinite length.
  * For practicality, the duration is stored with constraints similar to {@link Instant}.
  * The duration uses nanosecond resolution with a maximum value of the seconds that can
  * be held in a {@code long}. This is greater than the current estimated age of the universe.
  * <p>
  * The range of a duration requires the storage of a number larger than a {@code long}.
  * To achieve this, the class stores a {@code long} representing seconds and an {@code int}
  * representing nanosecond-of-second, which will always be between 0 and 999,999,999.
+ * The model is of a directed duration, meaning that the duration may be negative.
  * <p>
  * The duration is measured in "seconds", but these are not necessarily identical to
  * the scientific "SI second" definition based on atomic clocks.
  * This difference only impacts durations measured near a leap-second and should not affect
  * most applications.

@@ -110,11 +121,11 @@
  * This class is immutable and thread-safe.
  *
  * @since 1.8
  */
 public final class Duration
-        implements TemporalAdder, TemporalSubtractor, Comparable<Duration>, Serializable {
+        implements TemporalAmount, Comparable<Duration>, Serializable {
 
     /**
      * Constant for a duration of zero.
      */
     public static final Duration ZERO = new Duration(0, 0);

@@ -123,15 +134,18 @@
      */
     private static final long serialVersionUID = 3078945930695997490L;
     /**
      * Constant for nanos per second.
      */
-    private static final int NANOS_PER_SECOND = 1000_000_000;
+    private static final BigInteger BI_NANOS_PER_SECOND = BigInteger.valueOf(NANOS_PER_SECOND);
     /**
-     * Constant for nanos per second.
+     * The pattern for parsing.
      */
-    private static final BigInteger BI_NANOS_PER_SECOND = BigInteger.valueOf(NANOS_PER_SECOND);
+    private final static Pattern PATTERN =
+            Pattern.compile("([-+]?)P(?:([-+]?[0-9]+)D)?" +
+                    "(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?",
+                    Pattern.CASE_INSENSITIVE);
 
     /**
      * The number of seconds in the duration.
      */
     private final long seconds;

@@ -141,11 +155,57 @@
      */
     private final int nanos;
 
     //-----------------------------------------------------------------------
     /**
-     * Obtains an instance of {@code Duration} from a number of seconds.
+     * Obtains a {@code Duration} representing a number of standard 24 hour days.
+     * <p>
+     * The seconds are calculated based on the standard definition of a day,
+     * where each day is 86400 seconds which implies a 24 hour day.
+     * The nanosecond in second field is set to zero.
+     *
+     * @param days  the number of days, positive or negative
+     * @return a {@code Duration}, not null
+     * @throws ArithmeticException if the input days exceeds the capacity of {@code Duration}
+     */
+    public static Duration ofDays(long days) {
+        return create(Math.multiplyExact(days, SECONDS_PER_DAY), 0);
+    }
+
+    /**
+     * Obtains a {@code Duration} representing a number of standard hours.
+     * <p>
+     * The seconds are calculated based on the standard definition of an hour,
+     * where each hour is 3600 seconds.
+     * The nanosecond in second field is set to zero.
+     *
+     * @param hours  the number of hours, positive or negative
+     * @return a {@code Duration}, not null
+     * @throws ArithmeticException if the input hours exceeds the capacity of {@code Duration}
+     */
+    public static Duration ofHours(long hours) {
+        return create(Math.multiplyExact(hours, SECONDS_PER_HOUR), 0);
+    }
+
+    /**
+     * Obtains a {@code Duration} representing a number of standard minutes.
+     * <p>
+     * The seconds are calculated based on the standard definition of a minute,
+     * where each minute is 60 seconds.
+     * The nanosecond in second field is set to zero.
+     *
+     * @param minutes  the number of minutes, positive or negative
+     * @return a {@code Duration}, not null
+     * @throws ArithmeticException if the input minutes exceeds the capacity of {@code Duration}
+     */
+    public static Duration ofMinutes(long minutes) {
+        return create(Math.multiplyExact(minutes, SECONDS_PER_MINUTE), 0);
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Obtains a {@code Duration} representing a number of seconds.
      * <p>
      * The nanosecond in second field is set to zero.
      *
      * @param seconds  the number of seconds, positive or negative
      * @return a {@code Duration}, not null

@@ -153,12 +213,12 @@
     public static Duration ofSeconds(long seconds) {
         return create(seconds, 0);
     }
 
     /**
-     * Obtains an instance of {@code Duration} from a number of seconds
-     * and an adjustment in nanoseconds.
+     * Obtains a {@code Duration} representing a number of seconds and an
+     * adjustment in nanoseconds.
      * <p>
      * This method allows an arbitrary number of nanoseconds to be passed in.
      * The factory will alter the values of the second and nanosecond in order
      * to ensure that the stored nanosecond is in the range 0 to 999,999,999.
      * For example, the following will result in the exactly the same duration:

@@ -173,17 +233,17 @@
      * @return a {@code Duration}, not null
      * @throws ArithmeticException if the adjustment causes the seconds to exceed the capacity of {@code Duration}
      */
     public static Duration ofSeconds(long seconds, long nanoAdjustment) {
         long secs = Math.addExact(seconds, Math.floorDiv(nanoAdjustment, NANOS_PER_SECOND));
-        int nos = (int)Math.floorMod(nanoAdjustment, NANOS_PER_SECOND);
+        int nos = (int) Math.floorMod(nanoAdjustment, NANOS_PER_SECOND);
         return create(secs, nos);
     }
 
     //-----------------------------------------------------------------------
     /**
-     * Obtains an instance of {@code Duration} from a number of milliseconds.
+     * Obtains a {@code Duration} representing a number of milliseconds.
      * <p>
      * The seconds and nanoseconds are extracted from the specified milliseconds.
      *
      * @param millis  the number of milliseconds, positive or negative
      * @return a {@code Duration}, not null

@@ -198,11 +258,11 @@
         return create(secs, mos * 1000_000);
     }
 
     //-----------------------------------------------------------------------
     /**
-     * Obtains an instance of {@code Duration} from a number of nanoseconds.
+     * Obtains a {@code Duration} representing a number of nanoseconds.
      * <p>
      * The seconds and nanoseconds are extracted from the specified nanoseconds.
      *
      * @param nanos  the number of nanoseconds, positive or negative
      * @return a {@code Duration}, not null

@@ -217,57 +277,11 @@
         return create(secs, nos);
     }
 
     //-----------------------------------------------------------------------
     /**
-     * Obtains an instance of {@code Duration} from a number of standard length minutes.
-     * <p>
-     * The seconds are calculated based on the standard definition of a minute,
-     * where each minute is 60 seconds.
-     * The nanosecond in second field is set to zero.
-     *
-     * @param minutes  the number of minutes, positive or negative
-     * @return a {@code Duration}, not null
-     * @throws ArithmeticException if the input minutes exceeds the capacity of {@code Duration}
-     */
-    public static Duration ofMinutes(long minutes) {
-        return create(Math.multiplyExact(minutes, 60), 0);
-    }
-
-    /**
-     * Obtains an instance of {@code Duration} from a number of standard length hours.
-     * <p>
-     * The seconds are calculated based on the standard definition of an hour,
-     * where each hour is 3600 seconds.
-     * The nanosecond in second field is set to zero.
-     *
-     * @param hours  the number of hours, positive or negative
-     * @return a {@code Duration}, not null
-     * @throws ArithmeticException if the input hours exceeds the capacity of {@code Duration}
-     */
-    public static Duration ofHours(long hours) {
-        return create(Math.multiplyExact(hours, 3600), 0);
-    }
-
-    /**
-     * Obtains an instance of {@code Duration} from a number of standard 24 hour days.
-     * <p>
-     * The seconds are calculated based on the standard definition of a day,
-     * where each day is 86400 seconds which implies a 24 hour day.
-     * The nanosecond in second field is set to zero.
-     *
-     * @param days  the number of days, positive or negative
-     * @return a {@code Duration}, not null
-     * @throws ArithmeticException if the input days exceeds the capacity of {@code Duration}
-     */
-    public static Duration ofDays(long days) {
-        return create(Math.multiplyExact(days, 86400), 0);
-    }
-
-    //-----------------------------------------------------------------------
-    /**
-     * Obtains an instance of {@code Duration} from a duration in the specified unit.
+     * Obtains a {@code Duration} representing an amount in the specified unit.
      * <p>
      * The parameters represent the two parts of a phrase like '6 Hours'. For example:
      * <pre>
      *  Duration.of(3, SECONDS);
      *  Duration.of(465, HOURS);

@@ -286,114 +300,143 @@
         return ZERO.plus(amount, unit);
     }
 
     //-----------------------------------------------------------------------
     /**
-     * Obtains an instance of {@code Duration} representing the duration between two instants.
+     * Obtains a {@code Duration} representing the duration between two instants.
      * <p>
-     * A {@code Duration} represents a directed distance between two points on the time-line.
-     * As such, this method will return a negative duration if the end is before the start.
-     * To guarantee to obtain a positive duration call {@link #abs()} on the result of this factory.
+     * This calculates the duration between two temporal objects of the same type.
+     * The difference in seconds is calculated using
+     * {@link Temporal#periodUntil(Temporal, TemporalUnit)}.
+     * The difference in nanoseconds is calculated using by querying the
+     * {@link ChronoField#NANO_OF_SECOND NANO_OF_SECOND} field.
+     * <p>
+     * The result of this method can be a negative period if the end is before the start.
+     * To guarantee to obtain a positive duration call {@link #abs()} on the result.
      *
      * @param startInclusive  the start instant, inclusive, not null
      * @param endExclusive  the end instant, exclusive, not null
      * @return a {@code Duration}, not null
      * @throws ArithmeticException if the calculation exceeds the capacity of {@code Duration}
      */
-    public static Duration between(TemporalAccessor startInclusive, TemporalAccessor endExclusive) {
-        long secs = Math.subtractExact(endExclusive.getLong(INSTANT_SECONDS), startInclusive.getLong(INSTANT_SECONDS));
-        long nanos = endExclusive.getLong(NANO_OF_SECOND) - startInclusive.getLong(NANO_OF_SECOND);
-        secs = Math.addExact(secs, Math.floorDiv(nanos, NANOS_PER_SECOND));
-        nanos = Math.floorMod(nanos, NANOS_PER_SECOND);
-        return create(secs, (int) nanos);  // safe from overflow
+    public static  Duration between(Temporal startInclusive, Temporal endExclusive) {
+        long secs = startInclusive.periodUntil(endExclusive, SECONDS);
+        long nanos;
+        try {
+            nanos = endExclusive.getLong(NANO_OF_SECOND) - startInclusive.getLong(NANO_OF_SECOND);
+        } catch (DateTimeException ex) {
+            nanos = 0;
+        }
+        return ofSeconds(secs, nanos);
     }
 
     //-----------------------------------------------------------------------
     /**
-     * Obtains an instance of {@code Duration} by parsing a text string.
-     * <p>
-     * This will parse the string produced by {@link #toString()} which is
-     * the ISO-8601 format {@code PTnS} where {@code n} is
-     * the number of seconds with optional decimal part.
-     * The number must consist of ASCII numerals.
-     * There must only be a negative sign at the start of the number and it can
-     * only be present if the value is less than zero.
-     * There must be at least one digit before any decimal point.
-     * There must be between 1 and 9 inclusive digits after any decimal point.
-     * The letters (P, T and S) will be accepted in upper or lower case.
+     * Obtains a {@code Duration} from a text string such as {@code PnDTnHnMn.nS}.
+     * <p>
+     * This will parse a textual representation of a duration, including the
+     * string produced by {@code toString()}. The formats accepted are based
+     * on the ISO-8601 duration format {@code PnDTnHnMn.nS} with days
+     * considered to be exactly 24 hours.
+     * <p>
+     * The string starts with an optional sign, denoted by the ASCII negative
+     * or positive symbol. If negative, the whole period is negated.
+     * The ASCII letter "P" is next in upper or lower case.
+     * There are then four sections, each consisting of a number and a suffix.
+     * The sections have suffixes in ASCII of "D", "H", "M" and "S" for
+     * days, hours, minutes and seconds, accepted in upper or lower case.
+     * The suffixes must occur in order. The ASCII letter "T" must occur before
+     * the first occurrence, if any, of an hour, minute or second section.
+     * At least one of the four sections must be present, and if "T" is present
+     * there must be at least one section after the "T".
+     * The number part of each section must consist of one or more ASCII digits.
+     * The number may be prefixed by the ASCII negative or positive symbol.
+     * The number of days, hours and minutes must parse to an {@code long}.
+     * The number of seconds must parse to an {@code long} with optional fraction.
      * The decimal point may be either a dot or a comma.
+     * The fractional part may have from zero to 9 digits.
+     * <p>
+     * The leading plus/minus sign, and negative values for other units are
+     * not part of the ISO-8601 standard.
+     * <p>
+     * Examples:
+     * <pre>
+     *    "PT20.345S" -> parses as "20.345 seconds"
+     *    "PT15M"     -> parses as "15 minutes" (where a minute is 60 seconds)
+     *    "PT10H"     -> parses as "10 hours" (where an hour is 3600 seconds)
+     *    "P2D"       -> parses as "2 days" (where a day is 24 hours or 86400 seconds)
+     *    "P2DT3H4M"  -> parses as "2 days, 3 hours and 4 minutes"
+     *    "P-6H3M"    -> parses as "-6 hours and +3 minutes"
+     *    "-P6H3M"    -> parses as "-6 hours and -3 minutes"
+     *    "-P-6H+3M"  -> parses as "+6 hours and -3 minutes"
+     * </pre>
      *
      * @param text  the text to parse, not null
-     * @return a {@code Duration}, not null
-     * @throws DateTimeParseException if the text cannot be parsed to a {@code Duration}
+     * @return the parsed duration, not null
+     * @throws DateTimeParseException if the text cannot be parsed to a duration
      */
-    public static Duration parse(final CharSequence text) {
+    public static Duration parse(CharSequence text) {
         Objects.requireNonNull(text, "text");
-        int len = text.length();
-        if (len < 4 ||
-                (text.charAt(0) != 'P' && text.charAt(0) != 'p') ||
-                (text.charAt(1) != 'T' && text.charAt(1) != 't') ||
-                (text.charAt(len - 1) != 'S' && text.charAt(len - 1) != 's') ||
-                (len == 5 && text.charAt(2) == '-' && text.charAt(3) == '0')) {
-            throw new DateTimeParseException("Duration could not be parsed: " + text, text, 0);
-        }
-        String numberText = text.subSequence(2, len - 1).toString().replace(',', '.');
-        if (numberText.charAt(0) == '+') {
-            throw new DateTimeParseException("Duration could not be parsed: " + text, text, 2);
+        Matcher matcher = PATTERN.matcher(text);
+        if (matcher.matches()) {
+            // check for letter T but no time sections
+            if ("T".equals(matcher.group(3)) == false) {
+                boolean negate = "-".equals(matcher.group(1));
+                String dayMatch = matcher.group(2);
+                String hourMatch = matcher.group(4);
+                String minuteMatch = matcher.group(5);
+                String secondMatch = matcher.group(6);
+                String fractionMatch = matcher.group(7);
+                if (dayMatch != null || hourMatch != null || minuteMatch != null || secondMatch != null) {
+                    long daysAsSecs = parseNumber(text, dayMatch, SECONDS_PER_DAY, "days");
+                    long hoursAsSecs = parseNumber(text, hourMatch, SECONDS_PER_HOUR, "hours");
+                    long minsAsSecs = parseNumber(text, minuteMatch, SECONDS_PER_MINUTE, "minutes");
+                    long seconds = parseNumber(text, secondMatch, 1, "seconds");
+                    int nanos = parseFraction(text,  fractionMatch, seconds < 0 ? -1 : 1);
+                    try {
+                        return create(negate, daysAsSecs, hoursAsSecs, minsAsSecs, seconds, nanos);
+                    } catch (ArithmeticException ex) {
+                        throw (DateTimeParseException) new DateTimeParseException("Text cannot be parsed to a Duration: overflow", text, 0).initCause(ex);
+                    }
+                }
+            }
+        }
+        throw new DateTimeParseException("Text cannot be parsed to a Duration", text, 0);
+    }
+
+    private static long parseNumber(CharSequence text, String parsed, int multiplier, String errorText) {
+        // regex limits to [-+]?[0-9]+
+        if (parsed == null) {
+            return 0;
+        }
+        try {
+            long val = Long.parseLong(parsed);
+            return Math.multiplyExact(val, multiplier);
+        } catch (NumberFormatException | ArithmeticException ex) {
+            throw (DateTimeParseException) new DateTimeParseException("Text cannot be parsed to a Duration: " + errorText, text, 0).initCause(ex);
+        }
+    }
+
+    private static int parseFraction(CharSequence text, String parsed, int negate) {
+        // regex limits to [0-9]{0,9}
+        if (parsed == null || parsed.length() == 0) {
+            return 0;
         }
-        int dot = numberText.indexOf('.');
         try {
-            if (dot == -1) {
-                // no decimal places
-                if (numberText.startsWith("-0")) {
-                    throw new DateTimeParseException("Duration could not be parsed: " + text, text, 2);
-                }
-                return create(Long.parseLong(numberText), 0);
-            }
-            // decimal places
-            boolean negative = false;
-            if (numberText.charAt(0) == '-') {
-                negative = true;
-            }
-            long secs = Long.parseLong(numberText.substring(0, dot));
-            numberText = numberText.substring(dot + 1);
-            len = numberText.length();
-            if (len == 0 || len > 9 || numberText.charAt(0) == '-' || numberText.charAt(0) == '+') {
-                throw new DateTimeParseException("Duration could not be parsed: " + text, text, 2);
-            }
-            int nanos = Integer.parseInt(numberText);
-            switch (len) {
-                case 1:
-                    nanos *= 100000000;
-                    break;
-                case 2:
-                    nanos *= 10000000;
-                    break;
-                case 3:
-                    nanos *= 1000000;
-                    break;
-                case 4:
-                    nanos *= 100000;
-                    break;
-                case 5:
-                    nanos *= 10000;
-                    break;
-                case 6:
-                    nanos *= 1000;
-                    break;
-                case 7:
-                    nanos *= 100;
-                    break;
-                case 8:
-                    nanos *= 10;
-                    break;
+            parsed = (parsed + "000000000").substring(0, 9);
+            return Integer.parseInt(parsed) * negate;
+        } catch (NumberFormatException | ArithmeticException ex) {
+            throw (DateTimeParseException) new DateTimeParseException("Text cannot be parsed to a Duration: fraction", text, 0).initCause(ex);
+        }
             }
-            return negative ? ofSeconds(secs, -nanos) : create(secs, nanos);
 
-        } catch (ArithmeticException | NumberFormatException ex) {
-            throw new DateTimeParseException("Duration could not be parsed: " + text, text, 2, ex);
+    private static Duration create(boolean negate, long daysAsSecs, long hoursAsSecs, long minsAsSecs, long secs, int nanos) {
+        long seconds = Math.addExact(daysAsSecs, Math.addExact(hoursAsSecs, Math.addExact(minsAsSecs, secs)));
+        if (negate) {
+            return ofSeconds(seconds, nanos).negated();
         }
+        return ofSeconds(seconds, nanos);
     }
 
     //-----------------------------------------------------------------------
     /**
      * Obtains an instance of {@code Duration} using seconds and nanoseconds.

@@ -420,33 +463,70 @@
         this.nanos = nanos;
     }
 
     //-----------------------------------------------------------------------
     /**
-     * Checks if this duration is zero length.
+     * Gets the value of the requested unit.
      * <p>
-     * A {@code Duration} represents a directed distance between two points on
-     * the time-line and can therefore be positive, zero or negative.
-     * This method checks whether the length is zero.
+     * This returns a value for each of the two supported units,
+     * {@link ChronoUnit#SECONDS SECONDS} and {@link ChronoUnit#NANOS NANOS}.
+     * All other units throw an exception.
+     *
+     * @param unit the {@code TemporalUnit} for which to return the value
+     * @return the long value of the unit
+     * @throws DateTimeException if the unit is not supported
+     */
+    @Override
+    public long get(TemporalUnit unit) {
+        if (unit == SECONDS) {
+            return seconds;
+        } else if (unit == NANOS) {
+            return nanos;
+        } else {
+            throw new DateTimeException("Unsupported unit: " + unit.getName());
+        }
+    }
+
+    /**
+     * Gets the set of units supported by this duration.
+     * <p>
+     * The supported units are {@link ChronoUnit#SECONDS SECONDS},
+     * and {@link ChronoUnit#NANOS NANOS}.
+     * They are returned in the order seconds, nanos.
+     * <p>
+     * This set can be used in conjunction with {@link #get(TemporalUnit)}
+     * to access the entire state of the period.
      *
-     * @return true if this duration has a total length equal to zero
+     * @return a list containing the seconds and nanos units, not null
      */
-    public boolean isZero() {
-        return (seconds | nanos) == 0;
+    @Override
+    public List<TemporalUnit> getUnits() {
+        return DurationUnits.UNITS;
+    }
+
+    /**
+     * Private class to delay initialization of this list until needed.
+     * The circular dependency between Duration and ChronoUnit prevents
+     * the simple initialization in Duration.
+     */
+    private static class DurationUnits {
+        final static List<TemporalUnit> UNITS =
+                Collections.unmodifiableList(Arrays.<TemporalUnit>asList(SECONDS, NANOS));
     }
 
+    //-----------------------------------------------------------------------
     /**
-     * Checks if this duration is positive, excluding zero.
+     * Checks if this duration is zero length.
      * <p>
      * A {@code Duration} represents a directed distance between two points on
      * the time-line and can therefore be positive, zero or negative.
-     * This method checks whether the length is greater than zero.
+     * This method checks whether the length is zero.
      *
-     * @return true if this duration has a total length greater than zero
+     * @return true if this duration has a total length equal to zero
      */
-    public boolean isPositive() {
-        return seconds >= 0 && ((seconds | nanos) != 0);
+    public boolean isZero() {
+        return (seconds | nanos) == 0;
     }
 
     /**
      * Checks if this duration is negative, excluding zero.
      * <p>

@@ -497,10 +577,43 @@
         return nanos;
     }
 
     //-----------------------------------------------------------------------
     /**
+     * Returns a copy of this duration with the specified amount of seconds.
+     * <p>
+     * This returns a duration with the specified seconds, retaining the
+     * nano-of-second part of this duration.
+     * <p>
+     * This instance is immutable and unaffected by this method call.
+     *
+     * @param seconds  the seconds to represent, may be negative
+     * @return a {@code Duration} based on this period with the requested seconds, not null
+     */
+    public Duration withSeconds(long seconds) {
+        return create(seconds, nanos);
+    }
+
+    /**
+     * Returns a copy of this duration with the specified nano-of-second.
+     * <p>
+     * This returns a duration with the specified nano-of-second, retaining the
+     * seconds part of this duration.
+     * <p>
+     * This instance is immutable and unaffected by this method call.
+     *
+     * @param nanoOfSecond  the nano-of-second to represent, from 0 to 999,999,999
+     * @return a {@code Duration} based on this period with the requested nano-of-second, not null
+     * @throws DateTimeException if the nano-of-second is invalid
+     */
+    public Duration withNanos(int nanoOfSecond) {
+        NANO_OF_SECOND.checkValidIntValue(nanoOfSecond);
+        return create(seconds, nanoOfSecond);
+    }
+
+    //-----------------------------------------------------------------------
+    /**
      * Returns a copy of this duration with the specified duration added.
      * <p>
      * This instance is immutable and unaffected by this method call.
      *
      * @param duration  the duration to add, positive or negative, not null

@@ -550,10 +663,52 @@
         return plusSeconds(duration.getSeconds()).plusNanos(duration.getNano());
     }
 
     //-----------------------------------------------------------------------
     /**
+     * Returns a copy of this duration with the specified duration in standard 24 hour days added.
+     * <p>
+     * The number of days is multiplied by 86400 to obtain the number of seconds to add.
+     * This is based on the standard definition of a day as 24 hours.
+     * <p>
+     * This instance is immutable and unaffected by this method call.
+     *
+     * @param daysToAdd  the days to add, positive or negative
+     * @return a {@code Duration} based on this duration with the specified days added, not null
+     * @throws ArithmeticException if numeric overflow occurs
+     */
+    public Duration plusDays(long daysToAdd) {
+        return plus(Math.multiplyExact(daysToAdd, SECONDS_PER_DAY), 0);
+    }
+
+    /**
+     * Returns a copy of this duration with the specified duration in hours added.
+     * <p>
+     * This instance is immutable and unaffected by this method call.
+     *
+     * @param hoursToAdd  the hours to add, positive or negative
+     * @return a {@code Duration} based on this duration with the specified hours added, not null
+     * @throws ArithmeticException if numeric overflow occurs
+     */
+    public Duration plusHours(long hoursToAdd) {
+        return plus(Math.multiplyExact(hoursToAdd, SECONDS_PER_HOUR), 0);
+    }
+
+    /**
+     * Returns a copy of this duration with the specified duration in minutes added.
+     * <p>
+     * This instance is immutable and unaffected by this method call.
+     *
+     * @param minutesToAdd  the minutes to add, positive or negative
+     * @return a {@code Duration} based on this duration with the specified minutes added, not null
+     * @throws ArithmeticException if numeric overflow occurs
+     */
+    public Duration plusMinutes(long minutesToAdd) {
+        return plus(Math.multiplyExact(minutesToAdd, SECONDS_PER_MINUTE), 0);
+    }
+
+    /**
      * Returns a copy of this duration with the specified duration in seconds added.
      * <p>
      * This instance is immutable and unaffected by this method call.
      *
      * @param secondsToAdd  the seconds to add, positive or negative

@@ -649,10 +804,56 @@
         return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit));
     }
 
     //-----------------------------------------------------------------------
     /**
+     * Returns a copy of this duration with the specified duration in standard 24 hour days subtracted.
+     * <p>
+     * The number of days is multiplied by 86400 to obtain the number of seconds to subtract.
+     * This is based on the standard definition of a day as 24 hours.
+     * <p>
+     * This instance is immutable and unaffected by this method call.
+     *
+     * @param daysToSubtract  the days to subtract, positive or negative
+     * @return a {@code Duration} based on this duration with the specified days subtracted, not null
+     * @throws ArithmeticException if numeric overflow occurs
+     */
+    public Duration minusDays(long daysToSubtract) {
+        return (daysToSubtract == Long.MIN_VALUE ? plusDays(Long.MAX_VALUE).plusDays(1) : plusDays(-daysToSubtract));
+    }
+
+    /**
+     * Returns a copy of this duration with the specified duration in hours subtracted.
+     * <p>
+     * The number of hours is multiplied by 3600 to obtain the number of seconds to subtract.
+     * <p>
+     * This instance is immutable and unaffected by this method call.
+     *
+     * @param hoursToSubtract  the hours to subtract, positive or negative
+     * @return a {@code Duration} based on this duration with the specified hours subtracted, not null
+     * @throws ArithmeticException if numeric overflow occurs
+     */
+    public Duration minusHours(long hoursToSubtract) {
+        return (hoursToSubtract == Long.MIN_VALUE ? plusHours(Long.MAX_VALUE).plusHours(1) : plusHours(-hoursToSubtract));
+    }
+
+    /**
+     * Returns a copy of this duration with the specified duration in minutes subtracted.
+     * <p>
+     * The number of hours is multiplied by 60 to obtain the number of seconds to subtract.
+     * <p>
+     * This instance is immutable and unaffected by this method call.
+     *
+     * @param minutesToSubtract  the minutes to subtract, positive or negative
+     * @return a {@code Duration} based on this duration with the specified minutes subtracted, not null
+     * @throws ArithmeticException if numeric overflow occurs
+     */
+    public Duration minusMinutes(long minutesToSubtract) {
+        return (minutesToSubtract == Long.MIN_VALUE ? plusMinutes(Long.MAX_VALUE).plusMinutes(1) : plusMinutes(-minutesToSubtract));
+    }
+
+    /**
      * Returns a copy of this duration with the specified duration in seconds subtracted.
      * <p>
      * This instance is immutable and unaffected by this method call.
      *
      * @param secondsToSubtract  the seconds to subtract, positive or negative

@@ -714,12 +915,11 @@
      * <p>
      * This instance is immutable and unaffected by this method call.
      *
      * @param divisor  the value to divide the duration by, positive or negative, not zero
      * @return a {@code Duration} based on this duration divided by the specified divisor, not null
-     * @throws ArithmeticException if the divisor is zero
-     * @throws ArithmeticException if numeric overflow occurs
+     * @throws ArithmeticException if the divisor is zero or if numeric overflow occurs
      */
     public Duration dividedBy(long divisor) {
         if (divisor == 0) {
             throw new ArithmeticException("Cannot divide by zero");
         }

@@ -792,75 +992,118 @@
      * <p>
      * This returns a temporal object of the same observable type as the input
      * with this duration added.
      * <p>
      * In most cases, it is clearer to reverse the calling pattern by using
-     * {@link Temporal#plus(TemporalAdder)}.
+     * {@link Temporal#plus(TemporalAmount)}.
      * <pre>
      *   // these two lines are equivalent, but the second approach is recommended
      *   dateTime = thisDuration.addTo(dateTime);
      *   dateTime = dateTime.plus(thisDuration);
      * </pre>
      * <p>
-     * A {@code Duration} can only be added to a {@code Temporal} that
-     * represents an instant and can supply {@link ChronoField#INSTANT_SECONDS}.
+     * The calculation will add the seconds, then nanos.
+     * Only non-zero amounts will be added.
      * <p>
      * This instance is immutable and unaffected by this method call.
      *
      * @param temporal  the temporal object to adjust, not null
      * @return an object of the same type with the adjustment made, not null
      * @throws DateTimeException if unable to add
      * @throws ArithmeticException if numeric overflow occurs
      */
     @Override
     public Temporal addTo(Temporal temporal) {
-        long instantSecs = temporal.getLong(INSTANT_SECONDS);
-        long instantNanos = temporal.getLong(NANO_OF_SECOND);
-        instantSecs = Math.addExact(instantSecs, seconds);
-        instantNanos = Math.addExact(instantNanos, nanos);
-        instantSecs = Math.addExact(instantSecs, Math.floorDiv(instantNanos, NANOS_PER_SECOND));
-        instantNanos = Math.floorMod(instantNanos, NANOS_PER_SECOND);
-        return temporal.with(INSTANT_SECONDS, instantSecs).with(NANO_OF_SECOND, instantNanos);
+        if (seconds != 0) {
+            temporal = temporal.plus(seconds, SECONDS);
+        }
+        if (nanos != 0) {
+            temporal = temporal.plus(nanos, NANOS);
+        }
+        return temporal;
     }
 
     /**
      * Subtracts this duration from the specified temporal object.
      * <p>
      * This returns a temporal object of the same observable type as the input
      * with this duration subtracted.
      * <p>
      * In most cases, it is clearer to reverse the calling pattern by using
-     * {@link Temporal#minus(TemporalSubtractor)}.
+     * {@link Temporal#minus(TemporalAmount)}.
      * <pre>
      *   // these two lines are equivalent, but the second approach is recommended
      *   dateTime = thisDuration.subtractFrom(dateTime);
      *   dateTime = dateTime.minus(thisDuration);
      * </pre>
      * <p>
-     * A {@code Duration} can only be subtracted from a {@code Temporal} that
-     * represents an instant and can supply {@link ChronoField#INSTANT_SECONDS}.
+     * The calculation will subtract the seconds, then nanos.
+     * Only non-zero amounts will be added.
      * <p>
      * This instance is immutable and unaffected by this method call.
      *
      * @param temporal  the temporal object to adjust, not null
      * @return an object of the same type with the adjustment made, not null
      * @throws DateTimeException if unable to subtract
      * @throws ArithmeticException if numeric overflow occurs
      */
     @Override
     public Temporal subtractFrom(Temporal temporal) {
-        long instantSecs = temporal.getLong(INSTANT_SECONDS);
-        long instantNanos = temporal.getLong(NANO_OF_SECOND);
-        instantSecs = Math.subtractExact(instantSecs, seconds);
-        instantNanos = Math.subtractExact(instantNanos, nanos);
-        instantSecs = Math.addExact(instantSecs, Math.floorDiv(instantNanos, NANOS_PER_SECOND));
-        instantNanos = Math.floorMod(instantNanos, NANOS_PER_SECOND);
-        return temporal.with(INSTANT_SECONDS, instantSecs).with(NANO_OF_SECOND, instantNanos);
+        if (seconds != 0) {
+            temporal = temporal.minus(seconds, SECONDS);
+        }
+        if (nanos != 0) {
+            temporal = temporal.minus(nanos, NANOS);
+        }
+        return temporal;
     }
 
     //-----------------------------------------------------------------------
     /**
+     * Gets the number of minutes in this duration.
+     * <p>
+     * This returns the total number of minutes in the duration by dividing the
+     * number of seconds by 86400.
+     * This is based on the standard definition of a day as 24 hours.
+     * <p>
+     * This instance is immutable and unaffected by this method call.
+     *
+     * @return the number of minutes in the duration, may be negative
+     */
+    public long toDays() {
+        return seconds / SECONDS_PER_DAY;
+    }
+
+    /**
+     * Gets the number of minutes in this duration.
+     * <p>
+     * This returns the total number of minutes in the duration by dividing the
+     * number of seconds by 3600.
+     * <p>
+     * This instance is immutable and unaffected by this method call.
+     *
+     * @return the number of minutes in the duration, may be negative
+     */
+    public long toHours() {
+        return seconds / SECONDS_PER_HOUR;
+    }
+
+    /**
+     * Gets the number of minutes in this duration.
+     * <p>
+     * This returns the total number of minutes in the duration by dividing the
+     * number of seconds by 60.
+     * <p>
+     * This instance is immutable and unaffected by this method call.
+     *
+     * @return the number of minutes in the duration, may be negative
+     */
+    public long toMinutes() {
+        return seconds / SECONDS_PER_MINUTE;
+    }
+
+    /**
      * Converts this duration to the total length in milliseconds.
      * <p>
      * If this duration is too large to fit in a {@code long} milliseconds, then an
      * exception is thrown.
      * <p>

@@ -885,11 +1128,11 @@
      *
      * @return the total length of the duration in nanoseconds
      * @throws ArithmeticException if numeric overflow occurs
      */
     public long toNanos() {
-        long millis = Math.multiplyExact(seconds, 1000_000_000);
+        long millis = Math.multiplyExact(seconds, NANOS_PER_SECOND);
         millis = Math.addExact(millis, nanos);
         return millis;
     }
 
     //-----------------------------------------------------------------------

@@ -909,34 +1152,10 @@
             return cmp;
         }
         return nanos - otherDuration.nanos;
     }
 
-    /**
-     * Checks if this duration is greater than the specified {@code Duration}.
-     * <p>
-     * The comparison is based on the total length of the durations.
-     *
-     * @param otherDuration  the other duration to compare to, not null
-     * @return true if this duration is greater than the specified duration
-     */
-    public boolean isGreaterThan(Duration otherDuration) {
-        return compareTo(otherDuration) > 0;
-    }
-
-    /**
-     * Checks if this duration is less than the specified {@code Duration}.
-     * <p>
-     * The comparison is based on the total length of the durations.
-     *
-     * @param otherDuration  the other duration to compare to, not null
-     * @return true if this duration is less than the specified duration
-     */
-    public boolean isLessThan(Duration otherDuration) {
-        return compareTo(otherDuration) < 0;
-    }
-
     //-----------------------------------------------------------------------
     /**
      * Checks if this duration is equal to the specified {@code Duration}.
      * <p>
      * The comparison is based on the total length of the durations.

@@ -968,33 +1187,61 @@
     }
 
     //-----------------------------------------------------------------------
     /**
      * A string representation of this duration using ISO-8601 seconds
-     * based representation, such as {@code PT12.345S}.
+     * based representation, such as {@code PT8H6M12.345S}.
      * <p>
-     * The format of the returned string will be {@code PTnS} where n is
-     * the seconds and fractional seconds of the duration.
+     * The format of the returned string will be {@code PTnHnMnS}, where n is
+     * the relevant hours, minutes or seconds part of the duration.
+     * Any fractional seconds are placed after a decimal point i the seconds section.
+     * If a section has a zero value, it is omitted.
+     * The hours, minutes and seconds will all have the same sign.
+     * <p>
+     * Examples:
+     * <pre>
+     *    "20.345 seconds"                 -> "PT20.345S
+     *    "15 minutes" (15 * 60 seconds)   -> "PT15M"
+     *    "10 hours" (10 * 3600 seconds)   -> "PT10H"
+     *    "2 days" (2 * 86400 seconds)     -> "PT48H"
+     * </pre>
+     * Note that multiples of 24 hours are not output as days to avoid confusion
+     * with {@code Period}.
      *
      * @return an ISO-8601 representation of this duration, not null
      */
     @Override
     public String toString() {
+        if (this == ZERO) {
+            return "PT0S";
+        }
+        long hours = seconds / SECONDS_PER_HOUR;
+        int minutes = (int) ((seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE);
+        int secs = (int) (seconds % SECONDS_PER_MINUTE);
         StringBuilder buf = new StringBuilder(24);
         buf.append("PT");
-        if (seconds < 0 && nanos > 0) {
-            if (seconds == -1) {
+        if (hours != 0) {
+            buf.append(hours).append('H');
+        }
+        if (minutes != 0) {
+            buf.append(minutes).append('M');
+        }
+        if (secs == 0 && nanos == 0 && buf.length() > 2) {
+            return buf.toString();
+        }
+        if (secs < 0 && nanos > 0) {
+            if (secs == -1) {
                 buf.append("-0");
             } else {
-                buf.append(seconds + 1);
+                buf.append(secs + 1);
             }
         } else {
-            buf.append(seconds);
+            buf.append(secs);
         }
         if (nanos > 0) {
             int pos = buf.length();
-            if (seconds < 0) {
+            if (secs < 0) {
                 buf.append(2 * NANOS_PER_SECOND - nanos);
             } else {
                 buf.append(nanos + NANOS_PER_SECOND);
             }
             while (buf.charAt(buf.length() - 1) == '0') {