Biblioteca estándar
Duraciones, periodos y aritmética de fechas
Separa tiempo transcurrido de calendario, entiende los ajustes de fin de mes y calcula diferencias sin inventar días de 24 horas.
Desde Java 8
Este capítulo cubre Java 8. Estás leyendo en .
«Un día» necesita contexto
Una sesión que caduca dentro de 24 horas y una cita mañana a la misma hora no tienen la misma
regla. El cambio de hora puede hacer que entre dos mediodías consecutivos transcurran 23 o 25 horas.
java.time separa las cantidades de tiempo de las cantidades de calendario.
| Tipo | Qué representa | Ejemplo |
|---|---|---|
Duration | Cantidad basada en segundos y nanosegundos | Una espera de 90 minutos |
Period | Cantidad de años, meses y días del calendario ISO | Una renovación cada mes |
Duration.ofDays(1) significa exactamente 24 horas. Period.ofDays(1) significa avanzar
un día de calendario cuando lo aplicas a un tipo que lo admite. Un mes no tiene un número fijo
de segundos, así que no hay una conversión universal de Period.ofMonths(1) a Duration.
Dos maneras de avanzar desde el mismo origen
-
01
Origen en Madrid
30 marzo 2024, 12:00El offset inicial es +01:00, antes del salto de primavera. -
02
Un día de calendario
31 marzo, 12:00 +02:00Con Period conservas la hora local: han pasado 23 horas. -
03
Veinticuatro horas
31 marzo, 13:00 +02:00Con Duration conservas el tiempo transcurrido desde el origen.
Sumar con una intención explícita
Todos los programas del capítulo son compatibles con Java 8. Guarda este como DayOrHours.java.
Las salidas con Europe/Madrid usan las reglas históricas incluidas en el JDK 21 de la prueba.
import java.time.Duration;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class DayOrHours {
public static void main(String[] args) {
ZonedDateTime start = ZonedDateTime.of(
2024, 3, 30, 12, 0, 0, 0, ZoneId.of("Europe/Madrid"));
ZonedDateTime nextDay = start.plus(Period.ofDays(1));
ZonedDateTime after24Hours = start.plus(Duration.ofDays(1));
System.out.println(nextDay);
System.out.println(after24Hours);
System.out.println(Duration.between(start, nextDay).toHours());
System.out.println(Duration.between(start, after24Hours).toHours());
}
}Salida:
2024-03-31T12:00+02:00[Europe/Madrid]
2024-03-31T13:00+02:00[Europe/Madrid]
23
24En ZonedDateTime, plusDays(1) opera sobre el calendario local y plusHours(24) sobre
la línea temporal. Si el resultado local cae en un hueco o un solapamiento, también se aplican
las reglas de resolución de la zona. No todas las horas locales se pueden conservar siempre.
Fin de mes: ajustar no es recordar
Sumar un mes al 31 de enero no puede producir un 31 de febrero. plusMonths ajusta el día
al último válido del mes de destino. Lo mismo puede pasar al cambiar el año de un 29 de febrero.
No conserva por dentro una intención oculta de «último día del mes».
import java.time.LocalDate;
public class MonthEnd {
public static void main(String[] args) {
LocalDate start = LocalDate.of(2024, 1, 31);
LocalDate february = start.plusMonths(1);
System.out.println(february);
System.out.println(february.plusMonths(1));
System.out.println(start.plusMonths(2));
System.out.println(february.minusMonths(1));
System.out.println(LocalDate.of(2024, 2, 29).plusYears(1));
}
}Salida:
2024-02-29
2024-03-29
2024-03-31
2024-01-29
2025-02-28La suma encadenada perdió el día 31 al pasar por febrero. Restar después no recupera necesariamente la fecha inicial. Para un vencimiento mensual, define si quieres mantener el día original, ajustar cada resultado o usar siempre el último día del mes. No dejes que una cadena de llamadas decida esa política por accidente.
Ajustadores para reglas de calendario
TemporalAdjusters expresa reglas como el último día del mes o el siguiente lunes.
next siempre avanza; nextOrSame puede devolver la misma fecha si ya cumple la condición.
Estos métodos no conocen festivos ni jornadas laborables: esas reglas son de tu aplicación.
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
public class CalendarRules {
public static void main(String[] args) {
LocalDate monday = LocalDate.of(2024, 4, 1);
LocalDate january = LocalDate.of(2024, 1, 31);
System.out.println(monday.with(
TemporalAdjusters.next(DayOfWeek.MONDAY)));
System.out.println(monday.with(
TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY)));
System.out.println(january.plusMonths(1).with(
TemporalAdjusters.lastDayOfMonth()));
System.out.println(january.plusMonths(2).with(
TemporalAdjusters.lastDayOfMonth()));
}
}Salida:
2024-04-08
2024-04-01
2024-02-29
2024-03-31Para una serie mensual, calcula cada vencimiento desde la fecha base y su índice de mes. Después aplica el ajuste acordado. Así no acumulas las pérdidas de día de resultados intermedios. En cualquier caso, conserva el resultado: fechas, duraciones y periodos son inmutables.
Diferencias: componentes o unidades completas
Period.between descompone una diferencia entre fechas en años, meses y días.
getDays() devuelve solo el componente de días, no el total del intervalo.
Para contar días completos entre fechas, usa ChronoUnit.DAYS.between.
import java.time.Duration;
import java.time.LocalDate;
import java.time.Period;
import java.time.temporal.ChronoUnit;
public class Differences {
public static void main(String[] args) {
LocalDate start = LocalDate.of(2024, 1, 1);
LocalDate end = LocalDate.of(2024, 3, 2);
Period calendar = Period.between(start, end);
System.out.println(calendar);
System.out.println(calendar.getDays());
System.out.println(ChronoUnit.DAYS.between(start, end));
System.out.println(ChronoUnit.DAYS.between(end, start));
System.out.println(Duration.ofMinutes(90).toHours());
System.out.println(Duration.ofHours(49).toDays());
}
}Salida:
P2M1D
1
61
-61
1
2P2M1D expresa dos meses y un día. Duration.toHours() y toDays() devuelven unidades
completas, descartando la fracción hacia cero, no redondeando al entero más cercano.
En una Duration, un día usado por toDays() son 24 horas, no un cambio de fecha local.
Invierte los extremos de between y obtendrás una cantidad negativa.
Tipos que no admiten la operación
No puedes aplicar una Duration a un LocalDate: la fecha no tiene segundos ni hora.
Tampoco puedes pedir Duration.between de dos fechas sin hora. Esas operaciones lanzan
UnsupportedTemporalTypeException. Decide si necesitas días de calendario o si debes
incorporar una hora y una zona para medir tiempo transcurrido.
Un LocalTime vuelve a empezar al pasar medianoche. Si restas 23:00 a 01:00 sin fechas,
no has dicho que 01:00 sea «mañana». Para turnos nocturnos, conserva las fechas en el modelo.
Un LocalDateTime tampoco conoce cambios de offset: para medir tiempo real entre eventos,
trabaja con Instant o con valores que incluyan zona u offset.
Qué te llevas
Durationmide segundos y nanosegundos;Periodexpresa años, meses y días.- Un día de calendario no garantiza 24 horas en una zona con transiciones.
- Ajustar fin de mes puede perder el día original: sumar y restar no siempre se deshacen.
TemporalAdjustersexpresa reglas de calendario, pero no conoce tus festivos.- Un componente de
Periodno es un total; elige la unidad y los extremos de la diferencia.