2009-06-07 13 views
12

Tengo esta situación en la que estoy leyendo sobre 130K registros que contienen fechas almacenadas como campos String. Algunos registros contienen espacios en blanco (nulos), algunos contienen cadenas como esta: 'dd-MMM-aa' y algunos contienen este 'dd/MM/aaaa'.Determinar si una cadena es una fecha válida antes de analizar

He escrito un método como este:

public Date parsedate(String date){ 

    if(date !== null){ 
     try{ 
     1. create a SimpleDateFormat object using 'dd-MMM-yy' as the pattern 
     2. parse the date 
     3. return the parsed date 
     }catch(ParseException e){ 
      try{ 
       1. create a SimpleDateFormat object using 'dd/MM/yyy' as the pattern 
       2. parse the date 
       3. return parsed date 
      }catch(ParseException e){ 
       return null 
      } 
     } 
    }else{ 
     return null 
    } 

} 

lo que puede tener ya vimos el problema. Estoy usando try ... catch como parte de mi lógica. Sería mejor si puedo determinar de antemano que el String realmente contiene una fecha parseable en algún formato y luego intentar analizarlo.

Entonces, ¿hay alguna API o biblioteca que pueda ayudar con esto? No me importa escribir varias clases de Parse diferentes para manejar los diferentes formatos y luego crear una fábrica para seleccionar la correcta6, pero, ¿cómo puedo determinar cuál?

Gracias.

+3

Si decide mantener su solución, por favor crean sólo 2 casos de SimpleDateFormat y los almacenan como constantes en su clase en lugar de ellos la creación de 130K veces. – van

+2

Si las almacena como constantes, ¡asegúrese de que no se utilicen desde varios hilos a la vez! Me encontré con problemas con eso antes y contribuí con un detector de FindBugs, que encuentra AgeFormats y Calendarios estáticos. Están documentados como no seguros para subprocesos, pero eso es fácil de perder. Ver http://dschneller.blogspot.com/2007/04/calendar-dateformat-and-multi-threading.html, http://dschneller.blogspot.com/2007/04/findbugs-writing-custom-detectors- part.html y http://dschneller.blogspot.com/2007/05/findbugs-writing-custom-detectors-part.html –

+6

@van: No hagas eso. SimpleDateFormat no es seguro para subprocesos, por lo que si utiliza la clase de más de un subproceso, las cosas explotarán en su cara. – Apocalisp

Respuesta

7

Consulte Lazy Error Handling in Java para obtener una descripción general de cómo eliminar bloques try/catch utilizando un tipo Option.

Functional Java es tu amigo.

En esencia, lo que desea hacer es ajustar el análisis de la fecha en una función que no arroje nada, pero indica en su tipo de devolución si el análisis fue exitoso o no. Por ejemplo:

import fj.F; import fj.F2; 
import fj.data.Option; 
import java.text.SimpleDateFormat; 
import java.text.ParseException; 
import static fj.Function.curry; 
import static fj.Option.some; 
import static fj.Option.none; 
... 

F<String, F<String, Option<Date>>> parseDate = 
    curry(new F2<String, String, Option<Date>>() { 
    public Option<Date> f(String pattern, String s) { 
     try { 
     return some(new SimpleDateFormat(pattern).parse(s)); 
     } 
     catch (ParseException e) { 
     return none(); 
     } 
    } 
    }); 

bien, ahora tienes un analizador fecha reutilizable que no tira nada, sino que indica el fracaso devolviendo un valor de tipo Option.None. Aquí es cómo lo usa:

import fj.data.List; 
import static fj.data.Stream.stream; 
import static fj.data.Option.isSome_; 
.... 
public Option<Date> parseWithPatterns(String s, Stream<String> patterns) { 
    return stream(s).apply(patterns.map(parseDate)).find(isSome_()); 
} 

que le dará la fecha analizada con el primer patrón que coincida, o un valor de tipo Option.None, que es de tipo seguro mientras que no es nula.

Si se está preguntando qué Stream es ... it's a lazy list. Esto garantiza que ignore los patrones después de la primera exitosa. No hay necesidad de hacer demasiado trabajo.

Llame a su función como esta:

for (Date d: parseWithPatterns(someString, stream("dd/MM/yyyy", "dd-MM-yyyy")) { 
    // Do something with the date here. 
} 

O ...

Option<Date> d = parseWithPatterns(someString, 
            stream("dd/MM/yyyy", "dd-MM-yyyy")); 
if (d.isNone()) { 
    // Handle the case where neither pattern matches. 
} 
else { 
    // Do something with d.some() 
} 
+0

Esta es una respuesta muy interesante. Definitivamente tendré que buscar Java funcional. Gracias. –

3

Parece tres opciones si sólo tiene dos, los formatos conocidos:

  • cheque por la presencia de - o / primero y comenzar con el análisis que para ese formato.
  • de verificación de la longitud desde "dd-MMM-yy" y "dd/MM/aaaa" son diferentes
  • uso precompilados expresiones regulares

Este último parece innecesario.

+0

La comprobación de la longitud debería ser más rápida en Java y luego buscar un char. – van

+0

@van: no es necesario realizar una optimización prematura. – Eddie

2

Use expresiones regulares para analizar su cadena. Asegúrese de mantener ambas expresiones regulares precompiladas (no crea nuevas en cada llamada a método, pero guárdelas como constantes), y compare si realmente es más rápido que el try-catch que usa.

Todavía me resulta extraño que su método devuelva null si ambas versiones fallan en lugar de arrojar una excepción.

7

No sea demasiado exigente con el uso de try-catch en la lógica: esta es una de esas situaciones en las que Java lo obliga a hacerlo, por lo que no hay mucho que pueda hacer al respecto.

Pero en este caso podría utilizar DateFormat.parse(String, ParsePosition).

+0

en java8 aunque los documentos (http://docs.oracle.com/javase/8/docs/api/java/text/DateFormat.html#parse-java.lang.String-java.text.ParsePosition-) doesn No digo que arroje nada, el código indica que arroja algunas RuntimeException :( – Meow

3

Si sus formatos son exactos (7 de junio de 1999 sería 07-jun-99 o 07/06/1999: está seguro de tener ceros a la izquierda), entonces podría simplemente verificar la longitud de la cadena antes de intentar analizar.

Tenga cuidado con el nombre del mes corto en la primera versión, porque junio puede no ser junio en otro idioma.

Pero si sus datos provienen de una base de datos, entonces simplemente convertiría todas las fechas al formato común (es una vez, pero luego controla los datos y su formato).

5

Puede aprovechar las expresiones regulares para determinar en qué formato se encuentra la cadena y si coincide con cualquier formato válido.Algo como esto (no probado):

(. Vaya, me escribió esto en C# antes de la comprobación para ver qué idioma estaba utilizando)

Regex test = new Regex(@"^(?:(?<formatA>\d{2}-[a-zA-Z]{3}-\d{2})|(?<formatB>\d{2}/\d{2}/\d{3}))$", RegexOption.Compiled); 
Match match = test.Match(yourString); 
if (match.Success) 
{ 
    if (!string.IsNullOrEmpty(match.Groups["formatA"])) 
    { 
     // Use format A. 
    } 
    else if (!string.IsNullOrEmpty(match.Groups["formatB"])) 
    { 
     // Use format B. 
    } 
    ... 
} 
2

podría utilizar dividida para determinar qué formato utilizar

String[] parts = date.split("-"); 
df = (parts.length==3 ? format1 : format2); 

que asume que todos están en uno u otro formato, se podría mejorar la comprobación de si es necesario

3

en esta situación limitado, la mejor (y más rápido método) es certinally para analizar el día, luego basado en el siguiente carácter ya sea '/' o '-' tratar de analizar el resto. y si en algún momento hay datos inesperados, devuelva NULL entonces.

2

Suponiendo que los patrones que dio son las únicas opciones probables, miraría el String pasado para ver qué formato aplicar.

public Date parseDate(final String date) { 
    if (date == null) { 
    return null; 
    } 

    SimpleDateFormat format = (date.charAt(2) == '/') ? new SimpleDateFormat("dd/MMM/yyyy") 
                : new SimpleDateFormat("dd-MMM-yy"); 
    try { 
    return format.parse(date); 
    } catch (ParseException e) { 
    // Log a complaint and include date in the complaint 
    } 
    return null; 
} 

Como otros han mencionado, si se puede garantía de que va a Nunca el acceso a los DateFormat s de una manera multi-hilo, se puede hacer a nivel de clase o instancias estáticas.

2

Una alternativa para crear un SimpleDateFormat (o dos) por iteración sería rellenar de forma perezosa un contenedor ThreadLocal para estos formatos. Esto resolverá tanto los problemas de seguridad del subproceso como las preocupaciones sobre el rendimiento de creación de objetos.

1

Una clase sencilla utilidad que he escrito para mi proyecto. Espero que esto ayude a alguien.

ejemplos de uso:

DateUtils.multiParse("1-12-12"); 
DateUtils.multiParse("2-24-2012"); 
DateUtils.multiParse("3/5/2012"); 
DateUtils.multiParse("2/16/12"); 




public class DateUtils { 

    private static List<SimpleDateFormat> dateFormats = new ArrayList<SimpleDateFormat>(); 



    private Utils() { 
     dateFormats.add(new SimpleDateFormat("MM/dd/yy")); // must precede yyyy 
     dateFormats.add(new SimpleDateFormat("MM/dd/yyyy")); 
     dateFormats.add(new SimpleDateFormat("MM-dd-yy")); 
     dateFormats.add(new SimpleDateFormat("MM-dd-yyyy"));    

    } 
     private static Date tryToParse(String input, SimpleDateFormat format) { 
     Date date = null; 
     try { 
      date = format.parse(input); 
     } catch (ParseException e) { 

     } 

     return date; 
    } 

     public static Date multiParse(String input) { 
     Date date = null; 
     for (SimpleDateFormat format : dateFormats) { 
      date = tryToParse(input, format); 
      if (date != null) break; 
     } 
     return date; 
    } 
} 
Cuestiones relacionadas