Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

Cómo validar fechas en Java

Imagen ornamental, creada por campusMVP

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!

José M. Alarcón Aguín Fundador de campusMVP, es ingeniero industrial y especialista en consultoría de empresa. Ha escrito diversos libros, habiendo publicado hasta la fecha cientos de artículos sobre informática e ingeniería en publicaciones especializadas. Microsoft lo ha reconocido como MVP (Most Valuable Professional) en desarrollo web desde el año 2004 hasta la actualidad. Puedes seguirlo en Twitter en @jm_alarcon o leer sus blog técnico o personal. Ver todos los posts de José M. Alarcón Aguín
Archivado en: Lenguajes y plataformas

Boletín campusMVP.es

Solo cosas útiles. Una vez al mes.

🚀 Únete a miles de desarrolladores

DATE DE ALTA

x No me interesa | x Ya soy suscriptor

La mejor formación online para desarrolladores como tú

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.