SimpleDateFormat.parse () ignora el número de caracteres en el patrón

Estoy tratando de analizar una cadena de fecha que puede tener diferentes formatos de árbol. Aunque la cadena no debe coincidir con el segundo patrón, de alguna manera lo hace y, por lo tanto, devuelve una fecha incorrecta.

Ese es mi código:

import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class Start { public static void main(String[] args) { SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); try{ System.out.println(sdf.format(parseDate("2013-01-31"))); } catch(ParseException ex){ System.out.println("Unable to parse"); } } public static Date parseDate(String dateString) throws ParseException{ SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); SimpleDateFormat sdf2 = new SimpleDateFormat("dd-MM-yyyy"); SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy-MM-dd"); Date parsedDate; try { parsedDate = sdf.parse(dateString); } catch (ParseException ex) { try{ parsedDate = sdf2.parse(dateString); } catch (ParseException ex2){ parsedDate = sdf3.parse(dateString); } } return parsedDate; } } 

Con la entrada 2013-01-31 obtengo la salida 05.07.0036 .

Si bash analizar el 31-01-2013 o el 31.01.2013 obtengo el 31.01.2013 como esperaba.

Reconocí que el progtwig me dará exactamente el mismo resultado si configuro los patrones así:

 SimpleDateFormat sdf = new SimpleDateFormat("dMy"); SimpleDateFormat sdf2 = new SimpleDateFormat("dMy"); SimpleDateFormat sdf3 = new SimpleDateFormat("yMd"); 

¿Por qué ignora el número de caracteres en mi patrón?

Hay varios problemas serios con SimpleDateFormat. La configuración indulgente por defecto puede producir respuestas basura, y no puedo pensar en un caso donde indulgente tenga algún beneficio. Esta nunca debería haber sido la configuración predeterminada. Pero deshabilitar indulgente es solo parte de la solución. Todavía puede terminar con resultados de basura que son difíciles de detectar en las pruebas. Vea los comentarios en el código a continuación para ver ejemplos.

Aquí hay una extensión de SimpleDateFormat que obliga a emparejar patrones estrictos. Este debería haber sido el comportamiento predeterminado para esa clase.

 import java.text.DateFormatSymbols; import java.text.ParseException; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; /** * Extension of SimpleDateFormat that implements strict matching. * parse(text) will only return a Date if text exactly matches the * pattern. * * This is needed because SimpleDateFormat does not enforce strict * matching. First there is the lenient setting, which is true * by default. This allows text that does not match the pattern and * garbage to be interpreted as valid date/time information. For example, * parsing "2010-09-01" using the format "yyyyMMdd" yields the date * 2009/12/09! Is this bizarre interpretation the ninth day of the * zeroth month of 2010? If you are dealing with inputs that are not * strictly formatted, you WILL get bad results. You can override lenient * with setLenient(false), but this strangeness should not be the default. * * Second, setLenient(false) still does not strictly interpret the pattern. * For example "2010/01/5" will match "yyyy/MM/dd". And data disagreement like * "1999/2011" for the pattern "yyyy/yyyy" is tolerated (yielding 2011). * * Third, setLenient(false) still allows garbage after the pattern match. * For example: "20100901" and "20100901andGarbage" will both match "yyyyMMdd". * * This class restricts this undesirable behavior, and makes parse() and * format() functional inverses, which is what you would expect. Thus * text.equals(format(parse(text))) when parse returns a non-null result. * * @author zobell * */ public class StrictSimpleDateFormat extends SimpleDateFormat { protected boolean strict = true; public StrictSimpleDateFormat() { super(); setStrict(true); } public StrictSimpleDateFormat(String pattern) { super(pattern); setStrict(true); } public StrictSimpleDateFormat(String pattern, DateFormatSymbols formatSymbols) { super(pattern, formatSymbols); setStrict(true); } public StrictSimpleDateFormat(String pattern, Locale locale) { super(pattern, locale); setStrict(true); } /** * Set the strict setting. If strict == true (the default) * then parsing requires an exact match to the pattern. Setting * strict = false will tolerate text after the pattern match. * @param strict */ public void setStrict(boolean strict) { this.strict = strict; // strict with lenient does not make sense. Really lenient does // not make sense in any case. if (strict) setLenient(false); } public boolean getStrict() { return strict; } /** * Parse text to a Date. Exact match of the pattern is required. * Parse and format are now inverse functions, so this is * required to be true for valid text date information: * text.equals(format(parse(text)) * @param text * @param pos * @return */ @Override public Date parse(String text, ParsePosition pos) { int posIndex = pos.getIndex(); Date d = super.parse(text, pos); if (strict && d != null) { String format = this.format(d); if (posIndex + format.length() != text.length() || !text.endsWith(format)) { d = null; // Not exact match } } return d; } } 

Está documentado en el javadoc SimpleDateFormat :

Para formatear, el número de letras de patrón es el número mínimo de dígitos, y los números más cortos se rellenan con cero a esta cantidad. Para el análisis sintáctico, se ignora el número de letras de patrón a menos que sea necesario para separar dos campos adyacentes.

Una solución podría ser probar el formato aaaa-MM-dd con una expresión regular:

 public static Date parseDate(String dateString) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); SimpleDateFormat sdf2 = new SimpleDateFormat("dd-MM-yyyy"); SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy-MM-dd"); Date parsedDate; try { if (dateString.matches("\\d{4}-\\d{2}-\\d{2}")) { parsedDate = sdf3.parse(dateString); } else { throw new ParseException("", 0); } } catch (ParseException ex) { try { parsedDate = sdf2.parse(dateString); } catch (ParseException ex2) { parsedDate = sdf.parse(dateString); } } return parsedDate; } 

Gracias @Teetoo. Eso me ayudó a encontrar la solución a mi problema:

Si quiero que la función de análisis coincida exactamente con el patrón, tengo que establecer “indulgente” ( SimpleDateFormat.setLenient ) de mi SimpleDateFormat en false :

 SimpleDateFormat sdf = new SimpleDateFormat("dMy"); sdf.setLenient(false); SimpleDateFormat sdf2 = new SimpleDateFormat("dMy"); sdf2.setLenient(false); SimpleDateFormat sdf3 = new SimpleDateFormat("yMd"); sdf3.setLenient(false); 

Esto todavía analizará la fecha si solo uso una letra de patrón para cada segmento, pero reconocerá que 2013 no puede ser el día y, por lo tanto, no coincide con el segundo patrón. En combinación con un control de longitud, recibo exactamente lo que quiero.