Como posso "imprimir" uma duração em Java?

93

Alguém conhece uma biblioteca Java que pode imprimir um número em milissegundos da mesma maneira que o C #?

Por exemplo, 123456 ms como um longo seria impresso como 4d1h3m5s.

phatmanace
fonte
1
Para sua informação, o formato que você parece estar descrevendo é Durationdefinido no padrão sensato, ISO 8601 : PnYnMnDTnHnMnSonde Psignifica "Ponto" e marca o início, Tsepara a parte da data da parte do tempo e entre as ocorrências opcionais de um número com um único - abreviatura da letra. Por exemplo, PT4H30M= quatro horas e meia.
Basil Bourque
1
Se tudo mais falhar, é muito simples fazer você mesmo. Basta usar aplicações sucessivas de %e /para dividir o número em partes. Quase mais fácil do que algumas das respostas propostas.
Hot Licks de
@HotLicks Deixe isso para métodos de biblioteca para um código muito mais limpo e claro do que usar /e %.
Ole VV
Sim, nos 8 anos que se passaram desde que fiz esta pergunta (!) Mudei para o tempo joda, que se adapta muito bem ao meu caso de uso
phatmanace
@phatmanace O projeto Joda-Time está agora em modo de manutenção e aconselha a migração para as classes java.time construídas no Java 8 e posterior.
Basil Bourque

Respostas:

91

Joda Time tem uma maneira muito boa de fazer isso usando um PeriodFormatterBuilder .

Vitória rápida: PeriodFormat.getDefault().print(duration.toPeriod());

por exemplo

//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);
Rob Hruska
fonte
2
Parece com uma resposta abaixo que uma instância de Period pode ser criada diretamente, sem primeiro criar uma instância de Duration e depois convertê-la em Period. Por exemplo, período do período = novo período (milis); String formatada = formatter.print (ponto);
Basil Vandegriend
7
Cuidado com essa conversão "duration.toPeriod ()". Se a duração for muito grande, a parte do dia em diante permanecerá como 0. A parte das horas continuará crescendo. Você obterá 25h10m23s, mas nunca obterá o "d". A razão é que não existe uma maneira totalmente correta de converter horas em dias nos métodos estritos de Joda. Na maioria dos casos, se você está comparando dois instantes e quer imprimi-los, você pode fazer um novo Período (t1, t2) em vez de um novo Período (t1, t2) .toPeriod ().
Boon,
@Boon você sabe como converter a duração em período em relação aos dias? Eu uso a duração a partir da qual posso substituir o segundo no meu cronômetro. Configuração do evento PeriodType.daysTime()ou .standard()não ajudou
murt
4
@murt Se você realmente deseja e pode aceitar a definição padrão de um dia, você pode "formatter.print (duration.toPeriod (). normalizedStandard ())" no código acima. Diverta-se!
Boon
74

Criei uma solução simples, usando Java 8 Duration.toString()e um pouco de regex:

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

O resultado será semelhante a:

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

Se você não quiser espaços entre, apenas remova replaceAll.

lucasls
fonte
6
Você nunca deve confiar na saída de toStrong () porque isso pode mudar nas versões futuras.
Weltraumschaf
14
@Weltraumschaf não é provável que mude, já que está especificado no javadoc
aditsu quit porque SE é EVIL
9
@Weltraumschaf É improvável que mude, pois a saída de Duration::toStringé formatada de acordo com o padrão ISO 8601 bem definido e usado .
Basil Bourque
1
Se você não quiser frações, adicione .replaceAll("\\.\\d+", "")antes da chamada para toLowerCase().
zb226
1
@Weltraumschaf Seu ponto é geralmente correto e eu não confiaria na saída toString () da maioria das classes. Mas, neste caso muito específico, a definição do método afirma que sua saída segue o padrão ISO-8601, como você pode ver no Javadoc do método . Portanto, não é um detalhe de implementação como na maioria das classes, portanto, não é algo que eu esperaria que uma linguagem tão madura como Java mudasse assim,
lucasls
13

Apache commons-lang oferece uma classe útil para fazer isso também. DurationFormatUtils

por exemplo, DurationFormatUtils.formatDurationHMS( 15362 * 1000 ) )=> 4: 16: 02.000 (H: m: s.millis) DurationFormatUtils.formatDurationISO( 15362 * 1000 ) )=> P0Y0M0DT4H16M2.000S, cf. ISO8601

noleto
fonte
9

JodaTime tem uma Periodclasse que pode representar tais quantidades, e pode ser renderizada (via IsoPeriodFormat) no formato ISO8601 , por exemplo PT4D1H3M5S, por exemplo

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

Se esse formato não for o que você deseja, então PeriodFormatterBuilderpermite que você monte layouts arbitrários, incluindo seu estilo C # 4d1h3m5s.

skaffman
fonte
3
Apenas como uma nota, new Period(millis).toString()usa o ISOPeriodFormat.standard()por padrão.
Thomas Beauvais
9

Com o Java 8, você também pode usar o toString()método de java.time.Durationpara formatá-lo sem bibliotecas externas usando a representação baseada em segundos ISO 8601 , como PT8H6M12.345S.

Konrad Höffner
fonte
4
Observe que isso não imprime dias
Wim Deblauwe
58
Não tenho certeza se esse formato atende à minha definição de impressão bonita . :-)
james.garriss
@ james.garriss Para torná-lo um pouco mais bonito, consulte a Resposta por erickdeoliveiraleal.
Basil Bourque
7

Veja como você pode fazer isso usando código JDK puro:

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()); 
user678573
fonte
7

org.threeten.extra.AmountFormats.wordBased

O projeto ThreeTen-Extra , que é mantido por Stephen Colebourne, o autor de JSR 310, java.time e Joda-Time , tem uma AmountFormatsclasse que funciona com as classes de data e hora Java 8 padrão. É bastante prolixo, porém, sem opção para uma saída mais compacta.

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

1 minute, 9 seconds and 86 milliseconds

Adrian Baker
fonte
2
Uau, bom saber, eu não vi esse recurso. No entanto, tem uma grande falha: falta a vírgula Oxford .
Basil Bourque
4
@BasilBourque A vírgula Oxford é típica dos Estados Unidos, mas, curiosamente, não do Reino Unido. Minha lib Time4J (que tem uma internacionalização muito melhor) suporta essa distinção na classe net.time4j.PrettyTime e também permite o controle sobre a saída compacta, se desejado.
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

erickdeoliveiraleal
fonte
Como você está obtendo "toHoursPart" e superior?
Ghoti and Chips
4

Uma alternativa para a abordagem do construtor do Joda-Time seria uma solução baseada em padrões . Isso é oferecido pela minha biblioteca Time4J . Exemplo usando a classe Duration.Formatter (adicionado alguns espaços para mais legibilidade - remover os espaços resultará no estilo C # desejado):

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

Este formatador também pode imprimir durações de java.time-API (no entanto, os recursos de normalização desse tipo são menos poderosos):

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

A expectativa do OP de que "123456 ms como um longo seria impresso como 4d1h3m5s" é calculada de uma maneira obviamente errada. Assumo a negligência como razão. O mesmo formatador de duração definido acima também pode ser usado como analisador. O código a seguir mostra que "4d1h3m5s" corresponde a 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

Outra maneira é usar a classe net.time4j.PrettyTime(que também é boa para saída localizada e impressão de tempos relativos como "ontem", "próximo domingo", "4 dias atrás" etc.):

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

A largura do texto controla se as abreviações são usadas ou não. O formato da lista também pode ser controlado escolhendo o local apropriado. Por exemplo, o inglês padrão usa a vírgula Oxform, enquanto o Reino Unido não. A última versão v5.5 do Time4J suporta mais de 90 idiomas e usa traduções baseadas no repositório CLDR (um padrão da indústria).

Meno Hochschild
fonte
2
este PrettyTime é muito melhor do que o da outra resposta! pena que não achei isso primeiro.
ycomp
Não sei por que agora existem dois votos negativos para essa solução que funciona bem, então decidi estender a resposta, dando mais exemplos também sobre análise (o oposto de formatação) e para destacar a expectativa errada do OP.
Meno Hochschild
4

Uma versão Java 8 baseada na resposta do usuário 678573 :

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

... já que não há PeriodFormatter no Java 8 e nenhum método como getHours, getMinutes, ...

Eu ficaria feliz em ver uma versão melhor para Java 8.

Torsten
fonte
lança java.util.IllegalFormatConversionException: f! = java.lang.Long
erickdeoliveiraleal
com Duração d1 = Duração.deDias (0); d1 = d1.maisHoras (47); d1 = d1.maisMinutos (124); d1 = d1.maisSegundos (124); System.out.println (String.format ("% s dias e% 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 Obrigado por apontar isso. Acho que estava escrevendo originalmente o código para groovy, então pode ter funcionado nessa linguagem. Eu corrigi para java agora.
Torsten
3

Sei que isso pode não se adequar exatamente ao seu caso de uso, mas PrettyTime pode ser útil aqui.

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”
usuario
fonte
4
É possível ter apenas "10 minutos"?
Johnny