Como garantir que datas como 31/2/2012 não sejam aceitas pela sua classe Data?

Olá pessoal, estou fazendo um pequeno estudo sobre a biblioteca LocalDate. Por isso decidir tentar resolver essa questão da apostila da Caelum “Como garantir que datas como 31/2/2012 não sejam aceitas pela sua classe Data?” (como o título evidencia).
Basicamente criei as seguintes variáveis:

private LocalDate dataAbertura;
DateTimeFormatter formatar = DateTimeFormatter.ofPattern(“dd/MM/yyyy”);

Então a data é informada como String e passada para variável -dataAbertura- através do método -validarData()-, que usa a variável -formatar- para definir o formato da data.

public void validarData(String dtAbertura) throws ParseException{
this.dataAbertura = LocalDate.parse(dtAbertura, formatar);
}

Para exibir a data estou usando o seguinte método:

public String getDataAbertura() {
return this.dataAbertura.format(formatar);
}

Porém, quando copilo aparece a seguinte mensagem de erro:

Exception in thread “main” java.time.format.DateTimeParseException: Text ‘28/2/2019’ could not be parsed at index 3
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
at java.time.LocalDate.parse(LocalDate.java:400)
at Banco_Revisao_Hiury.Conta.validarData(Conta.java:130)
at Banco_Revisao_Hiury.RevisaoTreinoBanco.main(RevisaoTreinoBanco.java:31)
C:\Users\GEMEOS\AppData\Local\NetBeans\Cache\8.2rc\executor-snippets\run.xml:53: Java returned: 1
FALHA NA CONSTRUÇÃO (tempo total: 0 segundos)

Já li manuais, artigos e postagem sobre erros semelhantes (se não iguais) ao meu aqui no guj, mas nada esta funcionado. Poderiam me ajudar por favor?

1 curtida

Pelo que li sobre o @hugokotsubo, ele deve ser uma das pessoas mais indicadas do Brasil pra responder!

:grin:

1 curtida

Obrigado! :slight_smile:


O erro ocorre porque o formato “MM” exige que hajam 2 dígitos no mês (ou seja, a string deveria ter “02” em vez de somente “2”).

Se quiser 1 ou 2 dígitos, use apenas uma letra “M”:

DateTimeFormatter parser = DateTimeFormatter.ofPattern("dd/M/yyyy");
LocalDate data = LocalDate.parse("28/2/2019", parser);

Mas isso ainda não é suficiente, pois o código acima aceita datas como “31/2/2012”, fazendo alguns ajustes “estranhos”:

DateTimeFormatter parser = DateTimeFormatter.ofPattern("dd/M/yyyy");
LocalDate data = LocalDate.parse("31/2/2012", parser);
System.out.println(data.format(parser)); // 29/2/2012

Como pode ver, o dia 31/2 foi ajustado para 29/2.

Se não quer que sejam aceitas datas inválidas, basta trocar o java.time.format.ResolverStyle:

DateTimeFormatter parser = DateTimeFormatter.ofPattern("dd/M/yyyy")
    .withResolverStyle(ResolverStyle.STRICT);
LocalDate data = LocalDate.parse("31/2/2012", parser); // erro!

Assim, será lançada uma exceção caso a data seja inválida. No caso, o código acima dá este erro:

java.time.format.DateTimeParseException: Text ‘31/2/2012’ could not be parsed: Unable to obtain LocalDate from TemporalAccessor: {DayOfMonth=31, MonthOfYear=2, YearOfEra=2012},ISO of type java.time.format.Parsed

Então neste caso, para detectar que a data é inválida, precisaria de um bloco try/catch:

try {
    LocalDate data = LocalDate.parse("31/2/2012", parser);
    // se passou a data é válida, continue usando ela
} catch (DateTimeParseException e) {
    // data inválida
}

Só pra constar, existem 3 valores possíveis para ResolverStyle:

O modo LENIENT permite datas inválidas e faz ajustes automáticos. Por exemplo, 31/06/2017 é ajustado para 01/07/2017. Além disso, este modo aceita valores fora dos limites definidos para cada campo, como o dia 32, mês 15, etc. Por exemplo, 32/15/2017 é ajustado para 01/04/2018.

O modo SMART também faz alguns ajustes quando a data é inválida, então 31/06/2017 é interpretado como 30/06/2017. A diferença para LENIENT é que este modo não aceita valores fora dos limites dos campos (mês 15, dia 32, etc), então 32/15/2017 dá erro (lança um DateTimeParseException). É o modo default quando você cria um DateTimeFormatter, por isso ele aceita “31/2/2012” se você não indicar nenhum ResolverStyle.

O modo STRICT é o mais restrito: não aceita valores fora dos limites e nem faz ajustes quando a data é inválida, portanto 31/06/2017 e 32/15/2017 dão erro (lançam um DateTimeParseException). Este parece ser o que você precisa, para que ele não aceite “31/2/2012”.

2 curtidas

Muito obrigado por ajudar a resolver meu problema. Nunca iria imaginar que era algo tom simples.:joy:

/* 
* Verifica se a data é valida
* Modo de usar:
    if(isDateValid(data)==false){  
        return("data inválida");  
    }
* parametros: 
    -pData: data a ser verificada
*/
public static boolean isDataValida(String pData){
    if (pData == null || pData.equals(""))  
        return false; 
    try {
        pData = pData.replaceAll("\\.", "").replaceAll("\\-", "").replaceAll("\\_", "").replaceAll("/", "").replaceAll(" ", ""); //tira todos os caracteres
        DateFormat df = new SimpleDateFormat("dd/MM/yyyy");
        pData=df.format(pData);
        df.setLenient(false);
        df.parse(pData);
        return true;
    } catch (ParseException e) {
        return false;
    }
}