localdatetime - ¿Por qué no puedo obtener una duración en minutos u horas en java.time?
manejo de horas en java (5)
De la clase Duration
en la nueva API de fecha JSR 310 ( paquete java.time ) disponible en Java 8 y versiones posteriores, 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, la unidad DAYS se puede usar y se trata exactamente igual a 24 horas, ignorando así los efectos del horario de verano.
Entonces, ¿por qué falla el siguiente código?
Duration duration = Duration.ofSeconds(3000);
System.out.println(duration.get(ChronoUnit.MINUTES));
Esto genera una UnsupportedTemporalTypeException
tipo 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 el cálculo nosotros mismos a partir del número de segundos? ¿Por qué se implementó de esa manera?
"¿Por qué se implementó de esa manera?"
Otras respuestas tratan con los métodos toXxx()
que permiten consultar las horas / minutos. Voy a 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 completamente convencido de que tuviéramos pruebas suficientes de la forma correcta de trabajar el diseño en esa área, pero me torcí un poco para agregar el 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 a nivel de marco, no está diseñado para el uso diario. Desafortunadamente, el nombre del método get
no implica esto, lo que resulta en errores como llamar a get(ChronoUnit.MINUTES)
en la Duration
.
Entonces, la manera de pensar en get(TemporalUnit)
es imaginar un marco de bajo nivel que vea la cantidad como un Map<TemporalUnit, Long>
donde Duration
es un Map
de tamaño dos con claves de SECONDS
y NANOS
.
De la misma manera, 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 aplicación es ignorar el método get(TemporalUnit)
. Utilice 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 menos de un día.
Nuevo to…Part
Métodos parciales en Java 9
JDK-8142936 ahora implementado en Java 9, agrega los siguientes métodos para acceder a cada parte de una Duration
.
-
toDaysPart
-
toHoursPart
-
toMinutesPart
-
toSecondsPart
-
toMillisPart
-
toNanosPart
La documentation dice:
Esto devuelve un valor para cada una de las dos unidades compatibles, SEGUNDOS y NANOS. Todas las demás unidades lanzan una excepción.
Entonces, la mejor respuesta es la forma en que lo diseñaron.
Puedes usar algunos de los otros métodos para obtenerlo en hours :
long hours = duration.toHours();
o minutes :
long minutes = duration.toMinutes();
Me gustaría agregar a la respuesta de bigt (+1) que señalaba la utilidad de toHours
, toMinutes
y otros métodos de conversión. La especificación de la duración dice:
Esta clase modela una cantidad o cantidad de tiempo en términos de segundos y nanosegundos. [...]
El rango de una duración requiere el almacenamiento de un número mayor que un largo. Para lograr esto, la clase almacena unos segundos que representan largos y un 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érdidas.
Por el contrario, hay una variedad de métodos que incluyen toDays
, toHours
, toMillis
toMinutes
y toNanos
que hacen conversión en el valor de duración. Estas conversiones tienen pérdidas porque implican el truncamiento de los datos, o podrían generar una excepción si el valor de la duración no se puede representar en el formato solicitado.
Es claramente parte del diseño que los métodos get extraen los datos sin conversión y que los métodos to to realizan conversiones de algún tipo.
Para obtener los componentes de hora / minuto / segundo de una manera "normalizada", necesita calcularlos manualmente; el siguiente código se copia esencialmente del método 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 esta duración de salida:
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 utilizar la interfaz de Duración, puede encontrar fácilmente la pantalla de cadena exacta que desea. solo necesita reducir la unidad temporal mayor de la duración actual.
por ejemplo:
- calcular dias
- reducir días de duración
- calcular horas
- reduce las horas de duración, etc. hasta que obtengas la duración = 0