SimpleDateFormat.parse () ignora o número de caracteres no padrão

Eu estou tentando analisar uma cadeia de data que pode ter diferentes formatos de tree. Mesmo que o String não corresponda ao segundo padrão de alguma forma e, portanto, retorna uma data errada.

Esse é o meu 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; } } 

Com a input 2013-01-31 recebo a saída 05.07.0036 .

Se eu tentar analisar 31-01-2013 ou 31.01.2013 recebo 31.01.2013 como esperado.

Eu reconheci que o programa me dará exatamente a mesma saída se eu definir os padrões assim:

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

Por que ignora o número de caracteres no meu padrão?

Existem vários problemas sérios com o SimpleDateFormat. A configuração leniente padrão pode produzir respostas ruins, e não consigo pensar em um caso em que leniente tenha qualquer benefício. Isso nunca deveria ter sido a configuração padrão. Mas desabilitar lenient é apenas parte da solução. Você ainda pode acabar com resultados ruins que são difíceis de detectar nos testes. Veja os comentários no código abaixo para exemplos.

Aqui está uma extensão de SimpleDateFormat que força a correspondência estrita de padrões. Este deveria ter sido o comportamento padrão para essa class.

 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 no javadoc SimpleDateFormat :

Para formatação, o número de letras padrão é o número mínimo de dígitos e os números mais curtos são preenchidos com zero com esse valor. Para análise, o número de letras padrão é ignorado, a menos que seja necessário separar dois campos adjacentes.

Uma solução alternativa poderia ser testar o formato aaaa-MM-dd com um regex:

 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; } 

Obrigado @Teetoo. Isso me ajudou a encontrar a solução para o meu problema:

Se eu quiser que a function de análise corresponda exatamente ao padrão, eu tenho que definir “lenient” ( SimpleDateFormat.setLenient ) do meu SimpleDateFormat como 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); 

Isso ainda analisará a data se eu usar apenas uma letra padrão para cada segmento, mas reconhecerá que 2013 não pode ser o dia e, portanto, não corresponde ao segundo padrão. Em combinação com uma verificação de comprimento eu recebo exatamente o que eu quero.