Pergunta

Acho curioso que a maneira mais óbvia de criar Date objetos em Java tornou-se obsoleta e parece ter sido "substituído" com um não tão óbvio de usar o calendário branda.

Como você verificar que uma data, dada como uma combinação de dia, mês e ano, é uma data válida?

Por exemplo, 2008/02/31 (como em aaaa-mm-dd) seria uma data inválida.

Foi útil?

Solução

A forma atual é usar a classe calendário. Ele tem a setLenient método que irá validar a data e lance e exceção se ele está fora do intervalo como no seu exemplo.

Esqueceu-se de acrescentar: Se você receber uma instância de calendário e definir o tempo usando sua data, é assim que você começa a validação.

Calendar cal = Calendar.getInstance();
cal.setLenient(false);
cal.setTime(yourDate);
try {
    cal.getTime();
}
catch (Exception e) {
  System.out.println("Invalid date");
}

Outras dicas

Key é df.setLenient (false); . Isso é mais do que suficiente para casos simples. Se você estiver procurando por um mais robusto (duvido) e / ou bibliotecas alternativas como joda-time, em seguida, olhar para a resposta pelo usuário " tardate "

final static String DATE_FORMAT = "dd-MM-yyyy";

public static boolean isDateValid(String date) 
{
        try {
            DateFormat df = new SimpleDateFormat(DATE_FORMAT);
            df.setLenient(false);
            df.parse(date);
            return true;
        } catch (ParseException e) {
            return false;
        }
}

Como demonstrado por @Maglob, a abordagem básica é testar a conversão de string para data usando SimpleDateFormat.parse . Que vai pegar combinações inválidas dia / mês como 2008/02/31.

No entanto, na prática, que raramente é suficiente desde SimpleDateFormat.parse é extremamente liberal. Há dois comportamentos que você pode estar preocupado com:

caracteres inválidos na seqüência de data Surpreendentemente, 2008-02-2x vai "passar" como uma data válida com formato locale = "AAAA-MM-DD", por exemplo. Mesmo quando isLenient == false.

Anos: 2, 3 ou 4 dígitos Você também pode querer impor anos de 4 dígitos em vez de permitir o comportamento SimpleDateFormat padrão (que irá interpretar "12-02-31" de forma diferente dependendo se o formato era "AAAA-MM-DD" ou "AA-MM-DD" )

Uma solução Strict com a Biblioteca Padrão

Assim, uma cadeia completa de teste de data poderia ser assim: uma combinação de match regex, e em seguida, uma conversão de data forçado. O truque com a regex é torná-lo localidade de usar.

  Date parseDate(String maybeDate, String format, boolean lenient) {
    Date date = null;

    // test date string matches format structure using regex
    // - weed out illegal characters and enforce 4-digit year
    // - create the regex based on the local format string
    String reFormat = Pattern.compile("d+|M+").matcher(Matcher.quoteReplacement(format)).replaceAll("\\\\d{1,2}");
    reFormat = Pattern.compile("y+").matcher(reFormat).replaceAll("\\\\d{4}");
    if ( Pattern.compile(reFormat).matcher(maybeDate).matches() ) {

      // date string matches format structure, 
      // - now test it can be converted to a valid date
      SimpleDateFormat sdf = (SimpleDateFormat)DateFormat.getDateInstance();
      sdf.applyPattern(format);
      sdf.setLenient(lenient);
      try { date = sdf.parse(maybeDate); } catch (ParseException e) { }
    } 
    return date;
  } 

  // used like this:
  Date date = parseDate( "21/5/2009", "d/M/yyyy", false);

Note que a regex assume a cadeia de formato contém apenas caracteres dia, mês, ano, e separadores. Além de que, o formato pode ser em qualquer formato de localidade: "d / MM / aa", "aaaa-MM-dd", e assim por diante. A cadeia de formato para a localidade atual poderia ser obtido assim:

Locale locale = Locale.getDefault();
SimpleDateFormat sdf = (SimpleDateFormat)DateFormat.getDateInstance(DateFormat.SHORT, locale );
String format = sdf.toPattern();

Tempo Joda -? Melhor alternativa

Eu tenho ouvido sobre joda tempo recentemente e pensei que eu iria comparar. Dois pontos:

  1. parece melhor em ser rigoroso sobre caracteres inválidos na seqüência de data, ao contrário SimpleDateFormat
  2. Não consigo ver uma maneira de impor anos de 4 dígitos com ele ainda (mas eu acho que você poderia criar seu próprio DateTimeFormatter para este fim)

É muito simples de usar:

import org.joda.time.format.*;
import org.joda.time.DateTime;

org.joda.time.DateTime parseDate(String maybeDate, String format) {
  org.joda.time.DateTime date = null;
  try {
    DateTimeFormatter fmt = DateTimeFormat.forPattern(format);
    date =  fmt.parseDateTime(maybeDate);
  } catch (Exception e) { }
  return date;
}

Você pode usar SimpleDateFormat

Por exemplo algo como:

boolean isLegalDate(String s) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    sdf.setLenient(false);
    return sdf.parse(s, new ParsePosition(0)) != null;
}

tl; dr

modo estrito Use a em java.time.DateTimeFormatter para analisar uma LocalDate . Armadilha para o DateTimeParseException .

LocalDate.parse(                   // Represent a date-only value, without time-of-day and without time zone.
    "31/02/2000" ,                 // Input string.
    DateTimeFormatter              // Define a formatting pattern to match your input string.
    .ofPattern ( "dd/MM/uuuu" )
    .withResolverStyle ( ResolverStyle.STRICT )  // Specify leniency in tolerating questionable inputs.
)

Após a análise, você pode verificar o valor razoável. Por exemplo, uma data de nascimento dentro últimos cem anos.

birthDate.isAfter( LocalDate.now().minusYears( 100 ) )

Evite legados aulas de data e hora

Evite usar as problemáticas aulas de data e hora de idade enviados com as primeiras versões do Java. Agora suplantado pelo java.time classes.

LocalDate & DateTimeFormatter & ResolverStyle

O LocalDate classe representa uma data valor -apenas sem hora do dia e sem fuso horário.

String input = "31/02/2000";
DateTimeFormatter f = DateTimeFormatter.ofPattern ( "dd/MM/uuuu" );
try {
    LocalDate ld = LocalDate.parse ( input , f );
    System.out.println ( "ld: " + ld );
} catch ( DateTimeParseException e ) {
    System.out.println ( "ERROR: " + e );
}

O java.time.DateTimeFormatter classe pode ser configurado para cordas de análise com qualquer um dos três modos de clemência definidos no ResolverStyle enum. Nós inserir uma linha no código acima para tentar cada um dos modos.

f = f.withResolverStyle ( ResolverStyle.LENIENT );

Os resultados:

  • ResolverStyle.LENIENT
    ld: 2000/03/02
  • ResolverStyle.SMART
    ld: 2000-02-29
  • ResolverStyle.STRICT
    ERROR: java.time.format.DateTimeParseException: Text '31 / 02 / 2000' não pôde ser analisado: Data inválida '31 de fevereiro'

Podemos ver que em ResolverStyle.LENIENT modo, a data inválido é movido para a frente de um número equivalente de dias. Em modo ResolverStyle.SMART (o padrão), é feita uma decisão lógica para manter a data dentro do mês e indo com o último possível dia do mês, 29 de fevereiro em um ano bissexto, como não há nenhum dia 31 desse mês. O href="http://docs.oracle.com/javase/8/docs/api/java/time/format/ResolverStyle.html#STRICT" rel="noreferrer"> ResolverStyle.STRICT modo lança uma exceção reclamando que não existe tal data.

Todos os três destes são razoáveis ??dependendo do seu problema e políticas de negócios. Parece que no seu caso você deseja que o modo estrito para rejeitar a data inválida em vez de ajustá-lo.


Sobre java.time

O java.time framework é construído em Java 8 e posterior. Essas classes suplantar os velhos legado aulas de data e hora complicados, como java.util.Date , Calendar , e SimpleDateFormat .

O Joda-Time projeto , agora em modo de manutenção , aconselha migração para o java.time classes.

Para saber mais, consulte a A Oracle Tutorial . E busca Stack Overflow para muitos exemplos e explicações. Especificação é JSR 310 .

Você pode trocar java.time objetos diretamente com seu banco de dados. Use a driver JDBC compatível com JDBC 4.2 ou mais tarde. Não há necessidade de cordas, sem necessidade de aulas java.sql.*.

Onde obter as classes java.time?

O ThreeTen-Extra projeto estende java.time com aulas adicionais. Este projeto é um campo de provas para possíveis adições futuras para java.time. Você pode encontrar algumas classes úteis aqui, como Interval , YearWeek , YearQuarter , e mais .

java.time

Com o Data e API Time ( java.time classes) construídos em Java 8 e posterior, você pode usar LocalDate classe.

public static boolean isDateValid(int year, int month, int day) {
    boolean dateIsValid = true;
    try {
        LocalDate.of(year, month, day);
    } catch (DateTimeException e) {
        dateIsValid = false;
    }
    return dateIsValid;
}

Uma solução alternativa rigorosa utilizando a biblioteca padrão é para executar o seguinte:

1) Criar um SimpleDateFormat estrita usando seu padrão

2) Tentativa de analisar o valor inserido usuário usando o objeto formato

3) Se bem sucedido, reformatar o Data resultante de (2) usando o mesmo formato de data (a partir de (1))

4) Compare a data reformatado com o original, valor inserido pelo usuário. Se eles são iguais, então o valor inserido corresponde estritamente o seu padrão.

Desta forma, você não precisa criar expressões regulares complexas - no meu caso eu precisava para suportar todas sintaxe padrão de SimpleDateFormat, ao invés de ser limitado a certos tipos, como poucos dias, meses e anos

.

Com base na resposta do @Pangea para corrigir o problema apontado por @ ceklock , eu adicionei um método para verificar se o dateString não contém qualquer caractere inválido.

Aqui está como eu faço:

private boolean isDateCorrect(String dateString) {
    try {
        Date date = mDateFormatter.parse(dateString);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return matchesOurDatePattern(dateString);    //added my method
    }
    catch (ParseException e) {
        return false;
    }
}

/**
 * This will check if the provided string matches our date format
 * @param dateString
 * @return true if the passed string matches format 2014-1-15 (YYYY-MM-dd)
 */
private boolean matchesDatePattern(String dateString) {
    return dateString.matches("^\\d+\\-\\d+\\-\\d+");
}

Eu sugiro que você use classe org.apache.commons.validator.GenericValidator de apache.

GenericValidator.isDate(String value, String datePattern, boolean strict);

Nota:. Estrita - Se deve ou não ter uma correspondência exata do datePattern

Eu acho que o mais simples é apenas para converter uma string em um objeto de data e convertê-lo de volta para uma string. A dada seqüência de data é bom se ambas as cordas ainda corresponder.

public boolean isDateValid(String dateString, String pattern)
{   
    try
    {
        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
        if (sdf.format(sdf.parse(dateString)).equals(dateString))
            return true;
    }
    catch (ParseException pe) {}

    return false;
}

Assumindo que ambos esses são Cordas (caso contrário já estaria datas válidas), aqui está uma forma:

package cruft;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateValidator
{
    private static final DateFormat DEFAULT_FORMATTER;

    static
    {
        DEFAULT_FORMATTER = new SimpleDateFormat("dd-MM-yyyy");
        DEFAULT_FORMATTER.setLenient(false);
    }

    public static void main(String[] args)
    {
        for (String dateString : args)
        {
            try
            {
                System.out.println("arg: " + dateString + " date: " + convertDateString(dateString));
            }
            catch (ParseException e)
            {
                System.out.println("could not parse " + dateString);
            }
        }
    }

    public static Date convertDateString(String dateString) throws ParseException
    {
        return DEFAULT_FORMATTER.parse(dateString);
    }
}

Aqui está a saída que eu recebo:

java cruft.DateValidator 32-11-2010 31-02-2010 04-01-2011
could not parse 32-11-2010
could not parse 31-02-2010
arg: 04-01-2011 date: Tue Jan 04 00:00:00 EST 2011

Process finished with exit code 0

Como você pode ver, ele lida com ambos os casos bem.

Isso está funcionando muito bem para mim. Abordagem sugerida acima por Ben.

private static boolean isDateValid(String s) {
    SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
    try {
        Date d = asDate(s);
        if (sdf.format(d).equals(s)) {
            return true;
        } else {
            return false;
        }
    } catch (ParseException e) {
        return false;
    }
}

Dois comentários sobre o uso de SimpleDateFormat.

deve ser declarada como uma instância estática se declarou como o acesso estático deve ser sincronizado porque não é thread-safe

IME que é melhor do que instanciar uma instância para cada análise de uma data.

métodos acima da data de análise são agradáveis, i acabou de adicionar novo check-in métodos existentes que verifique a data convertida com data original usando formater, por isso funciona para quase cada caso, conforme i verificada. por exemplo. 2013/02/29 é data inválida. função dada analisar a data de acordo com os formatos atuais de data aceitáveis. Ele retorna verdadeiro se a data não é analisado com êxito.

 public final boolean validateDateFormat(final String date) {
        String[] formatStrings = {"MM/dd/yyyy"};
        boolean isInvalidFormat = false;
        Date dateObj;
        for (String formatString : formatStrings) {
            try {
                SimpleDateFormat sdf = (SimpleDateFormat) DateFormat.getDateInstance();
                sdf.applyPattern(formatString);
                sdf.setLenient(false);
                dateObj = sdf.parse(date);
                System.out.println(dateObj);
                if (date.equals(sdf.format(dateObj))) {
                    isInvalidFormat = false;
                    break;
                }
            } catch (ParseException e) {
                isInvalidFormat = true;
            }
        }
        return isInvalidFormat;
    }

Aqui está o que eu fiz para o ambiente Nó sem utilizar bibliotecas externas:

Date.prototype.yyyymmdd = function() {
   var yyyy = this.getFullYear().toString();
   var mm = (this.getMonth()+1).toString(); // getMonth() is zero-based
   var dd  = this.getDate().toString();
   return zeroPad([yyyy, mm, dd].join('-'));  
};

function zeroPad(date_string) {
   var dt = date_string.split('-');
   return dt[0] + '-' + (dt[1][1]?dt[1]:"0"+dt[1][0]) + '-' + (dt[2][1]?dt[2]:"0"+dt[2][0]);
}

function isDateCorrect(in_string) {
   if (!matchesDatePattern) return false;
   in_string = zeroPad(in_string);
   try {
      var idate = new Date(in_string);
      var out_string = idate.yyyymmdd();
      return in_string == out_string;
   } catch(err) {
      return false;
   }

   function matchesDatePattern(date_string) {
      var dateFormat = /[0-9]+-[0-9]+-[0-9]+/;
      return dateFormat.test(date_string); 
   }
}

E aqui está como usá-lo:

isDateCorrect('2014-02-23')
true
// to return valid days of month, according to month and year
int returnDaysofMonth(int month, int year) {
    int daysInMonth;
    boolean leapYear;
    leapYear = checkLeap(year);
    if (month == 4 || month == 6 || month == 9 || month == 11)
        daysInMonth = 30;
    else if (month == 2)
        daysInMonth = (leapYear) ? 29 : 28;
    else
        daysInMonth = 31;
    return daysInMonth;
}

// to check a year is leap or not
private boolean checkLeap(int year) {
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.YEAR, year);
    return cal.getActualMaximum(Calendar.DAY_OF_YEAR) > 365;
}

Aqui é que eu iria verificar o formato da data:

 public static boolean checkFormat(String dateTimeString) {
    return dateTimeString.matches("^\\d{4}-\\d{2}-\\d{2}") || dateTimeString.matches("^\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}")
            || dateTimeString.matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}") || dateTimeString
            .matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z") ||
            dateTimeString.matches("^\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}Z");
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top