如何将long + TimeUnit转换为Duration?

5

我正在将一些签名从f(long dur, TimeUnit timeUnit)迁移到f(Duration duration),并希望用后者实现前者。

由于使用的是Java 8,我找不到任何API可以轻松地将long+TU转换为Duration。唯一想到的办法就是做一些难看的事情,比如:

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 */
  }
}

或者我错过了一些API?

3个回答

9

Java 9+

在Java 9及以上版本中,您可以使用静态方法Duration.of(long, TemporalUnit)

它需要一个long类型的数值和一个TemporalUnit时间单位,所以您需要将TimeUnit转换为ChronoUnit

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

toChronoUnit() 方法是在 JDK 9 版本中引入的。

Java 8

Java 8 中,您可以使用 ThreeTen 库的实用方法 Temporals.chronoUnit(TimeUnit)TimeUnit 转换成 ChronoUnit

如果您不想在项目中引入此库的依赖项,则可以使用 Paul 的回答中提供的实用方法


很抱歉,这不是Java 8 API :( 不过我想这应该是正确的答案,而且我需要保留我的转换器工具。编辑:是的,toChronoUnit是从9开始的。 - Adam Kotwasinski
@AdamKotwasinski 你说得对,TimeUnitChronoUnit之间的约定是在Java 9中引入的。在Java 8中,我们无法仅使用内置功能将一个转换为另一个,也许有一些第三方库可以简化这个过程。 - Alexander Ivanchenko
2
@AdamKotwasinski ThreeTen 库提供了一个实用方法,已更新答案。 - Alexander Ivanchenko
1
TimeUnitChronoUnit的转换很简单,JDK实现非常干净。在Java 8中,我不会为这个简单的转换引入依赖项,而是会编写一个基于JDK源代码的静态实用程序方法(我将其粘贴到我的答案中)。 - Paul

3

针对Java 8,有一个绕行的解决方案我们可以不必要地将TU转换为毫秒(或其他单位)再转换为Duration,如下所示:

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

然而,对于非常大的值要谨慎,Long.MAX_VALUE 天无法正确转换为 long 毫秒数(与Duration的构造函数不同,后者在尝试使用此值进行初始化时会抛出异常):
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'

Duration的Javadoc说明:“为了实现这一点,该类存储一个表示秒数的长整型”,因此,实际上我们在上述边缘情况下进入了未定义的行为。 - Adam Kotwasinski
1
Duration 包含两个 long 值:一个表示 ,另一个表示 纳秒。将 TimeUnit.DAYS 转换为 毫秒 没有问题(如果您对此精度满意),除非涉及天文问题(导致 毫秒long 溢出的年数应该非常大)。真正的问题是 TimeUnit.NANOSECONDS.toMillis(100000) 将返回 0 - 精度已被截断为 毫秒。这个问题是否重要取决于您的需求。 - Alexander Ivanchenko
1
根据你的数据,你可以利用Duration.ofNanos()。要使表示纳秒数量的long溢出,持续时间需要大于290年。所以我猜大多数情况下你可以使用这个选项。很好,如果你不想在项目中引入第三方库的依赖,这是一个不错的选择。 - Alexander Ivanchenko
没错,我过于关注上限了。这可以通过使用纳秒来解决(例如 Duration.ofNanos(unit.toNanos(dur))),但是这样我们只能将参数减少到大约100k天(Duration.ofNanos(Long.MAX_VALUE).toDays())。应该升级Java以摆脱这个混乱。 - Adam Kotwasinski
1
高版本中有很多好处,例如Java 9的模块化系统。而且你推迟项目迁移的时间越长,将来就会变得越困难。 - Alexander Ivanchenko

1

你正在正确的道路上。既然你认为这条路很丑,解决方案就是隐藏它的丑陋!以下是 TimeUnit.toChronoUnit() 的实现,来源于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();
    }
}

为了让您的代码更加简洁,我建议基于上述内容实现一个静态实用方法(即传递一个TimeUnit参数),并在每个case中获取ChronoUnit而不进行转换。这样,您只需要调用一次Duration.of(long amount, TemporalUnit unit),就能使您的代码像使用Java 9+一样美观!

1
非常干净和简单明了,我在我的答案中添加了一个链接。 - Alexander Ivanchenko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接