Todo lo que llegue de un usuario se debe validar. Siempre. Es una regla universal. Y cuando un usuario rellena un formulario, sea en la web o en una aplicación de escritorio o en cualquier otro lado, lo que reciba la aplicación debe validarse antes de hacer nada con ello.
Un tipo de datos muy común que debemos validar son las fechas. Generalmente las recibiremos como una cadena de texto extraída desde un control, pero luego la utilizaremos o la almacenaremos como un tipo específico para fechas de Java.
Así pues, ¿cómo podemos recibir una cadena de texto que nos dicen que es una fecha y asegurarnos de que es una expresión de fecha válida para Java, usando solamente Java puro, no bibliotecas de terceros?
Existen al menos un par de métodos para solucionarlo, uno de ellos muy antiguo pero que funciona en todas las versiones de Java, incluso en las más viejas, y otro más moderno. Vamos a darle un repaso.
Método 1: con la clase DateFormat
Este método viene de los inicios de Java, antes de Java 8 y, aunque la verás explicada por ahí en muchos sitios, es algo ya un poco antediluviano. La incluimos aquí para que la conozcas o por si te la encuentras, pero hay métodos mejores y más sucintos.
Consiste en utilizar alguna clase derivada de la clase abstracta del paquete java.text
llamada DateFormat. En concreto la clase SimpleDateFormat
. Esta clase se utiliza para formatear las fechas y mostrarlas en el formato que nos interese pero también dispone de un método parse()
para parsear la cadena (y obtenerla como tipo Date
), y por lo tanto nos vale para comprobar que es una fecha válida.
El código sería similar a este:
import java.text.Format;
public static boolean isValidDate(String d, String dateFormat) {
DateFormat df = new SimpleDateFormat(dateFormat);
df.setLenient(false);
try {
df.parse(d);
} catch (ParseException e) {
return false;
}
return true;
}
String fecha = "23/05/1972";
System.out.println(isValidDate(fecha, "dd/MM/yyyy")); //true
Lo único que hace es instanciar un objeto de esa clase e indicándole el formato que queremos procesar. Luego llama al método parse()
para que interprete la cadena que nos interesa. Se capturan los errores de parseo para así controlar si ha habido algún problema y por lo tanto indicar que la fecha no es válida. La podríamos utilizar también para devolver un objeto Date
listo para utilizar si quisiésemos.
Fíjate en el método setLenient()
, que se utiliza para determinar si el formato se debe interpretar con exactitud (false
) o se permite que el método sea algo más flexible y trate de identificar la fecha aunque la cadena no coincida exactamente con el patrón (true
, que es el valor por defecto). Por regla general nos interesará que no sea demasiado laxo para que no cuelen demasiadas cadenas como válidas y se ajuste más a lo que buscamos, aunque dependerá de la aplicación y de nuestras necesidades.
En este caso, incluso con esta propiedad a false
, es capaz de interpretar bien las fechas aunque no lleven los dos dígitos del día o del mes (ej: "23/5/1972"
), o si el año lo especificamos solo con dos dígitos (ej: "23/5/72"
). En ambos casos indicará que es una fecha v válida.
Método 2: con java.time
Como ya te expliqué en su día en este artículo: "Cómo manejar correctamente fechas en Java: el paquete java.time", las clases tradicionales para manejo de fechas en Java están mal diseñadas, abusan del uso de enumeraciones y constantes para indicar cosas y además no son seguras para uso multihilo. Por eso, en aplicaciones modernas de Java deberíamos utilizar el paquete java.time
, mucho más apropiado.
En aquel artículo te contaba todos los detalles de cómo sacarle partido y, entre todo ello, cómo podemos parsear fechas. Así que vamos a utilizarlo para lo mismo que hemos hecho antes, en combinación de otra de las clases de ese paquete: DateTimeFormatter.
Esta clase está disponible a partir de Java 8 y te da más flexibilidad que la anterior al permitir formatos más complejos y también especificar códigos locales de países. También es segura para multihilo, como hemos comentado.
Su uso es bastante sencillo también. Se instancia utilizando su método estático ofPattern()
(hay varios similares en la clase, mira la documentación) para indicar el patrón de fecha a utilizar para la validación. Por ejemplo:
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import java.util.Locale;
public static boolean isValidDate(String d, String dateFormat) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(dateFormat);
try {
dtf.parse(d);
} catch(DateTimeParseException ex) {
return false;
}
return true;
}
String fecha = "23/05/1972";
System.out.println(isValidDate(fecha, "dd/MM/yyyy")); //true
En este caso le podemos indicar si debe ser flexible o no a la hora de interpretar el formato utilizando para ello el método withResolverStyle()
como se ve en este fragmento. A este se le pueden pasar 3 posibles valores: STRICT
, LENIENT
o SMART
, que será estricto en cuanto al formato pero será capaz de adaptarse a valores fuera de rango (por ejemplo, más días de los que tiene el mes):
DateTimeFormatter.ofPattern(dateFormat).withResolverStyle(ResolverStyle.STRICT);
Hay que tener en cuenta que esta clase es mucho más estricta en cualquier caso que la anterior. Por ejemplo, con la misma cadena de formato que antes ("dd/MM/yyyy"
) si le pasamos un día del mes o un mes sin el 0 correspondiente (por ejemplo: "23/5/1972"
, en ninguno de los modos de parseo será capaz de dar la fecha por válida. El "truco" en este caso es fijarse en que si utilizamos un solo carácter para el día o el mes será capaz de interpretar ambas versiones: "d/M/yyyy"
.
Esta clase dispone de varios métodos para obtener formatos estándar como los de ISO (ej: BASIC_ISO_DATE
o ISO_LOCAL_DATE
, échale un vistazo a la documentación del enlace), o para especificar mayor detalle de la cadena de formato.
Hay una forma adicional de hacer algo como esto mediante la clase LocalDate
de java.time
, pero está muy relacionado con el uso de un formateador, por lo que realmente no tiene mucho sentido usarlo ya que tenemos que instanciar igualmente un DateTimeFormatter
de la misma manera. El código sería idéntico al anterior pero usando el método parse()
de esta clase:
public static boolean isValidDate(String d, String dateFormat) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(dateFormat);
try {
LocalDate.parse(d, dtf);
} catch(DateTimeParseException ex) {
return false;
}
return true;
}
O sea, hemos cambiado dtf.parse(d)
por LocalDate.parse(d, dtf)
, lo cual es involucrar una clase más para hacer lo mismo. No merece la pena, pero por si te lo encuentras por ahí.
Y básicamente estas son las dos maneras que tenemos de comprobar si una fecha es válida usando tan solo lo que nos ofrece Java "de serie". Utiliza el segundo método siempre que puedas.
¡Espero que te resulte útil!