Analisando sua expectativa (em pseudo-código)
startDate.plus(Period.between(startDate, endDate)) == endDate
nós temos que discutir vários tópicos:
- como lidar com unidades separadas, como meses ou dias?
- como é definida a adição de uma duração (ou "período")?
- como determinar a distância temporal (duração) entre duas datas?
- como é definida a subtração de uma duração (ou "período")?
Vamos primeiro olhar para as unidades. Os dias não são um problema, pois são a menor unidade possível do calendário e cada data do calendário difere de qualquer outra data em números inteiros completos de dias. Portanto, sempre temos no pseudo-código igual se positivo ou negativo:
startDate.plus(ChronoUnit.Days.between(startDate, endDate)) == endDate
Os meses, no entanto, são complicados porque o calendário gregoriano define os meses com comprimentos diferentes. Portanto, pode ocorrer que a adição de qualquer número inteiro de meses a uma data possa causar uma data inválida:
[2019-08-31] + P1M = [2019-09-31]
A decisão de java.time
reduzir a data de término para uma data válida - aqui [30/09/2019] - é razoável e corresponde às expectativas da maioria dos usuários, porque a data final ainda preserva o mês calculado. No entanto, essa adição, incluindo uma correção de final de mês, NÃO é reversível . Consulte a operação revertida chamada subtração:
[30/09/2019] - P1M = [30/08 2019]
O resultado também é razoável porque: a) a regra básica da adição do mês é manter o dia do mês o máximo possível eb) [30/08/2019] + P1M = [30-09-2019].
Qual é a adição de uma duração (período) exatamente?
Em java.time
, a Period
é uma composição de itens que consiste em anos, meses e dias com quaisquer valores parciais inteiros. Portanto, a adição de a Period
pode ser resolvida com a adição de valores parciais à data de início. Como os anos são sempre conversíveis em 12 múltiplos de meses, primeiro podemos combinar anos e meses e, em seguida, adicionar o total em uma etapa para evitar efeitos colaterais estranhos nos anos bissextos. Os dias podem ser adicionados na última etapa. Um design razoável como feito em java.time
.
Como determinar o direito Period
entre duas datas?
Vamos discutir primeiro o caso em que a duração é positiva, o que significa que a data inicial é anterior à data final. Sempre podemos definir a duração, determinando primeiro a diferença em meses e depois em dias. Esse pedido é importante para obter um componente do mês porque, caso contrário, cada duração entre duas datas consistiria apenas em dias. Usando suas datas de exemplo:
[2019-09-30] + P1M1D = [2019-10-31]
Tecnicamente, a data de início é adiantada pela diferença calculada em meses entre o início e o fim. Em seguida, o delta do dia como diferença entre a data de início movida e a data final é adicionado à data de início movida. Dessa forma, podemos calcular a duração como P1M1D no exemplo. Até agora, tão razoável.
Como subtrair uma duração?
O ponto mais interessante no exemplo de adição anterior é que, por acidente, NÃO há correção de final de mês. mesmo assimjava.time
falha em fazer a subtração reversa. Primeiro, subtrai os meses e depois os dias:
[2019-10-31] - P1M1D = [2019-09-29]
Se java.time
, em vez disso, tentasse reverter as etapas da adição antes, a escolha natural seria subtrair primeiro os dias e depois os meses . Com essa ordem alterada, teríamos [2019-09-30]. A ordem alterada na subtração ajudaria desde que não houvesse correção de final de mês na etapa de adição correspondente. Isso é especialmente verdadeiro se o dia do mês de qualquer data inicial ou final não for maior que 28 (a duração mínima possível do mês). Infelizmente java.time
, definiu outro design cuja subtração Period
leva a resultados menos consistentes.
A adição de uma duração é reversível na subtração?
Primeiro, precisamos entender que a ordem alterada sugerida na subtração de uma duração de uma determinada data do calendário não garante a reversibilidade da adição. Exemplo de contador que possui uma correção de final de mês na adição:
[2011-03-31] + P3M1D = [2011-06-30] + P1D = [2011-07-01] (ok)
[2011-07-01] - P3M1D = [2011-06-30] - P3M = [2011-03-30] :-(
Alterar a ordem não é ruim porque gera resultados mais consistentes. Mas como curar as deficiências restantes? A única maneira que resta é alterar também o cálculo da duração. Em vez de usar o P3M1D, podemos ver que a duração P2M31D funcionará nas duas direções:
[2011-03-31] + P2M31D = [2011-05-31] + P31D = [2011-07-01] (ok)
[2011-07-01] - P2M31D = [2011-05-31] - P2M = [2011-03-31] (ok)
Portanto, a ideia é alterar a normalização da duração calculada. Isso pode ser feito verificando se a adição do delta do mês calculado é reversível em uma etapa de subtração - ou seja, evita a necessidade de uma correção no final do mês. java.time
infelizmente não oferece essa solução. Não é um bug, mas pode ser considerado uma limitação de design.
Alternativas?
Aprimorei minha biblioteca de tempo Time4J por métricas reversíveis que implementam as idéias fornecidas acima. Veja o seguinte exemplo:
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)
), o resultado nem sempre é a mesma data. Esta questão é mais sobrePeriod.between
invariantes da função.