Cómo comprobar la cordura de una fecha en Java

Resuelto Bloodboiler asked hace 16 años • 25 respuestas

Me parece curioso que la forma más obvia de crear Dateobjetos en Java haya quedado obsoleta y parezca haber sido "sustituida" por un calendario indulgente no tan obvio.

¿Cómo se comprueba que una fecha, dada como una combinación de día, mes y año, sea una fecha válida?

Por ejemplo, 2008-02-31 (como en aaaa-mm-dd) sería una fecha no válida.

Bloodboiler avatar Oct 23 '08 01:10 Bloodboiler
Aceptado

La clave es df.setLenient(false);. Esto es más que suficiente para casos sencillos. Si está buscando bibliotecas más sólidas (lo dudo) y/o alternativas como joda-time , mire la respuesta del usuario "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;
        }
}
Aravind Yarram avatar Dec 24 '2010 19:12 Aravind Yarram

Como lo muestra @Maglob, el enfoque básico es probar la conversión de cadena a fecha usando SimpleDateFormat.parse . Eso detectará combinaciones de día/mes no válidas como 2008-02-31.

Sin embargo, en la práctica eso rara vez es suficiente ya que SimpleDateFormat.parse es extremadamente liberal. Hay dos comportamientos que podrían preocuparle:

Caracteres no válidos en la cadena de fecha Sorprendentemente, 2008-02-2x "pasará" como una fecha válida con formato local = "aaaa-MM-dd", por ejemplo. Incluso cuando isLenient==false.

Años: ¿2, 3 o 4 dígitos? También es posible que desee aplicar años de 4 dígitos en lugar de permitir el comportamiento predeterminado de SimpleDateFormat (que interpretará "12-02-31" de manera diferente dependiendo de si su formato era "aaaa-MM-dd" o "aa-MM-dd" )

Una solución estricta con la biblioteca estándar

Entonces, una prueba completa de cadena hasta la fecha podría verse así: una combinación de coincidencia de expresiones regulares y luego una conversión de fecha forzada. El truco con la expresión regular es hacerla compatible con la configuración regional.

  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);

Tenga en cuenta que la expresión regular supone que la cadena de formato contiene solo día, mes, año y caracteres separadores. Aparte de eso, el formato puede estar en cualquier formato local: "d/MM/aa", "aaaa-MM-dd", etc. La cadena de formato para la configuración regional actual se podría obtener así:

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

Joda Time: ¿mejor alternativa?

Escuché sobre joda time recientemente y pensé en comparar. Dos puntos:

  1. Parece mejor ser estricto con los caracteres no válidos en la cadena de fecha, a diferencia de SimpleDateFormat
  2. Todavía no veo una manera de imponer años de 4 dígitos (pero supongo que podrías crear tu propio DateTimeFormatter para este propósito)

Es bastante sencillo de utilizar:

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;
}
tardate avatar May 21 '2009 10:05 tardate

tl; dr

Utilice el modo estricto para java.time.DateTimeFormatteranalizar un archivo LocalDate. Trampa para el 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.
)

Después del análisis, puede comprobar el valor razonable. Por ejemplo, una fecha de nacimiento dentro de los últimos cien años.

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

Evite clases heredadas de fecha y hora

Evite el uso de las antiguas y problemáticas clases de fecha y hora incluidas con las primeras versiones de Java. Ahora reemplazado por las clases java.time .

LocalDate& DateTimeFormatter&ResolverStyle

La LocalDateclase representa un valor de solo fecha sin hora del día y sin zona horaria.

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 );
}

La java.time.DateTimeFormatterclase se puede configurar para analizar cadenas con cualquiera de los tres modos de indulgencia definidos en la ResolverStyleenumeración. Insertamos una línea en el código anterior para probar cada uno de los modos.

f = f.withResolverStyle ( ResolverStyle.LENIENT );

Los resultados:

  • ResolverStyle.LENIENT
    fecha: 2000-03-02
  • ResolverStyle.SMART
    fecha: 2000-02-29
  • ResolverStyle.STRICT
    ERROR: java.time.format.DateTimeParseException: No se pudo analizar el texto '31/02/2000': Fecha no válida '31 DE FEBRERO'

Podemos ver que en ResolverStyle.LENIENTmodo, la fecha no válida se adelanta un número equivalente de días. En ResolverStyle.SMARTel modo (el predeterminado), se toma una decisión lógica para mantener la fecha dentro del mes y seguir con el último día posible del mes, el 29 de febrero en un año bisiesto, ya que no hay el día 31 en ese mes. El ResolverStyle.STRICTmodo arroja una excepción quejándose de que no existe tal fecha.

Los tres son razonables según el problema y las políticas de su negocio. Parece que en su caso desea que el modo estricto rechace la fecha no válida en lugar de ajustarla.


Tabla de todos los tipos de fecha y hora en Java, tanto modernos como heredados.


Acerca de java.time

El marco java.time está integrado en Java 8 y versiones posteriores. Estas clases reemplazan a las antiguas y problemáticas clases de fecha y hora heredadas , como java.util.Date, Calendary SimpleDateFormat.

Para obtener más información, consulte el Tutorial de Oracle . Y busque Stack Overflow para obtener muchos ejemplos y explicaciones. La especificación es JSR 310 .

El proyecto Joda-Time , ahora en modo de mantenimiento , aconseja la migración a las clases java.time .

Puede intercambiar objetos java.time directamente con su base de datos. Utilice un controlador JDBC compatible con JDBC 4.2 o posterior. No hay necesidad de cadenas, no hay necesidad de java.sql.*clases.

¿Dónde obtener las clases java.time?

  • Java SE 8 , Java SE 9 , Java SE 10 , Java SE 11 y posteriores: parte de la API de Java estándar con una implementación incluida.
    • Java 9 agrega algunas características y correcciones menores.
  • Java SE 6 y Java SE 7
    • La mayor parte de la funcionalidad java.time está adaptada a Java 6 y 7 en ThreeTen-Backport .
  • Androide
    • Versiones posteriores de implementaciones de paquetes de Android de las clases java.time .
    • Para Android anterior (<26), el proyecto ThreeTenABP adapta ThreeTen-Backport (mencionado anteriormente). Consulte Cómo utilizar ThreeTenABP… .

Tabla de qué biblioteca java.time usar con qué versión de Java o Android

El proyecto ThreeTen-Extra amplía java.time con clases adicionales. Este proyecto es un campo de pruebas para posibles adiciones futuras a java.time. Puede encontrar algunas clases útiles aquí, como , Interval, y más .YearWeekYearQuarter

Basil Bourque avatar Sep 22 '2016 22:09 Basil Bourque

Puedes usar SimpleDateFormat

Por ejemplo algo como:

boolean isLegalDate(String s) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    sdf.setLenient(false);
    return sdf.parse(s, new ParsePosition(0)) != null;
}
Maglob avatar Oct 22 '2008 19:10 Maglob