Cómo analizar fechas en múltiples formatos usando SimpleDateFormat
Estoy intentando analizar algunas fechas que salen de un documento. Parece que los usuarios ingresaron estas fechas en un formato similar pero no exacto.
Aquí están los formatos:
9/09
9/2009
09/2009
9/1/2009
9-1-2009
¿Cuál es la mejor manera de intentar analizar todos estos? Estos parecen ser los más comunes, pero supongo que lo que me bloquea es que si tengo un patrón de "M/aaaa", ¿no siempre se activará antes de "MM/aaaa"? ¿Tengo que configurar mis bloques try/catch? ¿Anidado de manera menos restrictiva a más restrictiva? Parece que se necesitará mucha duplicación de código para hacerlo bien.
Necesitarás usar un SimpleDateFormat
objeto diferente para cada patrón diferente. Dicho esto, no necesitas tantos diferentes, gracias a esto :
Número: para formatear, el número de letras del patrón es el número mínimo de dígitos y los números más cortos se rellenan con ceros hasta esta cantidad. Para el análisis, se ignora el número de letras del patrón a menos que sea necesario para separar dos campos adyacentes.
Entonces, necesitarás estos formatos:
"M/y"
(que cubre9/09
,9/2009
, y09/2009
)"M/d/y"
(que cubre9/1/2009
)"M-d-y"
(que cubre9-1-2009
)
Entonces, mi consejo sería escribir un método que funcione así ( no probado ):
// ...
List<String> formatStrings = Arrays.asList("M/y", "M/d/y", "M-d-y");
// ...
Date tryParse(String dateString)
{
for (String formatString : formatStrings)
{
try
{
return new SimpleDateFormat(formatString).parse(dateString);
}
catch (ParseException e) {}
}
return null;
}
¿Qué tal simplemente definir múltiples patrones? Pueden provenir de un archivo de configuración que contiene patrones conocidos, codificados de forma rígida y que se lee como:
List<SimpleDateFormat> knownPatterns = new ArrayList<SimpleDateFormat>();
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"));
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm.ss'Z'"));
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"));
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss"));
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"));
for (SimpleDateFormat pattern : knownPatterns) {
try {
// Take a try
return new Date(pattern.parse(candidate).getTime());
} catch (ParseException pe) {
// Loop on
}
}
System.err.println("No known Date format found: " + candidate);
return null;
El enfoque de Matt anterior está bien, pero tenga en cuenta que tendrá problemas si lo usa para diferenciar entre fechas del formato y/M/d
y d/M/y
. Por ejemplo, un formateador inicializado y/M/d
aceptará una fecha similar 01/01/2009
y le devolverá una fecha que claramente no es la que deseaba. Solucioné el problema de la siguiente manera, pero tengo tiempo limitado y no estoy satisfecho con la solución por 2 razones principales:
- Viola una de las pautas de Josh Bloch, específicamente "no usar excepciones para manejar el flujo del programa".
- Puedo ver que el
getDateFormat()
método se convertirá en una pesadilla si lo necesitaras para manejar muchos otros formatos de fecha.
Si tuviera que hacer algo que pudiera manejar muchísimos formatos de fecha diferentes y tuviera que tener un alto rendimiento, entonces creo que usaría el enfoque de crear una enumeración que vincule cada expresión regular de fecha diferente a su formato. Luego utilícelo MyEnum.values()
para recorrer la enumeración y probar con if(myEnum.getPattern().matches(date))
en lugar de detectar una excepción de formato de fecha.
De todos modos, dicho esto, lo siguiente puede manejar fechas de los formatos 'y/M/d' 'y-M-d' 'y M d' 'd/M/y' 'd-M-y' 'd M y'
y todas las demás variaciones de aquellos que también incluyen formatos de hora:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateUtil {
private static final String[] timeFormats = {"HH:mm:ss","HH:mm"};
private static final String[] dateSeparators = {"/","-"," "};
private static final String DMY_FORMAT = "dd{sep}MM{sep}yyyy";
private static final String YMD_FORMAT = "yyyy{sep}MM{sep}dd";
private static final String ymd_template = "\\d{4}{sep}\\d{2}{sep}\\d{2}.*";
private static final String dmy_template = "\\d{2}{sep}\\d{2}{sep}\\d{4}.*";
public static Date stringToDate(String input){
Date date = null;
String dateFormat = getDateFormat(input);
if(dateFormat == null){
throw new IllegalArgumentException("Date is not in an accepted format " + input);
}
for(String sep : dateSeparators){
String actualDateFormat = patternForSeparator(dateFormat, sep);
//try first with the time
for(String time : timeFormats){
date = tryParse(input,actualDateFormat + " " + time);
if(date != null){
return date;
}
}
//didn't work, try without the time formats
date = tryParse(input,actualDateFormat);
if(date != null){
return date;
}
}
return date;
}
private static String getDateFormat(String date){
for(String sep : dateSeparators){
String ymdPattern = patternForSeparator(ymd_template, sep);
String dmyPattern = patternForSeparator(dmy_template, sep);
if(date.matches(ymdPattern)){
return YMD_FORMAT;
}
if(date.matches(dmyPattern)){
return DMY_FORMAT;
}
}
return null;
}
private static String patternForSeparator(String template, String sep){
return template.replace("{sep}", sep);
}
private static Date tryParse(String input, String pattern){
try{
return new SimpleDateFormat(pattern).parse(input);
}
catch (ParseException e) {}
return null;
}
}
Si trabaja en Java 1.8, puede aprovechar DateTimeFormatterBuilder
public static boolean isTimeStampValid(String inputString)
{
DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ofPattern("" + "[yyyy-MM-dd'T'HH:mm:ss.SSSZ]" + "[yyyy-MM-dd]"));
DateTimeFormatter dateTimeFormatter = dateTimeFormatterBuilder.toFormatter();
try {
dateTimeFormatter.parse(inputString);
return true;
} catch (DateTimeParseException e) {
return false;
}
}
Ver publicación: ¿Java 8 Fecha equivalente a DateTimeFormatterBuilder de Joda con múltiples formatos de analizador?