Cómo analizar fechas en múltiples formatos usando SimpleDateFormat

Resuelto Derek asked hace 14 años • 13 respuestas

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.

Derek avatar Oct 26 '10 21:10 Derek
Aceptado

Necesitarás usar un SimpleDateFormatobjeto 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 cubre 9/09, 9/2009, y 09/2009)
  • "M/d/y"(que cubre 9/1/2009)
  • "M-d-y"(que cubre 9-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;
}
Matt Ball avatar Oct 26 '2010 14:10 Matt Ball

¿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;
xdjkx avatar Jul 17 '2013 22:07 xdjkx

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/dy d/M/y. Por ejemplo, un formateador inicializado y/M/daceptará una fecha similar 01/01/2009y 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:

  1. Viola una de las pautas de Josh Bloch, específicamente "no usar excepciones para manejar el flujo del programa".
  2. 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;
    }


}
ChrisR avatar Dec 11 '2012 21:12 ChrisR

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?

Aaron G. avatar Sep 28 '2016 18:09 Aaron G.