Regex for a valid YYYY year in java?

Asked

Viewed 72 times

1

I am using these regular expressions for a future date check:

Pattern padraoDia = Pattern.compile("([1-9]|0[1-9])|[1-2][0-9]|3[0-1]");
Pattern padraoMes = Pattern.compile("(0[1-9]|[1-9])|1[0-2]");
Pattern padraoAno = Pattern.compile("[0-9]{4}");

So I do the verification through the if to carry out what I wish:

if (padraoDia.matcher(dia).matches() && padraoMes.matcher(mes).matches() && padraoAno.matcher(ano).matches()) {}

dia, mes and ano are strings. It turns out that if the string ano for "8052" it will validate how true. It has as I define in my regex an interval of years, for example 2000 to 2030?

  • 3

    And why do this validation in REGEX? It would not be simpler to treat the value itself?

1 answer

6


It’s like I set in my regex an interval of years

It does, but it’s not worth it.

Regex basically works with text, and even digits are treated as mere characters, regardless of their numerical value. To check if the string contains a number, and if it is in a certain range of values, it is easier to convert the string to number. Something like this:

public static boolean anoValido(String ano) {
    try {
        // converte a string para número
        int valorAno = Integer.parseInt(ano);
        // verifica se o valor está na faixa de anos válidos
        return 2000 <= valorAno && valorAno <= 2030;
    } catch (NumberFormatException e) {
        // se quiser, não precisa imprimir mensagem nenhuma, só retorne false
        System.out.println("Valor do ano não é um número");
    }
    return false;
}

If the string is not number, it falls into the catch, and then you can decide how to treat the exception (print error message, just return false, or let the exception burst by removing the try/catch, and who called the method turn to treat it, etc).


A better way

If you want to validate the date as a whole, it is better to treat the day/month/year combination, rather than validating each one separately. This is because not every combination is valid: there are months that do not have 31 days, there are February in leap years, etc. That is, even if the day is 31 (which is valid), it will not always be a valid date because it depends on the month (and even the year, if it is February 29).

So, better to use a date API, which already has all these validations ready.
From the Java 8, you can use the API java.time:

public static boolean dataValida(String strDia, String strMes, String strAno) {
    try {
        int ano = Integer.parseInt(strAno);
        // se o ano é válido, tenta criar a data
        // se o ano for inválido, nem tento converter o dia e mês
        if (2000 <= ano && ano <= 2030) {
            // converte as strings para números
            int dia = Integer.parseInt(strDia);
            int mes = Integer.parseInt(strMes);
            // tenta criar a data, se der certo retorna true
            LocalDate.of(ano, mes, dia);
            return true;
        }
    } catch (NumberFormatException | DateTimeException e) {
        // imprime mensagem de erro? Não faz nada?
    }
    return false;
}

That is, if the year is in the desired range, I try to create the date, using a java.time.LocalDate (this class already checks if the month has 31 days, if it is leap year and February can have 29 days, if some value is outside the range - such as month greater than 12, negative values, etc). If the date is invalid, it launches a DateTimeException, and if any string is not a number, the code throws a NumberFormatException (and both fall into the catch, returning false).


One detail is that so he ends up accepting values like "0001" or "1" (and the method parseInt converts all to number 1). But if the idea is that strings should have exactly a certain amount of digits (day and month always with 2 digits, year always with 4), an alternative is to use a java.time.format.DateTimeFormatter:

public static boolean dataValida(String strDia, String strMes, String strAno) {
    try {
        // dia e mês sempre devem ter 2 dígitos, ano deve ter 4 dígitos
        DateTimeFormatter parser = DateTimeFormatter.ofPattern("ddMMuuuu")
            .withResolverStyle(ResolverStyle.STRICT);
        LocalDate data = LocalDate.parse(strDia + strMes + strAno, parser);
        int ano = data.getYear();
        // verifica o ano
        return 2000 <= ano && ano <= 2030;
    } catch (DateTimeParseException e) {
        // imprime mensagem de erro? Não faz nada?
    }
    return false;
}

So you use the right tool for your problem (a date API to validate a date). With regex can even "work", but is not the most suitable.


For Java <= 7, you would have to use the legacy API (java.util.Date, java.util.Calendar and java.text.SimpleDateFormat):

public static boolean dataValida(String strDia, String strMes, String strAno) {
    try {
        SimpleDateFormat sdf = new SimpleDateFormat("ddMMyyyy");
        sdf.setLenient(false); // não aceitar valores inválidos
        Date date = sdf.parse(strDia + strMes + strAno);

        // pegar o ano
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        int ano = cal.get(Calendar.YEAR);
        return 2000 <= ano && ano <= 2030;
    } catch (ParseException e) {
        return false;
    }
}

The detail is that SimpleDateFormat by default is lenient and accepts "anything", then we need to do setLenient(false) so that he only accepts valid dates.

The idea is similar: first I try to create the date. If it worked, I check the year. If the date is invalid, it falls on catch and return false.


Do you really want to use regex?

I already said it’s not to use, but if you want much, at first would be thus:

Pattern padraoAno = Pattern.compile("20([0-2][0-9]|30)");

I mean, it starts with 20 and then there are two possibilities:

  • [0-2][0-9]: a digit from zero to 2, followed by a digit from zero to 9 (this covers the values of 00 to 29), or
  • 30: the number 30

So he takes years from 2000 to 2030.

But as I said, still this does not validate the combination day/month/year: it does not check if the month can have day 31, or if it is leap year (in case the month is February, need to know if it can have 29 days), etc. And to do all this, you would need a regex far more complicated:

^(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[1,3-9]|1[0-2])(\/|-|\.)(?:29|30)\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.)(?:0?[1-9]|1\d|2[0-8])\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$

Do you really want to use this? I recommend using the date API and forget regex (about using regex to manipulate/validate dates, read more here, here, here and here).

Regex may even be cool (I particularly like it a lot), but is not always the best solution.

  • 1

    Thank you for your reply!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.