¿Cómo puedo “imprimir con estilo” una duración en Java?

93

¿Alguien sabe de una biblioteca de Java que pueda imprimir un número en milisegundos de la misma manera que lo hace C #?

Por ejemplo, 123456 ms como un largo se imprimiría como 4d1h3m5s.

phatmanace
fuente
1
Para su información, el formato que parece estar describiendo está Durationdefinido en el estándar sensible, ISO 8601 : PnYnMnDTnHnMnSdonde Psignifica "Período" y marca el comienzo, Tsepara la parte de la fecha de la parte del tiempo, y en el medio hay apariciones opcionales de un número con un solo -abreviatura de la letra. Por ejemplo, PT4H30M= cuatro horas y media.
Basil Bourque
1
Si todo lo demás falla, es muy sencillo hacerlo usted mismo. Simplemente use aplicaciones sucesivas de %y /para dividir el número en partes. Casi más fácil que algunas de las respuestas propuestas.
Hot Licks
@HotLicks Deje que los métodos de la biblioteca obtengan un código mucho más limpio y claro que usar /y %.
Ole VV
Sí, en los 8 años transcurridos desde que hice esta pregunta (!) Me mudé a joda time, que se adapta muy bien a mi caso de uso
phatmanace
@phatmanace El proyecto Joda-Time está ahora en modo de mantenimiento y recomienda la migración a las clases java.time integradas en Java 8 y versiones posteriores.
Basil Bourque

Respuestas:

91

Joda Time tiene una manera bastante buena de hacer esto usando un PeriodFormatterBuilder .

Victoria rapida: PeriodFormat.getDefault().print(duration.toPeriod());

p.ej

//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
fuente
2
A partir de una respuesta a continuación, se desprende que se puede crear una instancia de Periodo directamente, sin crear primero una instancia de Duración y luego convertirla en Periodo. Por ejemplo, período período = nuevo período (milis); Cadena formateada = formatter.print (punto);
Basil Vandegriend
7
Tenga cuidado con esta conversión "duration.toPeriod ()". Si la duración es bastante grande, la porción del día en adelante permanecerá como 0. La porción de horas seguirá creciendo. Obtendrá 25h10m23s pero nunca obtendrá la "d". La razón es que no existe una forma completamente correcta de convertir horas en días de la manera estricta de Joda. La mayoría de los casos, si está comparando dos instantes y desea imprimirlo, puede hacer un nuevo período (t1, t2) en lugar de una nueva duración (t1, t2) .toPeriod ().
Boon
@Boon, ¿sabes cómo convertir la duración en período con respecto a días? Utilizo la duración de la que puedo sustituir el segundo en mi en mi temporizador. Configuración del evento PeriodType.daysTime()o .standard()no ayudó
murt
4
@murt Si realmente lo desea y puede aceptar la definición estándar de un día, puede "formatter.print (duration.toPeriod (). normalizedStandard ())" en el código anterior. ¡Que te diviertas!
Boon
74

Creé una solución simple, usando Java 8 Duration.toString()y un poco de expresión regular:

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

El resultado se verá así:

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

Si no quiere espacios entre ellos, simplemente elimínelos replaceAll.

Lucas
fuente
6
Nunca debe confiar en la salida de toStrong () porque esto puede cambiar en versiones posteriores.
Weltraumschaf
14
@Weltraumschaf no es probable que cambie, ya que está especificado en el javadoc
aditsu renunció porque SE es MALVADO
9
@Weltraumschaf Es poco probable que cambie ya que la salida de Duration::toStringestá formateada de acuerdo con el estándar ISO 8601 bien definido y usado .
Basil Bourque
1
Si no quiere fracciones, sume .replaceAll("\\.\\d+", "")antes de llamar a toLowerCase().
zb226
1
@Weltraumschaf Su punto en general es correcto y yo no confiaría en la salida de toString () de la mayoría de las clases. Pero en este caso muy específico, la definición del método establece que su salida sigue el estándar ISO-8601, como puede ver en el Javadoc del método . Por lo tanto, no es un detalle de implementación como en la mayoría de las clases, por lo tanto, no es algo que esperaría que un lenguaje tan maduro como Java cambie de esa manera,
lucasls
13

Apache commons-lang proporciona una clase útil para hacer esto también DurationFormatUtils

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

noleto
fuente
9

JodaTime tiene una Periodclase que puede representar tales cantidades y se puede renderizar (vía IsoPeriodFormat) en formato ISO8601 , por ejemplo PT4D1H3M5S,

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

Si ese formato no es el que desea, entonces le PeriodFormatterBuilderpermite ensamblar diseños arbitrarios, incluido su estilo C # 4d1h3m5s.

skaffman
fuente
3
Solo como una nota, new Period(millis).toString()usa el ISOPeriodFormat.standard()predeterminado.
Thomas Beauvais
9

Con Java 8 también puede utilizar el toString()método de java.time.Durationpara formatearlo sin bibliotecas externas utilizando la representación basada en segundos ISO 8601 como PT8H6M12.345S.

Konrad Höffner
fuente
4
Tenga en cuenta que esto no imprime días
Wim Deblauwe
58
No estoy seguro de que este formato cumpla con mi definición de impresión bonita . :-)
james.garriss
@ james.garriss Para hacerlo un poco más bonito, vea la Respuesta de erickdeoliveiraleal.
Basil Bourque
7

A continuación, le indicamos cómo puede hacerlo utilizando 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()); 
usuario678573
fuente
7

org.threeten.extra.AmountFormats.wordBased

El proyecto ThreeTen-Extra , mantenido por Stephen Colebourne, autor de JSR 310, java.time y Joda-Time , tiene una AmountFormatsclase que funciona con las clases estándar de fecha y hora de Java 8. Sin embargo, es bastante detallado, sin opción para una salida más 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
fuente
2
Vaya, bueno saberlo, no he visto esa característica. Sin embargo, tiene un defecto importante: falta la coma de Oxford .
Basil Bourque
4
@BasilBourque La coma de Oxford es típica de EE. UU., Pero curiosamente no del Reino Unido. Mi lib Time4J (que tiene una internacionalización mucho mejor) admite esta distinción en la clase net.time4j.PrettyTime y también permite el control sobre la salida compacta si se desea.
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
fuente
¿Cómo está obteniendo "toHoursPart" y más?
Ghoti and Chips
4

Una alternativa al enfoque de construcción de Joda-Time sería una solución basada en patrones . Esto lo ofrece mi biblioteca Time4J . Ejemplo usando la clase Duration.Formatter (se agregaron algunos espacios para una mayor legibilidad; eliminar los espacios producirá el estilo C # deseado):

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 formateador también puede imprimir duraciones de java.time-API (sin embargo, las características de normalización de ese tipo son menos poderosas):

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

La expectativa del OP de que "123456 ms como un largo se imprimirían como 4d1h3m5s" se calcula de una manera obviamente incorrecta. Asumo el descuido como razón. El mismo formateador de duración definido anteriormente también se puede utilizar como analizador. El siguiente código muestra que "4d1h3m5s" corresponde más bien 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

Otra forma es usar la clase net.time4j.PrettyTime(que también es buena para resultados localizados e imprimir tiempos relativos como "ayer", "próximo domingo", "hace 4 días", 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

El ancho del texto controla si se usan o no abreviaturas. El formato de la lista también se puede controlar eligiendo la configuración regional adecuada. Por ejemplo, el inglés estándar utiliza la coma Oxform, mientras que el Reino Unido no. La última versión v5.5 de Time4J admite más de 90 idiomas y utiliza traducciones basadas en el repositorio CLDR (un estándar de la industria).

Meno Hochschild
fuente
2
¡este PrettyTime es mucho mejor que el de la otra respuesta! Lástima que no encontré esto primero.
ycomp
No sé por qué ahora hay dos votos negativos para esta solución que funciona bien, así que he decidido extender la respuesta dando más ejemplos también sobre el análisis (lo opuesto al formateo) y para resaltar la expectativa incorrecta del OP.
Meno Hochschild
4

Una versión de Java 8 basada en la respuesta del usuario 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()));
}

... ya que no hay PeriodFormatter en Java 8 y no hay métodos como getHours, getMinutes, ...

Me alegraría ver una versión mejor para Java 8.

Torsten
fuente
lanza java.util.IllegalFormatConversionException: f! = java.lang.Long
erickdeoliveiraleal
con Duración d1 = Duración de los días (0); d1 = d1.plusHours (47); d1 = d1.plusMinutes (124); d1 = d1.plusSeconds (124); System.out.println (String.format ("% s días y% 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 Gracias por señalar eso. Creo que originalmente estaba escribiendo el código para groovy, por lo que podría haber funcionado en ese idioma. Lo corregí para Java ahora.
Torsten
3

Me doy cuenta de que esto podría no ajustarse exactamente a su caso de uso, pero PrettyTime podría ser útil aquí.

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”
Mella
fuente
4
¿Es posible tener solo "10 minutos"?
Johnny