如何在Java中“漂亮地打印”持续时间?


93

有谁知道Java库可以像C#一样以毫秒为单位打印数字?

例如,最长的123456 ms将被打印为4d1h3m5s。


1
仅供参考,您似乎要描述的格式是Duration在明智的标准ISO 8601中定义的:PnYnMnDTnHnMnS其中的P意思是“句点”并标记开始,T将日期部分与时间部分分开,并且在两者之间是一个可选的数字出现字母缩写。例如,PT4H30M=四个半小时。
罗勒·布尔克2014年

1
如果所有其他方法都失败了,那么自己做是一件非常简单的事情。只要使用的连续应用%,并/以数量分成几部分。几乎比一些建议的答案容易。
热门点击

@HotLicks将其留给库方法以获得比使用/and 更干净,更清晰的代码%
Ole VV

是的,在我问这个问题以来的8年中(!)我已移至非常适合我的用例的joda时间
phatmanace

@phatmanace Joda-Time项目现在处于维护模式,建议迁移到Java 8及更高版本中内置的java.time类。
罗勒·布尔克

Answers:


91

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);

2
从下面的答案看来,可以直接创建Period的实例,而无需先创建Duration实例,然后将其转换为Period。例如,时期=新时期(毫秒);格式化的字符串= formatter.print(period);
Basil Vandegriend

7
当心这种“ duration.toPeriod()”转换。如果持续时间很大,则以后的天数部分将保持为0。小时数部分将继续增长。您将获得25h10m23s,但永远不会获得“ d”。原因是没有完全正确的方法可以按照乔达的严格方法将小时数转换为几天。在大多数情况下,如果要比较两个时刻并希望将其打印出来,则可以执行新的Period(t1,t2)而不是新的Duration(t1,t2).toPeriod()。
Boon 2015年

@Boon您知道如何将持续时间转换为天数吗?我使用的持续时间可以代替计时器中的秒。活动设置PeriodType.daysTime().standard()没有帮助
沉默

4
@murt如果您确实想要并且可以接受一天的标准定义,则可以在上面的代码中“ formatter.print(duration.toPeriod()。normalizedStandard())”。玩得开心!
恩恩

74

我使用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


6
您永远不要依赖toStrong()的输出,因为这可能在其他版本中更改。
Weltraumschaf

14
@Weltraumschaf是不可能改变的,因为它在Javadoc自行指定
aditsu退出,因为SE是邪恶的

9
@Weltraumschaf由于Duration::toString根据定义明确且使用较早的ISO 8601标准格式化输出的格式,因此不太可能更改。
罗勒·布尔克

1
如果您不需要分数,请.replaceAll("\\.\\d+", "")在调用之前添加toLowerCase()
zb226

1
@Weltraumschaf您的观点通常是正确的,我自己不会依赖大多数类的toString()输出。但是在这种非常特殊的情况下,方法定义指出其输出遵循ISO-8601标准,如您在方法的Javadoc中所见。因此,这不是大多数类中的实现细节,因此,我不希望像Java这样成熟的语言能够像这样进行更改,
lucasls

13

Apache commons-lang提供了一个有用的类来完成此任务,DurationFormatUtils

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


9

JodaTime具有Period类,它可以表示这样的量,并且可以呈现(通过IsoPeriodFormat在)ISO8601的格式,例如PT4D1H3M5S,如

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

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


3
正如一个音符,new Period(millis).toString()使用ISOPeriodFormat.standard()默认。
Thomas Beauvais 2014年


7

使用纯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()); 

7

org.threeten.extra.AmountFormats.wordBased

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

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

1 minute, 9 seconds and 86 milliseconds


2
哇,很高兴知道,我还没有看到该功能。但是有一个主要缺陷:缺少牛津逗号
罗勒·布尔克

4
@BasilBourque牛津逗号在美国很典型,但有趣的是在英国不常见。我的lib Time4J(国际化程度更高)支持net.time4j.PrettyTime类中的这种区别,并且如果需要,还可以控制紧凑的输出。
Meno Hochschild

6

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 d 1h 6m 4s


您如何获得“ toHoursPart”及以上?
Ghoti和Chips

4

基于模式的解决方案可以替代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将被打印为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逗号,而英国则不使用。Time4J的最新版本v5.5支持90多种语言,并使用基于CLDR存储库(行业标准)的翻译。


2
这个PrettyTime比另一个答案更好!太糟糕了,我没有首先找到这个。
ycomp

我不知道为什么现在对于这个运行良好的解决方案有两个反对意见,所以我决定通过给出更多有关解析的示例(与格式相反)并强调OP的错误期望来扩展答案。
Meno Hochschild

4

一个Java的8版本的基础上user678573的回答

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
erickdeoliveiraleal

持续时间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

@erickdeoliveiraleal感谢您指出这一点。我想我最初是为Groovy编写代码的,所以它可能已经在那种语言下工作了。我现在为Java更正了它。
Torsten

3

我意识到这可能不完全适合您的用例,但是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”

4
可能只有“ 10分钟”吗?
约翰尼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.