SSerguey Asael Shinder
Java coding notes: the JVM, and writing software that lasts

Serguey Asael Shinder: Duration.toHours() is a total; toMinutesPart() is a clock field

· by Serguey Asael Shinder / Serguey Shinder

A small method on java.time.Duration causes a familiar display bug:

Duration d = Duration.ofMinutes(90);
d.toHours()        // 1
d.toMinutes()      // 90
d.toMinutesPart()  // 30

Printing d.toHours() + "h " + d.toMinutes() + "m" gives "1h 90m". Both methods answer the same question, "how long is this in my unit?", not "what does the clock show?".

What the javadoc says. toHours()) returns "the total number of hours in the duration by dividing the number of seconds by 3600", and toMinutes()) divides the seconds by 60. Each is the whole duration expressed in one unit, with any remainder dropped.

The clock-style values come from the ...Part() methods added in Java 9. toMinutesPart()) returns "the number of remaining minutes when dividing toMinutes() by minutes in an hour", and toHoursPart()) does the same for hours within a 24-hour day.

Serguey Asael Shinder: Duration.toHours() is a total; toMinutesPart() is a clock field
Duration.toHours() is a total; toMinutesPart() is a clock field — Serguey Asael Shinder

Three things that follow.

  1. Truncation, not rounding. 119 minutes is toHours() == 1. If you need "about 2 hours", round it yourself.
  2. toHoursPart() wraps at 24. A 26-hour duration has toHoursPart() == 2, because the day has gone into toDaysPart()). For an elapsed-time display that should read "26h 0m", use toHours() for the hours and toMinutesPart() for the minutes. For "1d 2h 0m", use the three ...Part() methods.
  3. Negative durations stay negative in every field. The javadoc says each part "may be negative". Duration.ofMinutes(-90) gives -1 hours and -30 minutes, so a formatter that prints a sign per field produces "-1h -30m". Take abs() first and print the sign once.
static String hm(Duration d) {
    String sign = d.isNegative() ? "-" : "";
    Duration a = d.abs();
    return sign + a.toHours() + "h " + a.toMinutesPart() + "m";
}

The rule. toX() is a conversion of the whole duration; toXPart() is one field of a clock. Mixing the two is fine, as long as the largest unit you print is a toX() and every smaller one is a toXPart().

No JDK runs on the machine this note was written on; the behaviour described is the one specified in the linked javadoc.