validar - sumar dias a una fecha java
Cómo sanear comprobar una fecha en Java (17)
Me parece curioso que la forma más obvia de crear objetos de Date
en Java haya quedado en desuso y parece haber sido "sustituida" por un calendario no tan obvio de usar.
¿Cómo verifica 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.
java.time
Con la API de fecha y hora (clases java.time ) incorporada en Java 8 y LocalDate
posteriores, puede usar la clase LocalDate
.
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;
}
tl; dr
Utilice el modo estricto en java.time.DateTimeFormatter
para analizar una fecha LocalDate
. Trampa para la 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 comprobar el valor razonable. Por ejemplo, una fecha de nacimiento dentro de los últimos cien años.
birthDate.isAfter( LocalDate.now().minusYears( 100 ) )
Evitar las clases de fecha y hora heredadas
Evite el uso de las antiguas y problemáticas clases de fecha y hora que se incluyen con las primeras versiones de Java. Ahora suplantado por las clases java.time .
LocalDate
& DateTimeFormatter
& ResolverStyle
La clase LocalDate
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 clase java.time.DateTimeFormatter
se puede configurar para analizar cadenas con cualquiera de los tres modos de clemencia definidos en la enumeración ResolverStyle
. 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
ld: 2000-03-02 -
ResolverStyle.SMART
ld: 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 el modo ResolverStyle.LENIENT
, la fecha no válida se adelanta un número equivalente de días. En el modo ResolverStyle.SMART
(el valor predeterminado), se toma una decisión lógica para mantener la fecha dentro del mes y continuar con el último día posible del mes, el 29 de febrero en un año bisiesto, ya que no hay 31 días en ese mes. El modo ResolverStyle.STRICT
lanza una excepción quejándose de que no existe tal fecha.
Los tres de estos son razonables dependiendo de su problema y políticas comerciales. Parece que, en su caso, desea que el modo estricto rechace la fecha no válida en lugar de ajustarlo.
Acerca de java.time
El marco java.time está integrado en Java 8 y java.time posteriores. Estas clases sustituyen a las antiguas y problemáticas clases de fecha y hora como java.util.Date
, java.util.Calendar , y SimpleDateFormat
.
El proyecto Joda-Time , ahora en modo de mantenimiento , aconseja la migración a las clases java.time .
Para obtener más información, consulte el Tutorial de Oracle . Y busca 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 se necesitan cadenas, no se necesitan clases java.sql.*
.
¿Dónde obtener las clases java.time?
- Java SE 8 , Java SE 9 y posteriores
- Incorporado.
- Parte de la API de Java estándar con una implementación en paquete.
- Java 9 agrega algunas características menores y correcciones.
- Java SE 6 y Java SE 7
- Gran parte de la funcionalidad de java.time está respaldada a Java 6 y 7 en ThreeTen-Backport .
- Android
- Versiones posteriores de las implementaciones de paquetes de Android de las clases java.time.
- Para Android anterior (<26), el proyecto ThreeTenABP adapta ThreeTen-Backport (mencionado anteriormente). Vea Cómo usar ThreeTenABP… .
El proyecto ThreeTen-Extra extiende java.time con clases adicionales. Este proyecto es un terreno de prueba para posibles adiciones futuras a java.time. Puede encontrar algunas clases útiles aquí como Interval
, YearWeek
, YearQuarter
, y more .
A partir de la respuesta de @Pangea para solucionar el problema señalado por @ceklock , agregué un método para verificar que el dateString
no contiene 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+");
}
Aquí está yo comprobarí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");
}
Como lo muestra @Maglob, el enfoque básico es probar la conversión de la cadena hasta la fecha utilizando SimpleDateFormat.parse . Eso atrapará combinaciones de día / mes inválidas como 2008-02-31.
Sin embargo, en la práctica esto rara vez es suficiente ya que SimpleDateFormat.parse es extremadamente liberal. Hay dos comportamientos que podrían interesarte:
Los caracteres no válidos en la cadena de fecha Sorprendentemente, 2008-02-2x se "pasarán" como una fecha válida con el formato de configuración regional = "aaaa-MM-dd", por ejemplo. Incluso cuando isLenient == false.
Años: 2, 3 o 4 dígitos? También es posible que desee imponer 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 fue "yyyy-MM-dd" o "yy-MM-dd" )
Una solución estricta con la biblioteca estándar
Por lo tanto, 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 hacer que sea amigable con el entorno local.
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 días, meses, años y caracteres separadores. Aparte de eso, el formato puede estar en cualquier formato de configuración regional: "d / MM / aa", "aaaa-MM-dd", etc. La cadena de formato para la configuración regional actual se podría obtener de esta manera:
Locale locale = Locale.getDefault();
SimpleDateFormat sdf = (SimpleDateFormat)DateFormat.getDateInstance(DateFormat.SHORT, locale );
String format = sdf.toPattern();
Joda Time - ¿Mejor alternativa?
He estado escuchando acerca del tiempo de joda recientemente y pensé que compararía. Dos puntos:
- Parece mejor ser estricto con los caracteres no válidos en la cadena de fecha, a diferencia de SimpleDateFormat
- Aún no puedo ver una forma de imponerle años de 4 dígitos (pero creo que podría crear su propio DateTimeFormatter para este propósito)
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;
}
Creo que lo más simple es simplemente 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;
}
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 una instancia para cada análisis de una fecha.
Esto es lo que hice para el entorno Node sin 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);
}
}
Y aquí está cómo usarlo:
isDateCorrect(''2014-02-23'')
true
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;
}
}
La clave es df.setLenient (false); . Esto es más que suficiente para casos simples. Si está buscando bibliotecas más robustas (lo dudo) y / o alternativas como joda-time, busque la respuesta del usuario "tarde"
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;
}
}
La forma actual es utilizar la clase de calendario. Tiene el método setLenient 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 con su fecha, esta es la forma en que 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");
}
Le sugiero que use la clase org.apache.commons.validator.GenericValidator
de apache.
GenericValidator.isDate(String value, String datePattern, boolean strict);
Nota: estricto: si se tiene o no una coincidencia exacta del datePattern.
Los métodos anteriores de análisis de fecha son agradables, acabo de agregar una nueva verificación en los métodos existentes que verifican la fecha convertida con la fecha original usando el formater, por lo que funciona para casi todos los casos como lo verifiqué. Por ejemplo, 29/02/2013 no es válida. La función dada analiza la fecha según los formatos de fecha aceptables actuales. Devuelve true 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;
}
Puedes usar SimpleDateFormat.parse
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;
}
Suponiendo que ambas son cadenas (de lo contrario, ya serían fechas válidas), aquí hay una 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);
}
}
Aquí está la salida que recibo:
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 bien ambos casos.
Una solución estricta alternativa que utiliza la biblioteca estándar es realizar lo siguiente:
1) Cree un SimpleDateFormat estricto usando su patrón
2) Intente analizar el valor ingresado por el usuario utilizando el objeto de formato
3) Si tiene éxito, vuelva a formatear la Fecha resultante de (2) utilizando el mismo formato de fecha (de (1))
4) Compare la fecha reformateada con el valor original ingresado por el usuario. Si son iguales, el valor introducido 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.
// 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;
}