Pregunta

Encuentro curioso que la forma más obvia de crear Date objetos en Java ha quedado en desuso y parece haber sido " sustituido " con un calendario no tan obvio para usar indulgente.

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

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

¿Fue útil?

Solución

La forma actual es usar la clase de calendario. Tiene el setLenient método que validará la fecha y el lanzamiento y la excepción si está fuera de rango como en su ejemplo.

Olvidé agregar: Si obtiene una instancia de calendario y establece la hora usando su fecha, así es como obtiene la validación.

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

Otros consejos

La clave es df.setLenient (false); . Esto es más que suficiente para casos simples. Si está buscando una biblioteca más robusta (dudo) y / o alternativa, como joda-time, consulte la respuesta del usuario < !> quot; 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 se muestra en @Maglob, el enfoque básico es probar la conversión de cadena a fecha usando SimpleDateFormat.parse . Eso detectará combinaciones inválidas de día / mes 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 le pueden interesar:

Caracteres inválidos en la cadena de fecha Sorprendentemente, 2008-02-2x & Quot; pasará & Quot; 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 SimpleDateFormat (que interpretará & Quot; 12-02-31 & Quot; de manera diferente dependiendo de si su formato era & Quot; aaaa- MM-dd & Quot; o & Quot; aa-MM-dd & Quot;)

Una solución estricta con la biblioteca estándar

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

  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 asume que la cadena de formato contiene solo caracteres de día, mes, año y separador. Aparte de eso, el formato puede estar en cualquier formato de configuración regional: & "; D / MM / aa &"; & "; Aaaa-MM-dd &"; Y así sucesivamente. La cadena de formato para el entorno local actual podría obtenerse así:

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

Joda Time - ¿Mejor alternativa?

He estado escuchando 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. No puedo ver una forma de aplicar años de 4 dígitos todavía (pero supongo que podrías crear tu propio DateTimeFormatter para este fin)

Es bastante simple 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;
}

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

tl; dr

Utilice el modo estricto en java.time.DateTimeFormatter a analizar un LocalDate . Captura 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 de analizar, puede verificar el valor razonable. Por ejemplo, una fecha de nacimiento en los últimos cien años.

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

Evite las clases de fecha y hora heredadas

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

DateTimeFormatter & amplificador; ResolverStyle & amplificador; ResolverStyle.LENIENT

La ResolverStyle.SMART representa la clase 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 );
}

El ResolverStyle.STRICT La clase se puede configurar para analizar cadenas con cualquiera de los tres modos de clemencia definidos en java.util.Date enumeración. Insertamos una línea en el código anterior para probar cada uno de los modos.

f = f.withResolverStyle ( ResolverStyle.LENIENT );

Los resultados:

  • Calendar
    ld: 2000-03-02
  • SimpleDateFormat
    ld: 2000-02-29
  • java.sql.*
    ERROR: java.time.format.DateTimeParseException: No se pudo analizar el texto '31 / 02/2000 ': Fecha no válida' 31 DE FEBRERO '

Podemos ver eso en < => , la fecha no válida se adelanta un número equivalente de días. En Interval modo (el valor predeterminado), se toma una decisión lógica para mantener la fecha dentro del mes e ir con el último día posible del mes, 29 de febrero en un año bisiesto, ya que no hay 31 días en ese mes. El modo YearWeek lanza 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 empresa. Parece que en su caso desea que el modo estricto rechace la fecha no válida en lugar de ajustarla.


Acerca de java.time

El java.time framework está integrado en Java 8 y versiones posteriores. Estas clases suplantan las viejas y problemáticas legacy clases de fecha y hora como YearQuarter , <=> , & amp; <=> .

El Joda-Time , ahora en modo de mantenimiento , aconseja la migración al java.time classes.

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

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 <=> clases.

¿Dónde obtener las clases java.time?

El proyecto ThreeTen-Extra extiende 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 <=> , <=> , < a href = "http://www.threeten.org/threeten-extra/apidocs/org/threeten/extra/YearQuarter.html" rel = "noreferrer"> <=> y más .

java.time

Con la API de fecha y hora ( java.time classes) integrado en Java 8 y versiones posteriores, puede usar la LocalDate clase.

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

Una solución alternativa estricta que usa la biblioteca estándar es realizar lo siguiente:

1) Cree un SimpleDateFormat estricto utilizando su patrón

2) Intente analizar el valor introducido por el usuario utilizando el objeto de formato

3) Si tiene éxito, vuelva a formatear la Fecha resultante de (2) usando el mismo formato de fecha (de (1))

4) Compare la fecha reformateada con el valor original introducido por el usuario. Si son iguales, entonces el valor ingresado coincide estrictamente con su patrón.

De esta manera, no necesita crear expresiones regulares complejas; en mi caso, necesitaba admitir toda la sintaxis de patrones de SimpleDateFormat, en lugar de limitarme a ciertos tipos, como días, meses y años.

Basándose en la respuesta de @Pangea para solucionar el problema señalado por @ ceklock , agregué un método para verificar que dateString no contenga ningún carácter no válido.

Así es como lo hago:

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

Te sugiero que uses org.apache.commons.validator.GenericValidator clase de apache.

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

Nota: estricto: si se debe tener o no una coincidencia exacta de datePattern.

Creo que lo más simple es convertir una cadena en un objeto de fecha y volver a convertirla en una cadena. La cadena de fecha dada está bien si ambas cadenas aún coinciden.

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

Suponiendo que ambas son cadenas (de lo contrario, ya serían fechas válidas), aquí hay una manera:

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

Aquí está el resultado que obtengo:

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 puede ver, maneja sus dos casos muy bien.

Esto está funcionando muy bien para mí. Enfoque sugerido anteriormente 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;
    }
}

Dos comentarios sobre el uso de SimpleDateFormat.

  

debe declararse como una instancia estática   si se declara como acceso estático, debe sincronizarse ya que no es seguro para subprocesos

IME que es mejor que crear instancias de una instancia para cada análisis de una fecha.

Los métodos anteriores de análisis de fechas son agradables, acabo de agregar una nueva verificación en los métodos existentes que verifican dos veces la fecha convertida con la fecha original usando el formateador, por lo que funciona para casi cada caso como lo verifiqué. p.ej. 29/02/2013 es una fecha no válida. La función dada analiza la fecha de acuerdo con los formatos de fecha aceptables actuales. Devuelve verdadero si la fecha no se analiza correctamente.

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

Esto es lo que hice para el entorno Node sin 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); 
   }
}

Y aquí está cómo usarlo:

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

Aquí verificaría el formato de fecha:

 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 bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top