5

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?

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
Adam Kotwasinski
  • 4,377
  • 3
  • 17
  • 40

3 Answers3

9

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.

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
  • 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. – Adam Kotwasinski Jun 16 '22 at 18:07
  • @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. – Alexander Ivanchenko Jun 16 '22 at 18:17
  • 2
    @AdamKotwasinski [ThreeTen](https://github.com/ThreeTen/threeten-extra) library provides a utility method for that, updated the answer. – Alexander Ivanchenko Jun 16 '22 at 18:30
  • 1
    The conversion from `TimeUnit` to `ChronoUnit` is straightforward and [the JDK implementation](https://github.com/openjdk/jdk/blob/739769c8fc4b496f08a92225a12d07414537b6c0/src/java.base/share/classes/java/util/concurrent/TimeUnit.java#L456) 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). – Paul Jun 25 '22 at 20:11
3

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'
Adam Kotwasinski
  • 4,377
  • 3
  • 17
  • 40
  • 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. – Adam Kotwasinski Jun 20 '22 at 21:12
  • 1
    `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. – Alexander Ivanchenko Jun 20 '22 at 22:13
  • 1
    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. – Alexander Ivanchenko Jun 20 '22 at 22:28
  • 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. – Adam Kotwasinski Jun 20 '22 at 22:28
  • 1
    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. – Alexander Ivanchenko Jun 20 '22 at 22:35
1

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+!

Paul
  • 19,704
  • 14
  • 78
  • 96