Why can't I get a duration in minutes or hours in java.time?
Asked Answered
D

6

93

Of the Duration class in the new JSR 310 date API (java.time package) available in Java 8 and later, the javadoc says :

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 DAYS unit can be used and is treated as exactly equal to 24 hours, thus ignoring daylight savings effects.

So, why does the following code crash ?

Duration duration = Duration.ofSeconds(3000);
System.out.println(duration.get(ChronoUnit.MINUTES));

This raises an UnsupportedTemporalTypeException :

java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Minutes
    at java.time.Duration.get(Duration.java:537)

So what is the recommended way to extract minutes and hours from a duration object ? Do we have to make the calculation ourselves from the number of seconds ? Why was it implemented that way ?

Dianoia answered 30/6, 2014 at 13:28 Comment(5)
#20827547 (the not-accepted answer)Platitudinous
FYI, there is a project to extend java.time, ThreeTen Extra. But I don't think it presently has anything to help you.Kela
Seems that JSR 310 java.time is not all that we hoped. It is not a complete and superior replacement for Joda-Time. While java.time has its strengths, so does Joda-Time. Joda-Time offers three classes for handling a span of time (Duration, Period, Interval), has good built-in support for ISO 8601 string representation of spans, and good support for defining your own formatters.Kela
Yeah, I was still using the old Date API, waiting for the 310, but now I am thinking, will I go with Joda instead. But the 310 API is still so much better than the old Date one!Dianoia
@PierreHenry Fortunately, we can use both java.time and Joda-Time, applying either in the case where its strengths have merit. Just be careful with your import statements as they share a few common class names (LocalDate etc.). For clarity, I suggest never mixing java.time and Joda-Time in the same chunk of code. As for those old java.util.Date & .Calendar & SimpleDateFormat, definitely avoid them.Kela
S
119

"Why was it implemented that way?"

Other answers deal with the toXxx() methods that allow the hours/minutes to be queried. I'll try to deal with the why.

The TemporalAmount interface and get(TemporalUnit) method was added fairly late in the process. I personally was not entirely convinced that we had enough evidence of the right way to work the design in that area, but was slightly arm-twisted to add TemporalAmount. I believe that in doing so we slightly confused the API.

In hindsight, I believe that TemporalAmount contains the right methods, but I believe that get(TemporalUnit) should have had a different method name. The reason is that get(TemporalUnit) is essentially a framework-level method - it is not designed for day-today use. Unfortunately the method name get does not imply this, resulting in bugs like calling get(ChronoUnit.MINUTES) on Duration.

So, the way to think of get(TemporalUnit) is to imagine a low-level framework viewing the amount as a Map<TemporalUnit, Long> where Duration is a Map of size two with keys of SECONDS and NANOS.

In the same, way, Period is viewed from the low-level frameworks as a Map of size three - DAYS, MONTHS and YEARS (which fortunately has less chance of errors).

Overall, the best advice for application code is to ignore the method get(TemporalUnit). Use getSeconds(), getNano(), toHours() and toMinutes() instead.

Finally, one way to get "hh:mm:ss" from a Duration is to do:

LocalTime.MIDNIGHT.plus(duration).format(DateTimeFormatter.ofPattern("HH:mm:ss"))

Not pretty at all, but it does work for durations less than one day.

New to…Part methods in Java 9

JDK-8142936 issue now implemented in Java 9, adding the following methods to access each part of a Duration.

  • toDaysPart
  • toHoursPart
  • toMinutesPart
  • toSecondsPart
  • toMillisPart
  • toNanosPart
Stansberry answered 30/6, 2014 at 22:46 Comment(2)
Thanks for the interesting insider view on the why, and the useful little formatting trick :)Dianoia
Difference between toDays and toDaysPart? Documentation is the same. Is it just because for other units like hours and minutes it make sense to have both methods, so they also added two methods for days with the same effect?Frawley
A
38

The documentation says:

This returns a value for each of the two supported units, SECONDS and NANOS. All other units throw an exception.

So, best guess answer -- that's the way they designed it.

You can use some of the other methods to get it in hours:

long hours = duration.toHours();

or minutes:

long minutes = duration.toMinutes();
Acie answered 30/6, 2014 at 13:52 Comment(3)
Interesting. I actually didn't see those toXXX methods. But they convert the total number of seconds to a number of the required unit, without taking into account the superior units. E.g : for a duration of 10000 seconds, the toMinutes() method will return 166. Not so useful if you need do display hh:mm:ss, but can be used as part of a more complete calculation method.Dianoia
@PierreHenry Well, it's not exactly in the format you want, but you can use duration.toString() to get the duration as a human-readable valueAcie
To get just the hours component (without the days): int hours = (int) ( duration.toHours() % 24L );Judson
J
14

To get the hour/minute/second components in a "normalised" way, you need to calculate them manually - the code below is essentially copied from the Duration#toString method:

Duration duration = Duration.ofSeconds(3000);
long hours = duration.toHours();
int minutes = (int) ((duration.getSeconds() % (60 * 60)) / 60);
int seconds = (int) (duration.getSeconds() % 60);
System.out.println(hours + ":" + minutes + ":" + seconds);
Jorie answered 30/6, 2014 at 15:52 Comment(1)
I can't find a Duration API to do this. So I follow this answer.Sennight
D
11

I'd like to add to bigt's answer (+1) that pointed out the useful toHours, toMinutes and other conversion methods. The Duration specification says:

This class models a quantity or amount of time in terms of seconds and nanoseconds. [...]

The range of a duration requires the storage of a number larger than a long. To achieve this, the class stores a long representing seconds and an int representing nanosecond-of-second, which will always be between 0 and 999,999,999.

There are a variety of getter methods such as getSeconds, getNano, and get(TemporalUnit). Given that a duration is represented as a (seconds, nanos) pair, it's clear why get(TemporalUnit) is restricted to seconds and nanos. These getter methods extract data directly from the duration instance and are lossless.

By contrast, there is a variety of to-methods including toDays, toHours, toMillis toMinutes, and toNanos that do conversion on the duration value. These conversions are lossy in that they involve truncation of data, or they might throw an exception if the duration value cannot be represented in the requested format.

It's clearly part of the design that the get-methods extract data without conversion and the to-methods perform conversions of some sort.

Dionne answered 30/6, 2014 at 19:19 Comment(0)
S
5

Remove Hourse then get minutes

long hours = attendanceDuration.toHours(); long minutes = attendanceDuration.minusHours(hours).toMinutes();

Stability answered 11/2, 2019 at 13:38 Comment(0)
F
-1

By below code i got this duration output:

1 day, 2 hours, 5 minutes

code example:

private String getDurationAsString(Duration duration)
{
    StringBuilder durationAsStringBuilder = new StringBuilder();
    if (duration.toDays() > 0)
    {
        String postfix = duration.toDays() == 1 ? "" : "s";
        durationAsStringBuilder.append(duration.toDays() + " day");
        durationAsStringBuilder.append(postfix);
    }

    duration = duration.minusDays(duration.toDays());
    long hours = duration.toHours();
    if (hours > 0)
    {
        String prefix = Utils.isEmpty(durationAsStringBuilder.toString()) ? "" : ", ";
        String postfix = hours == 1 ? "" : "s";
        durationAsStringBuilder.append(prefix);
        durationAsStringBuilder.append(hours + " hour");
        durationAsStringBuilder.append(postfix);
    }

    duration = duration.minusHours(duration.toHours());
    long minutes = duration.toMinutes();
    if (minutes > 0)
    {
        String prefix = Utils.isEmpty(durationAsStringBuilder.toString()) ? "" : ", ";
        String postfix = minutes == 1 ? "" : "s";
        durationAsStringBuilder.append(prefix);
        durationAsStringBuilder.append(minutes + " minute");
        durationAsStringBuilder.append(postfix);
    }

    return durationAsStringBuilder.toString();
}

Explanation:

By using Duration interface you can easily find the exact String display you want. you just need to reduce the bigger TemporalUnit from current duration.

for example:

  1. calculate days
  2. reduce days from duration
  3. calculate hours
  4. reduce hours from duration etc... until u get duration = 0
Function answered 5/3, 2018 at 18:31 Comment(2)
Which 'Utils' are you using?Soke
@Soke Just using my prive "Utils" class to check if string is null or empty, nothing specialFunction

© 2022 - 2024 — McMap. All rights reserved.