Buscar a data de 30 dias antes

Senhores, bom dia.

Estava buscando um código para pegar a data de 30 dias atrás. Pensei no código abaixo mas, óbvio, eu teria que melhorar ele para testar correr dentro do número exato de dias de cada mês, com suas particularidades.

Minha dúvida é: vocês recomendariam alguma forma mais simples de fazer?

Desde já muito obrigado.

=========================

    Date data = new Date();
    Date dataAnterior = new Date();

    data = Date.from(Instant.now());

    dataAnterior.setYear(data.getMonth() == 1 ? data.getYear() - 1 : data.getYear());
    dataAnterior.setMonth(data.getMonth() == 1 ? 12 : data.getMonth() - 1);
    dataAnterior.setDate(data.getDate()); // ok, preciso melhorar isso, para calcular exatamente o dia

Se for java 8+, talvez assim seja melhor:

LocalDateTime hoje = LocalDateTime.now();
LocalDateTime menos30Dias = hoje.minus(Duration.ofDays(30));

System.out.println(hoje); 			// 2021-06-10T11:21:56
System.out.println(menos30Dias); 	// 2021-05-11T11:21:56
1 curtida

Como você está usando Instant, então com certeza está usando Java >= 8. Sendo assim, a recomendação é que use o java.time.

Só que aí depende muito do que você precisa.

Se quer apenas o dia, mês e ano, sem se importar com o horário, pode usar java.time.LocalDate:

// data atual, menos 30 dias
LocalDate dataAnterior = LocalDate.now().minusDays(30);

Veja que ela já tem o método minusDays (muito mais direto ao ponto do que criar um Duration). Claro que se você já tiver um Duration criado, aí não tem problema usá-lo, mas eu não criaria um só para isso sendo que já existe um método que faz isso direto (e claro que para um código simples, tanto faz).

Se usar um LocalDateTime, como sugerido, você também terá o horário, que pode ou não ser o desejado (ou tanto faz, depende de cada caso). Usando Date você não tem escolha, mas no java.time foram criados vários tipos diferentes justamente para você usar o mais apropriado para cada situação. Se não quer saber do horário, use LocalDate (ou, se quiser a data, hora e fuso horário, use ZonedDateTime, por exemplo).

O bom é que todas essas classes possuem o método minusDays, com suas particularidades: LocalDateTime subtrai os dias mas mantém o mesmo horário, ZonedDateTime faz o mesmo, porém também cuidando dos ajustes relativos ao fuso horário, como por exemplo o horário de verão (se a data está em horário de verão, mas ao subtrair você cai em uma data no horário “normal”, ZonedDateTime já ajusta isso; as outras classes não se preocupam com isso porque nem tem fuso horário, etc).


Mas é isso mesmo que você quer?

Pelo seu código, o que dá a entender é que na verdade você quer subtrair 1 mês. E isso tem um problema, pois já testou o seu código quando a data é 31 de março?

// 31 de março
data = Date.from(Instant.parse("2021-03-31T10:00:00Z"));
// o resto do código é igual

O resultado será 3 de março. Isso porque na verdade você criou uma data correspondente a 31 de fevereiro, só que internamente Date acaba ajustando para 3 de março (o “raciocínio” é que fevereiro tem 28 dias, então “31 de fevereiro” seriam 3 dias depois do dia 28 - se faz sentido ou não, é outra história, mas é isso que acontece).

O problema é justamente o fato de que um mês nem sempre é igual a 30 dias. Na verdade existem 7 meses com 31 dias, 4 com 30 dias e 1 com 28 ou 29, ou seja, a maioria dos meses (exatamente dois terços deles) não têm 30 dias.

Isso leva a várias armadilhas. Claro que em vários casos vai “funcionar”, mas tem muitos corner cases. Se a data for 31 de janeiro, ao subtrair 30 dias, o resultado é 1 de janeiro (mas usando o seu código, o resultado é 31 de dezembro do ano anterior, então estou assumindo que é isso que você quer).

Então na verdade o que você precisaria é subtrair 1 mês:

LocalDate dataAnterior = LocalDate.now().minusMonths(1);

Lembrando que ainda tem casos “estranhos”: se a data for 31 de março, ao subtrair 1 mês o resultado é 28 de fevereiro (ou 29 se for ano bissexto). O java.time faz esse ajuste por entender que, se estou subtraindo um mês, então o resultado deve ser alguma data no mês anterior. Se o dia 31 não existe no mês anterior, então é ajustado para o último dia válido.

Enfim, decida o que quer: subtrair 30 dias ou 1 mês (em alguns casos vai ser equivalente, mas em outros não).


E só para encerrar, se precisa muito que o resultado seja um java.util.Date, você pode usar java.util.Calendar para fazer os cálculos:

// data atual
Calendar cal = Calendar.getInstance();

// subtrai 1 mês
cal.add(Calendar.MONTH, -1);
// **Ou** subtrai 30 dias
cal.add(Calendar.DAY_OF_MONTH, -30);

Date dataAnterior = cal.getTime();

Lembrando dos mesmos detalhes já apontados: se a data for 31 de março e subtrair 1 mês, o resultado será 28 de fevereiro, mas se subtrair 30 dias, o resultado será 1 de março.


Obs: vale lembrar que com Date e Calendar, os meses são indexados em zero (janeiro é zero, fevereiro é 1, etc), então seu código já não estava tratando isso corretamente. Ele só “funcionou” por coincidência, pois se o mês for janeiro (ou seja, zero), você seta o mês para -1, e internamente o Date entende que isso é “dezembro do ano anterior”).

2 curtidas

Que aula! Muito obrigado!