Eu tenho dois LocalDate
s declarados da seguinte maneira:
val startDate = LocalDate.of(2019, 10, 31) // 2019-10-31
val endDate = LocalDate.of(2019, 9, 30) // 2019-09-30
Então eu calculo o período entre eles usando a Period.between
função:
val period = Period.between(startDate, endDate) // P-1M-1D
Aqui o período tem a quantidade negativa de meses e dias, o que é esperado, dado que endDate
é anterior a startDate
.
No entanto, quando adiciono isso de period
volta ao startDate
, o resultado que estou obtendo não é o endDate
, mas a data um dia antes:
val endDate1 = startDate.plus(period) // 2019-09-29
Então a questão é: por que o invariante não
startDate.plus(Period.between(startDate, endDate)) == endDate
espera para essas duas datas?
É Period.between
quem retorna um período incorreto ou LocalDate.plus
quem o adiciona incorretamente?
date.plus(period).minus(period)
), o resultado nem sempre é a mesma data. Esta questão é mais sobrePeriod.between
invariantes da função.java.time
aritmética do calendário funciona. Basicamente, adicionar e remover não é conversível entre si, principalmente se o dia do mês de uma ou de duas datas for maior que 28. Consulte também a documentação da classe AbstractDuration no meu tempo, lib Time4J para obter mais informações matemáticas ...AbstractDuration
documentos estão afirmando que a invariânciat1.plus(t1.until(t2)).equals(t2) == true
deve se manter, e eu estou perguntando por que não é o casojava.time
aqui.Respostas:
Se você olhar como
plus
é implementado paraLocalDate
você verá
plusMonths(...)
eplusDays(...)
ali.plusMonths
lida com casos em que um mês tem 31 dias e o outro 30. Portanto, o código a seguir será impresso em2019-09-30
vez de inexistente2019-09-31
Depois disso, subtrair um dia resulta em
2019-09-29
. Este é o resultado correto, uma vez2019-09-29
e2019-10-31
têm 1 mês 1 dia de intervaloO
Period.between
cálculo é estranho e, neste caso, resume-se aonde
getProlepticMonth
é o número total de meses entre 00-00-00. Nesse caso, são 1 mês e 1 dia.Pelo meu entendimento, é um bug em um
Period.between
LocalDate#plus
interação de períodos negativos e , pois o código a seguir tem o mesmo significadomas imprime o correto
2019-10-31
.O problema é que
LocalDate#plusMonths
normaliza a data para estar sempre "correta". No código a seguir, você pode ver que, após subtrair 1 mês do2019-10-31
resultado,2019-09-31
ele é normalizado para2019-10-30
fonte
Eu acredito que você está simplesmente sem sorte. O invariante que você inventou parece razoável, mas não é válido em java.time.
Parece que o
between
método apenas subtrai os números do mês e os dias do mês e, como os resultados têm o mesmo sinal, está contente com esse resultado. Acho que concordo que provavelmente uma decisão melhor poderia ter sido tomada aqui, mas como @Meno Hochschild afirmou corretamente, a matemática envolvendo os 29, 30 ou 31 meses dificilmente pode ser clara, e não ouso sugerir qual seria a melhor regra fui.Aposto que eles não vão mudar isso agora. Nem mesmo se você registrar um relatório de erro (que você pode sempre tentar). Muito código já conta com o funcionamento de mais de cinco anos e meio.
Adicionar
P-1M-1D
novamente à data de início funciona da maneira que eu esperava. Subtrair 1 mês de (realmente adicionando –1 mês a) a 31 de outubro gera 30 de setembro e subtrair 1 dia gera 29 de setembro. Novamente, não está claro, você poderia argumentar a favor de 30 de setembro.fonte
Analisando sua expectativa (em pseudo-código)
nós temos que discutir vários tópicos:
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:
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
, aPeriod
é uma composição de itens que consiste em anos, meses e dias com quaisquer valores parciais inteiros. Portanto, a adição de aPeriod
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 emjava.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 assim
java.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). Infelizmentejava.time
, definiu outro design cuja subtraçãoPeriod
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:
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:
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:
fonte