【问题标题】:How can I "pretty print" a Duration in Java?如何在 Java 中“漂亮地打印”持续时间?
【发布时间】:2011-03-29 03:15:33
【问题描述】:

有没有人知道一个 Java 库,它可以像 C# 一样以毫秒为单位打印一个数字?

例如,123456 ms 作为 long 将被打印为 4d1h3m5s。

【问题讨论】:

  • 仅供参考,您似乎描述的格式是在合理标准中定义的DurationISO 8601PnYnMnDTnHnMnS 其中P 表示“期间”并标记开始,@987654326 @ 将日期部分与时间部分分开,在两者之间是可选出现的带有单字母缩写的数字。例如,PT4H30M = 四个半小时。
  • 如果所有其他方法都失败了,那么您自己做是一件非常简单的事情。只需使用%/ 的连续应用将数字分成几部分。几乎比一些建议的答案更容易。
  • @HotLicks 将其留给库方法,以获得比使用 /% 更清晰、更清晰的代码。
  • 是的,在我问这个问题后的 8 年里(!)我已经转移到非常适合我的用例的 joda time
  • @phatmanace Joda-Time 项目现在处于维护模式,建议迁移到 Java 8 中内置的 java.time 类和稍后。

标签: java datetime date time


【解决方案1】:

另一个 Java 9+ 解决方案:

private String formatDuration(Duration duration) {
    List<String> parts = new ArrayList<>();
    long days = duration.toDaysPart();
    if (days > 0) {
        parts.add(plural(days, "day"));
    }
    int hours = duration.toHoursPart();
    if (hours > 0 || !parts.isEmpty()) {
        parts.add(plural(hours, "hour"));
    }
    int minutes = duration.toMinutesPart();
    if (minutes > 0 || !parts.isEmpty()) {
        parts.add(plural(minutes, "minute"));
    }
    int seconds = duration.toSecondsPart();
    parts.add(plural(seconds, "second"));
    return String.join(", ", parts);
}

private String plural(long num, String unit) {
    return num + " " + unit + (num == 1 ? "" : "s");
}

例如,

6 days, 21 hours, 2 minutes, 14 seconds

【讨论】:

  • 你也可以使用StringBuilder或者StringJoiner
【解决方案2】:

一个基于user678573's answerJava 8版本:

private static String humanReadableFormat(Duration duration) {
    return String.format("%s days and %sh %sm %ss", duration.toDays(),
            duration.toHours() - TimeUnit.DAYS.toHours(duration.toDays()),
            duration.toMinutes() - TimeUnit.HOURS.toMinutes(duration.toHours()),
            duration.getSeconds() - TimeUnit.MINUTES.toSeconds(duration.toMinutes()));
}

...因为 Java 8 中没有 PeriodFormatter,也没有 getHours、getMinutes 等方法...

我很高兴看到更好的 Java 8 版本。

【讨论】:

  • 抛出 java.util.IllegalFormatConversionException: f != java.lang.Long
  • 持续时间 d1 = Duration.ofDays(0); d1 = d1.plusHours(47); d1 = d1.plusMinutes(124); d1 = d1.plusSeconds(124); System.out.println(String.format("%s days and %sh %sm %1.0fs", d1.toDays(), d1.toHours() - TimeUnit.DAYS.toHours(d1.toDays()), d1 .toMinutes() - TimeUnit.HOURS.toMinutes(d1.toHours()), d1.toSeconds() - TimeUnit.MINUTES.toSeconds(d1.toMinutes())));
  • @erickdeoliveiraleal 感谢您指出这一点。我想我最初是为 groovy 编写代码,所以它可能在那种语言中工作。我现在为 java 更正了。
【解决方案3】:

Joda-Time 的构建器方法的替代方案是基于模式的解决方案。这是由我的图书馆 Time4J 提供的。使用 Duration.Formatter 类的示例(添加了一些空格以提高可读性 - 删除空格将产生所需的 C# 样式):

IsoUnit unit = ClockUnit.MILLIS;
Duration<IsoUnit> dur = // normalized duration with multiple components
  Duration.of(123456, unit).with(Duration.STD_PERIOD);
Duration.Formatter<IsoUnit> f = // create formatter/parser with optional millis
  Duration.Formatter.ofPattern("D'd' h'h' m'm' s[.fff]'s'");

System.out.println(f.format(dur)); // output: 0d 0h 2m 3.456s

此格式化程序还可以打印java.time-API 的持续时间(但是,该类型的规范化功能不太强大):

System.out.println(f.format(java.time.Duration.ofMillis(123456))); // output: 0d 0h 2m 3.456s

OP 期望“123456 ms 作为 long 将被打印为 4d1h3m5s”以明显错误的方式计算。我认为草率是原因。上面定义的相同持续时间格式化程序也可以用作解析器。以下代码显示“4d1h3m5s”对应于349385000 = 1000 * (4 * 86400 + 1 * 3600 + 3 * 60 + 5)

System.out.println(
  f.parse("4d 1h 3m 5s")
   .toClockPeriodWithDaysAs24Hours()
   .with(unit.only())
   .getPartialAmount(unit)); // 349385000

另一种方法是使用 net.time4j.PrettyTime 类(这也适用于本地化输出和打印“昨天”、“下周日”、“4 天前”等相关时间):

String s = PrettyTime.of(Locale.ENGLISH).print(dur, TextWidth.NARROW);
System.out.println(s); // output: 2m 3s 456ms

s = PrettyTime.of(Locale.ENGLISH).print(dur, TextWidth.WIDE);
System.out.println(s); // output: 2 minutes, 3 seconds, and 456 milliseconds

s = PrettyTime.of(Locale.UK).print(dur, TextWidth.WIDE);
System.out.println(s); // output: 2 minutes, 3 seconds and 456 milliseconds

文本宽度控制是否使用缩写。也可以通过选择适当的语言环境来控制列表格式。例如,标准英语使用 Oxform 逗号,而 UK 不使用。 Time4J 的最新版本 v5.5 支持 90 多种语言,并使用基于 CLDR 存储库(行业标准)的翻译。

【讨论】:

  • 这个 PrettyTime 比另一个答案中的要好得多!太糟糕了,我没有先找到这个。
  • 我不知道为什么这个运行良好的解决方案现在有两个反对意见,所以我决定通过提供更多关于解析(格式相反)的示例来扩展答案并突出错误对OP的期望。
【解决方案4】:

org.threeten.extra.AmountFormats.wordBased

ThreeTen-Extra 项目由 JSR 310、java.timeJoda-Time 的作者 Stephen Colebourne 维护,有一个 AmountFormats与标准 Java 8 日期时间类一起使用的类。虽然它相当冗长,但没有更紧凑的输出选项。

Duration d = Duration.ofMinutes(1).plusSeconds(9).plusMillis(86);
System.out.println(AmountFormats.wordBased(d, Locale.getDefault()));

1 minute, 9 seconds and 86 milliseconds

【讨论】:

  • 哇,很高兴知道,我还没有看到这个功能。但是有一个主要缺陷:缺少Oxford comma
  • @BasilBourque 牛津逗号在美国很常见,但有趣的是,在英国则不然。我的 lib Time4J(具有更好的国际化)在 net.time4j.PrettyTime 类中支持这种区别,并且如果需要还允许控制紧凑输出。
【解决方案5】:

Java 9+

Duration d1 = Duration.ofDays(0);
        d1 = d1.plusHours(47);
        d1 = d1.plusMinutes(124);
        d1 = d1.plusSeconds(124);
System.out.println(String.format("%s d %sh %sm %ss", 
                d1.toDaysPart(), 
                d1.toHoursPart(), 
                d1.toMinutesPart(), 
                d1.toSecondsPart()));

2 天 1 小时 6 分钟 4 秒

【讨论】:

  • 你是如何获得“toHoursPart”及以上的?
【解决方案6】:

我构建了一个简单的解决方案,使用 Java 8 的 Duration.toString() 和一些正则表达式:

public static String humanReadableFormat(Duration duration) {
    return duration.toString()
            .substring(2)
            .replaceAll("(\\d[HMS])(?!$)", "$1 ")
            .toLowerCase();
}

结果将如下所示:

- 5h
- 7h 15m
- 6h 50m 15s
- 2h 5s
- 0.1s

如果您不想在两者之间有空格,只需删除 replaceAll

【讨论】:

  • 你不应该依赖 toStrong() 的输出,因为这可能会在以后的版本中改变。
  • @Weltraumschaf 它不太可能改变,因为它是在 javadoc 中指定的
  • 如果您不想要分数,请在调用 toLowerCase() 之前添加 .replaceAll("\\.\\d+", "")
  • @Weltraumschaf 您的观点总体上是正确的,我自己不会依赖大多数类的 toString() 输出。但是在这个非常特殊的情况下,方法 definition 声明它的输出遵循 ISO-8601 标准,正如您在方法的 Javadoc 中看到的那样。所以它不像大多数类那样是一个实现细节,因此我不希望像 Java 这样成熟的语言会发生这样的变化,
【解决方案7】:

我知道这可能不完全适合您的用例,但 PrettyTime 可能在这里有用。

PrettyTime p = new PrettyTime();
System.out.println(p.format(new Date()));
//prints: “right now”

System.out.println(p.format(new Date(1000*60*10)));
//prints: “10 minutes from now”

【讨论】:

  • 是否有可能只有“10 分钟”?
【解决方案8】:

使用 Java 8,您还可以使用 java.time.DurationtoString() 方法对其进行格式化,而无需使用 ISO 8601 seconds based representation 的外部库,例如 PT8H6M12.345S。

【讨论】:

  • 请注意,这不会打印天数
  • 不确定这种格式是否符合我对 pretty 打印的定义。 :-)
  • @james.garriss 要让它更漂亮一点,请参阅 eri​​ckdeoliveiraleal 的 the Answer
【解决方案9】:

Joda Time 有一个很好的方法来使用PeriodFormatterBuilder

快赢:PeriodFormat.getDefault().print(duration.toPeriod());

例如

//import org.joda.time.format.PeriodFormatter;
//import org.joda.time.format.PeriodFormatterBuilder;
//import org.joda.time.Duration;

Duration duration = new Duration(123456); // in milliseconds
PeriodFormatter formatter = new PeriodFormatterBuilder()
     .appendDays()
     .appendSuffix("d")
     .appendHours()
     .appendSuffix("h")
     .appendMinutes()
     .appendSuffix("m")
     .appendSeconds()
     .appendSuffix("s")
     .toFormatter();
String formatted = formatter.print(duration.toPeriod());
System.out.println(formatted);

【讨论】:

  • 从下面的答案看来,可以直接创建 Period 的实例,而无需先创建 Duration 实例,然后将其转换为 Period。例如。周期 = 新周期(毫秒);格式化字符串 = formatter.print(period);
  • 当心这种“duration.toPeriod()”转换。如果持续时间很大,则日部分将保持为 0。小时部分将继续增长。您将获得 25h10m23s 但永远不会获得“d”。原因是没有完全正确的方法可以按照 Joda 的严格方式将小时转换为天。大多数情况下,如果您要比较两个瞬间并想要打印它,您可以使用 new Period(t1, t2) 而不是 new Duration(t1, t2).toPeriod()。
  • @Boon 你知道如何将持续时间转换为相对于天数的期间吗?我使用持续时间,我可以在我的计时器中替换秒。事件设置 PeriodType.daysTime().standard() 没有帮助
  • @murt 如果你真的想要,并且可以接受一天的标准定义,那么你可以在上面的代码中使用“formatter.print(duration.toPeriod().normalizedStandard())”。玩得开心!
【解决方案10】:

以下是使用纯 JDK 代码的方法:

import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.Duration;

long diffTime = 215081000L;
Duration duration = DatatypeFactory.newInstance().newDuration(diffTime);

System.out.printf("%02d:%02d:%02d", duration.getDays() * 24 + duration.getHours(), duration.getMinutes(), duration.getSeconds()); 

【讨论】:

    【解决方案11】:

    Apache commons-lang 提供了一个有用的类来完成这项工作DurationFormatUtils

    例如 DurationFormatUtils.formatDurationHMS( 15362 * 1000 ) ) => 4:16:02.000 (H:m:s.millis) DurationFormatUtils.formatDurationISO( 15362 * 1000 ) ) => P0Y0M0DT4H16M2.000S,参见。 ISO8601

    【讨论】:

      【解决方案12】:

      JodaTime 有一个Period 类可以表示这些数量,并且可以以ISO8601 格式呈现(通过IsoPeriodFormat),例如PT4D1H3M5S,例如

      Period period = new Period(millis);
      String formatted = ISOPeriodFormat.standard().print(period);
      

      如果该格式不是您想要的格式,那么 PeriodFormatterBuilder 可以让您组合任意布局,包括您的 C# 样式 4d1h3m5s

      【讨论】:

      • 请注意,new Period(millis).toString() 默认使用ISOPeriodFormat.standard()
      猜你喜欢
      • 2011-04-05
      • 1970-01-01
      • 2010-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-22
      相关资源
      最近更新 更多