¿Por qué no puedo obtener una duración en minutos u horas en java.time?

De la clase de Duration en la nueva API de fecha JSR 310 ( paquete java.time ) disponible en Java 8 y posterior, el javadoc dice:

Esta clase modela una cantidad o cantidad de tiempo en términos de segundos y nanosegundos. Se puede acceder utilizando otras unidades basadas en la duración, como minutos y horas . Además, se puede utilizar la unidad DAYS y se trata exactamente igual a 24 horas, ignorando así los efectos de ahorro de luz diurna.

Entonces, ¿por qué se bloquea el siguiente código?

 Duration duration = Duration.ofSeconds(3000); System.out.println(duration.get(ChronoUnit.MINUTES)); 

Esto genera una UnsupportedTemporalTypeException :

 java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Minutes at java.time.Duration.get(Duration.java:537) 

Entonces, ¿cuál es la forma recomendada de extraer minutos y horas de un objeto de duración? ¿Tenemos que hacer los cálculos nosotros mismos a partir de la cantidad de segundos? ¿Por qué se implementó de esa manera?

“¿Por qué se implementó de esa manera?”

Otras respuestas se refieren a los métodos toXxx() que permiten consultar las horas / minutos. Trataré de lidiar con el por qué.

La interfaz TemporalAmount y el método get(TemporalUnit) se agregaron bastante tarde en el proceso. Personalmente, no estaba del todo convencido de que tuviéramos suficiente evidencia de la forma correcta de trabajar el diseño en esa área, pero se armó un poco del arm para agregar TemporalAmount . Creo que al hacerlo confundimos ligeramente la API.

En retrospectiva, creo que TemporalAmount contiene los métodos correctos, pero creo que get(TemporalUnit) debería haber tenido un nombre de método diferente. La razón es que get(TemporalUnit) es esencialmente un método de nivel de marco: no está diseñado para uso diario. Lamentablemente, el método get no implica esto, lo que provoca errores como llamar a get(ChronoUnit.MINUTES) en Duration .

Entonces, la manera de pensar en get(TemporalUnit) es imaginar un marco de bajo nivel que vea la cantidad como un Map donde Duration es un Map del tamaño dos con claves de SECONDS y NANOS .

De la misma manera, el Period se ve desde los marcos de bajo nivel como un Map de tamaño tres – DAYS , MONTHS y YEARS (que afortunadamente tiene menos posibilidades de errores).

En general, el mejor consejo para el código de la aplicación es ignorar el método get(TemporalUnit) . Use getSeconds() , getNano() , toHours() y toMinutes() lugar.

Finalmente, una forma de obtener “hh: mm: ss” de una Duration es hacer:

 LocalTime.MIDNIGHT.plus(duration).format(DateTimeFormatter.ofPattern("HH:mm:ss")) 

No es bonito en absoluto, pero funciona durante duraciones de menos de un día.

Nuevo to…Part Métodos de to…Part en Java 9

El problema JDK-8142936 ahora se implementó en Java 9, agregando los siguientes métodos para acceder a cada parte de una Duration .

  • toDaysPart
  • toHoursPart
  • toMinutesPart
  • toSecondsPart
  • toMillisPart
  • toNanosPart

La documentación dice:

Esto devuelve un valor para cada una de las dos unidades admitidas, SECONDS y NANOS. Todas las otras unidades lanzan una excepción.

Entonces, la mejor respuesta es la forma en que lo diseñaron.

Puede usar algunos de los otros métodos para obtenerlo en horas :

 long hours = duration.toHours(); 

o minutos :

 long minutes = duration.toMinutes(); 

Me gustaría agregar a la respuesta de bigt (+1) que señaló el útil toHours , toMinutes y otros métodos de conversión. La especificación de Duración dice:

Esta clase modela una cantidad o cantidad de tiempo en términos de segundos y nanosegundos. […]

El rango de duración requiere el almacenamiento de un número mayor que un largo. Para lograr esto, la clase almacena una representación larga de segundos y una int que representa nanosegundos de segundo, que siempre estará entre 0 y 999,999,999.

Hay una variedad de métodos getter como getSeconds , getNano y get(TemporalUnit) . Dado que una duración se representa como un par (segundos, nanos), está claro por qué get(TemporalUnit) está restringido a segundos y nanos. Estos métodos getter extraen datos directamente de la instancia de duración y no tienen pérdida.

Por el contrario, existe una variedad de métodos toDays incluyen toDays , toHours , toMillis toMinutes y toNanos que realizan conversiones en el valor de duración. Estas conversiones son con pérdida ya que implican truncamiento de datos, o pueden arrojar una excepción si el valor de duración no se puede representar en el formato solicitado.

Claramente, es parte del diseño que los métodos get extraigan datos sin conversión y los métodos to realizan conversiones de algún tipo.

Para obtener los componentes de hora / minuto / segundo de forma “normalizada”, debe calcularlos manualmente: el código que se muestra a continuación se copia esencialmente del método de Duration#toString :

 Duration duration = Duration.ofSeconds(3000); long hours = duration.toHours(); int minutes = (int) ((duration.getSeconds() % (60 * 60)) / 60); int seconds = (int) (duration.getSeconds() % 60); System.out.println(hours + ":" + minutes + ":" + seconds); 

Por debajo del código obtuve este resultado de duración:

1 día, 2 horas, 5 minutos

ejemplo de código:

 private String getDurationAsString(Duration duration) { StringBuilder durationAsStringBuilder = new StringBuilder(); if (duration.toDays() > 0) { String postfix = duration.toDays() == 1 ? "" : "s"; durationAsStringBuilder.append(duration.toDays() + " day"); durationAsStringBuilder.append(postfix); } duration = duration.minusDays(duration.toDays()); long hours = duration.toHours(); if (hours > 0) { String prefix = Utils.isEmpty(durationAsStringBuilder.toString()) ? "" : ", "; String postfix = hours == 1 ? "" : "s"; durationAsStringBuilder.append(prefix); durationAsStringBuilder.append(hours + " hour"); durationAsStringBuilder.append(postfix); } duration = duration.minusHours(duration.toHours()); long minutes = duration.toMinutes(); if (minutes > 0) { String prefix = Utils.isEmpty(durationAsStringBuilder.toString()) ? "" : ", "; String postfix = minutes == 1 ? "" : "s"; durationAsStringBuilder.append(prefix); durationAsStringBuilder.append(minutes + " minute"); durationAsStringBuilder.append(postfix); } return durationAsStringBuilder.toString(); } 

Explicación

Al usar la interfaz de duración, puede encontrar fácilmente la visualización de cadena exacta que desea. solo necesitas reducir la TemporalUnit más grande de la duración actual.

por ejemplo:

  1. calcular días
  2. reducir días a partir de la duración
  3. calcular horas
  4. reduce horas de duración, etc. … hasta que obtengas duración = 0