SimpleDateFormat.parse () ignora il numero di caratteri nel pattern

Sto cercando di analizzare una data String che può avere diversi formati di albero. Anche se la stringa non dovrebbe corrispondere al secondo modello, in qualche modo lo fa e quindi restituisce una data errata.

Questo è il mio codice:

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 l’input 2013-01-31 ottengo l’output 05.07.0036 .

Se provo a analizzare 31-01-2013 o 31.01.2013 ottengo 31.01.2013 come previsto.

Ho riconosciuto che il programma mi darà esattamente lo stesso risultato se imposto i pattern in questo modo:

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

Perché ignora il numero di caratteri nel mio pattern?

Esistono diversi problemi gravi con SimpleDateFormat. L’impostazione del privilegio predefinita può produrre risposte inutili e non riesco a pensare a un caso in cui il clemente abbia qualche vantaggio. Questa non dovrebbe mai essere l’impostazione predefinita. Ma disabilitare la clemenza è solo una parte della soluzione. Puoi ancora finire con i risultati garbage che sono difficili da catturare nei test. Vedi i commenti nel codice qui sotto per gli esempi.

Ecco un’estensione di SimpleDateFormat che impone una corrispondenza di pattern rigorosa. Questo dovrebbe essere il comportamento predefinito per quella 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; } } 

È documentato in javadoc SimpleDateFormat :

Per la formattazione, il numero di lettere di pattern è il numero minimo di cifre, e i numeri più brevi sono riempiti a zero per questo importo. Per l’analisi, il numero di lettere di pattern viene ignorato a meno che non sia necessario separare due campi adiacenti.

Una soluzione alternativa potrebbe essere quella di testare il formato aaaa-MM-g con una 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; } 

Grazie a @Teetoo. Questo mi ha aiutato a trovare la soluzione al mio problema:

Se voglio che la funzione di analisi corrisponda esattamente al pattern, devo impostare ” SimpleDateFormat.setLenient ” ( SimpleDateFormat.setLenient ) del mio SimpleDateFormat su 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); 

Ciò analizzerà ancora la data se utilizzo solo una lettera di modello per ogni segmento ma riconoscerà che il 2013 non può essere il giorno e quindi non corrisponde al secondo modello. In combinazione con un controllo della lunghezza, ricevo esattamente quello che voglio.