How to convert from long+TimeUnit to Duration?
Asked Answered
D

3

7

I'm migrating some signatures from f(long dur, TimeUnit timeUnit) to f(Duration duration), and would like to implement the former with the latter.

Being on Java 8, I can't find any API to easily convert the long+TU to a Duration, the only idea that comes to me is to do something ugly like:

static Duration convert(long dur, TimeUnit timeUnit) {
  switch (timeUnit) {
    case DAYS:
      return Duration.ofDays(dur);
    case HOURS:
      /* alternative, but (again) I don't have an easy conversion from TimeUnit -> ChronoUnit */
      return Duration.of(dur, ChronoUnit.HOURS);
    case ..... /* and so on */
  }
}

Or did I miss some API?

Dennadennard answered 16/6, 2022 at 17:50 Comment(0)
D
4

For Java 8 there is a roundabout solution :\ (we unnecessarily convert from our TU -> millis (or whatever unit) -> Duration) like this:

long dur = ...
TimeUnit unit = ...
Duration result = Duration.ofMillis(unit.toMillis(dur));

Caveat emptor with extremely large values though, Long.MAX_VALUE days cannot be correctly converted into long millis (compared to Duration's ctor that does throw when trying to init with such a value):

final long millis = TimeUnit.DAYS.toMillis(Long.MAX_VALUE); // ouch
final Duration dur = Duration.ofMillis(dur);
System.err.println(dur.toDays() == Long.MAX_VALUE); // returns 'false'
Dennadennard answered 20/6, 2022 at 20:21 Comment(5)
Duration's Javadoc states: To achieve this, the class stores a long representing seconds, so actually we got into undefined behaviour with the above corner-case.Dennadennard
Duration holds two long values: one for seconds, another for nanoseconds. There's no issue with conversion of TimeUnit.DAYS to milliseconds (if you're OK with this precision), unless are dealing with astronomical problems (the number of years to cause long overflow in milliseconds should be very large). The real problem is TimeUnit.NANOSECONDS.toMillis(100000) will give 0 - the precision has been truncated to milliseconds. Whether this issue is important depends on your requirements.Moreta
And depending on your data, you can make use of Duration.ofNanos(). To overflow long representing the number of nanoseconds, the duration need to be greater than 290 years. So I guess most likely you can use this option. Fair enough, it's a good find if you don't want to introduce a dependency on a third-party library in your project.Moreta
Right, I concentrated on the upper-bound too much. This could be worked around with using nanos (as in Duration.ofNanos(unit.toNanos(dur))), but then we just reduced arguments to about 100k days (Duration.ofNanos(Long.MAX_VALUE).toDays()). Should just upgrade Java to get rid of this mess.Dennadennard
There are loots boons in higher versions. For instance, modular system of Java 9. And more longer you're postponing the migration of the project, the more difficult it would be in the future.Moreta
M
11

Java 9+

You can use static method Duration.of(long, TemporalUnit).

It expects an amount as long, and a TemporalUnit, so you need to convert the TimeUnit into ChronoUnit.

static Duration convert(long dur, TimeUnit timeUnit) {
    return Duration.of(dur, timeUnit.toChronoUnit());
}

Method toChronoUnit() was introduced in JDK version 9.

Java 8

With Java 8 you can translate TimeUnit into ChronoUnit using ThreeTen library's utility method Temporals.chronoUnit​(TimeUnit).

If you don't want to introduce a dependency on this library in your project, you can make use of the utility method provided in the answer by Paul.

Moreta answered 16/6, 2022 at 18:4 Comment(4)
Not a Java 8 API I'm afraid :( Guess that's the proper answer anyways, and I need to keep my converter util. EDIT: yeah, toChronoUnit is since 9.Dennadennard
@AdamKotwasinski You're right, the convention between TimeUnit and ChronoUnit was introduced in Java 9. With Java 8 we can't translate one into another using built-in features only, maybe there are some third-party libraries that can facilitate that.Moreta
@AdamKotwasinski ThreeTen library provides a utility method for that, updated the answer.Moreta
The conversion from TimeUnit to ChronoUnit is straightforward and the JDK implementation is clean. Rather than introducing a dependency for this simple conversion (in Java 8) I would write a static utility method based on the JDK source code (which I pasted into my answer).Dunseath
D
4

For Java 8 there is a roundabout solution :\ (we unnecessarily convert from our TU -> millis (or whatever unit) -> Duration) like this:

long dur = ...
TimeUnit unit = ...
Duration result = Duration.ofMillis(unit.toMillis(dur));

Caveat emptor with extremely large values though, Long.MAX_VALUE days cannot be correctly converted into long millis (compared to Duration's ctor that does throw when trying to init with such a value):

final long millis = TimeUnit.DAYS.toMillis(Long.MAX_VALUE); // ouch
final Duration dur = Duration.ofMillis(dur);
System.err.println(dur.toDays() == Long.MAX_VALUE); // returns 'false'
Dennadennard answered 20/6, 2022 at 20:21 Comment(5)
Duration's Javadoc states: To achieve this, the class stores a long representing seconds, so actually we got into undefined behaviour with the above corner-case.Dennadennard
Duration holds two long values: one for seconds, another for nanoseconds. There's no issue with conversion of TimeUnit.DAYS to milliseconds (if you're OK with this precision), unless are dealing with astronomical problems (the number of years to cause long overflow in milliseconds should be very large). The real problem is TimeUnit.NANOSECONDS.toMillis(100000) will give 0 - the precision has been truncated to milliseconds. Whether this issue is important depends on your requirements.Moreta
And depending on your data, you can make use of Duration.ofNanos(). To overflow long representing the number of nanoseconds, the duration need to be greater than 290 years. So I guess most likely you can use this option. Fair enough, it's a good find if you don't want to introduce a dependency on a third-party library in your project.Moreta
Right, I concentrated on the upper-bound too much. This could be worked around with using nanos (as in Duration.ofNanos(unit.toNanos(dur))), but then we just reduced arguments to about 100k days (Duration.ofNanos(Long.MAX_VALUE).toDays()). Should just upgrade Java to get rid of this mess.Dennadennard
There are loots boons in higher versions. For instance, modular system of Java 9. And more longer you're postponing the migration of the project, the more difficult it would be in the future.Moreta
D
2

You're on the right track. Since you think the right track is ugly the solution is to hide the ugliness! Here's the implementation of TimeUnit.toChronoUnit() from OpenJDK:

/**
 * Converts this {@code TimeUnit} to the equivalent {@code ChronoUnit}.
 *
 * @return the converted equivalent ChronoUnit
 * @since 9
 */
public ChronoUnit toChronoUnit() {
    switch (this) {
    case NANOSECONDS:  return ChronoUnit.NANOS;
    case MICROSECONDS: return ChronoUnit.MICROS;
    case MILLISECONDS: return ChronoUnit.MILLIS;
    case SECONDS:      return ChronoUnit.SECONDS;
    case MINUTES:      return ChronoUnit.MINUTES;
    case HOURS:        return ChronoUnit.HOURS;
    case DAYS:         return ChronoUnit.DAYS;
    default: throw new AssertionError();
    }
}

To keep your code cleaner I would implement a static utility method based on above (i.e. pass in a TimeUnit parameter) and get the ChronoUnit without doing a conversion in each case. You'll end up with 1 call to Duration.of(long amount, TemporalUnit unit) and your code will be as beautiful as if you were using Java 9+!

Dunseath answered 25/6, 2022 at 20:2 Comment(1)
Indeed, very clean and straightforward, added a link to my answer.Moreta

© 2022 - 2024 — McMap. All rights reserved.