Analizando sus expectativas (en pseudocódigo)
startDate.plus(Period.between(startDate, endDate)) == endDate
Tenemos que discutir varios temas:
- ¿Cómo manejar unidades separadas como meses o días?
- ¿Cómo se define la adición de una duración (o "período")?
- ¿Cómo determinar la distancia temporal (duración) entre dos fechas?
- ¿Cómo se define la resta de una duración (o "período")?
Primero veamos las unidades. Los días no son un problema porque son la unidad de calendario más pequeña posible y cada fecha de calendario difiere de cualquier otra fecha en enteros enteros de días. Entonces siempre tenemos un pseudocódigo igual si es positivo o negativo:
startDate.plus(ChronoUnit.Days.between(startDate, endDate)) == endDate
Sin embargo, los meses son difíciles porque el calendario gregoriano define los meses calendario con diferentes duraciones. Entonces, puede surgir la situación de que la adición de cualquier número entero de meses a una fecha puede causar una fecha no válida:
[2019-08-31] + P1M = [2019-09-31]
La decisión de java.time
reducir la fecha de finalización a una válida, aquí [2019-09-30], es razonable y corresponde a las expectativas de la mayoría de los usuarios porque la fecha final aún conserva el mes calculado. Sin embargo, esta adición que incluye una corrección de fin de mes NO es reversible , vea la operación revertida llamada resta:
[2019-09-30] - P1M = [2019-08-30]
El resultado también es razonable porque a) la regla básica de la suma mensual es mantener el día del mes lo más posible yb) [2019-08-30] + P1M = [2019-09-30].
¿Cuál es la adición de una duración (período) exactamente?
En java.time
, a Period
es una composición de elementos que consta de años, meses y días con cualquier cantidad parcial entera. Por lo tanto, la adición de a Period
se puede resolver a la suma de los importes parciales a la fecha de inicio. Como los años son siempre convertibles a 12 múltiplos de meses, primero podemos combinar años y meses y luego sumar el total en un solo paso para evitar efectos secundarios extraños en los años bisiestos. Los días se pueden agregar en el último paso. Un diseño razonable como hecho en java.time
.
¿Cómo determinar el derecho Period
entre dos fechas?
Primero discutamos el caso cuando la duración es positiva, lo que significa que la fecha de inicio es anterior a la fecha de finalización. Entonces siempre podemos definir la duración determinando primero la diferencia en meses y luego en días. Este pedido es importante para lograr un componente de mes porque, de lo contrario, cada duración entre dos fechas solo consistiría en días. Usando sus fechas de ejemplo:
[2019-09-30] + P1M1D = [2019-10-31]
Técnicamente, la fecha de inicio se adelanta por la diferencia calculada en meses entre el inicio y el final. Luego, el día delta como diferencia entre la fecha de inicio movida y la fecha de finalización se agrega a la fecha de inicio movida. De esta manera podemos calcular la duración como P1M1D en el ejemplo. Hasta ahora muy razonable.
¿Cómo restar una duración?
El punto más interesante en el ejemplo de adición anterior es que, por accidente, NO hay corrección de fin de mes. Sin embargo, java.time
no puede hacer la resta inversa. Primero resta los meses y luego los días:
[2019-10-31] - P1M1D = [2019-09-29]
Si, en java.time
cambio, hubiera intentado revertir los pasos de la suma anterior, entonces la elección natural habría sido restar primero los días y luego los meses . Con este orden cambiado, obtendríamos [2019-09-30]. El orden cambiado en la resta ayudaría siempre que no hubiera una corrección de fin de mes en el paso de suma correspondiente. Esto es especialmente cierto si el día del mes de cualquier fecha de inicio o finalización no es mayor que 28 (la duración mínima posible del mes). Lamentablemente, java.time
ha definido otro diseño para la resta Period
que conduce a resultados menos consistentes.
¿Es la suma de una duración reversible en la resta?
Primero tenemos que entender que el orden de cambio sugerido en la resta de una duración de una fecha de calendario dada no garantiza la reversibilidad de la adición. Ejemplo de contador que tiene una corrección de fin de mes en la adición:
[2011-03-31] + P3M1D = [2011-06-30] + P1D = [2011-07-01] (ok)
[2011-07-01] - P3M1D = [2011-06-30] - P3M = [2011-03-30] :-(
Cambiar el orden no es malo porque produce resultados más consistentes. Pero, ¿cómo curar las deficiencias restantes? El único camino que queda es cambiar también el cálculo de la duración. En lugar de usar P3M1D, podemos ver que la duración P2M31D funcionará en ambas direcciones:
[2011-03-31] + P2M31D = [2011-05-31] + P31D = [2011-07-01] (ok)
[2011-07-01] - P2M31D = [2011-05-31] - P2M = [2011-03-31] (ok)
Entonces, la idea es cambiar la normalización de la duración calculada. Esto se puede hacer observando si la suma del delta del mes calculado es reversible en un paso de resta, es decir, evita la necesidad de una corrección de fin de mes. java.time
desafortunadamente no ofrece tal solución. No es un error, pero puede considerarse como una limitación de diseño.
¿Alternativas?
He mejorado mi biblioteca de tiempo Time4J con métricas reversibles que implementan las ideas dadas anteriormente. Ver el siguiente ejemplo:
PlainDate d1 = PlainDate.of(2011, 3, 31);
PlainDate d2 = PlainDate.of(2011, 7, 1);
TimeMetric<CalendarUnit, Duration<CalendarUnit>> metric =
Duration.inYearsMonthsDays().reversible();
Duration<CalendarUnit> duration =
metric.between(d1, d2); // P2M31D
Duration<CalendarUnit> invDur =
metric.between(d2, d1); // -P2M31D
assertThat(d1.plus(duration), is(d2)); // first invariance
assertThat(invDur, is(duration.inverse())); // second invariance
assertThat(d2.minus(duration), is(d1)); // third invariance
date.plus(period).minus(period)
) el resultado no siempre es la misma fecha. Esta pregunta es más sobrePeriod.between
los invariantes de la función.