For some time now I’ve been using “@DateFormat@(DateFormat JavaDoc)”:http://java.sun.com/j2se/1.4.2/docs/api/java/text/DateFormat.html (actually “@SimpleDateFormat@(SimpleDateFormat JavaDoc)”:http://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html) to parse user inputted dates it situations where a decent date chooser wasn’t available. Some simple bit of code like this would normally suffice:
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd/mm/YYYY");
...
try {
DATE_FORMAT.parse(someString);
} catch (ParseException pe) {
//date is invalid
}
I learnt two things about this approach. Firstly, @DateFormat@ is _not_ thread safe. This fact was only documented in Java 1.4 despite it having never been thread safe. This is one of those things you learn, and is easily put right.
The second thing however, is that the parsing itself is lazy to the point that it is actually just plain wrong. Lets take @12/12/200@ as a value for @someString@ in the code above. @SimpleDateFormat@ will parse the string and return a date (12th of December in the year 200 AD) and Will not throw an exception, despite the fact we specified a four digit year. This is certainly not the expected behaviour – what if I missed off a final digit by accident? Even worse, it will also parse @12/12/200T@. The nearest the documentation comes to warning us of this behaviour is this little note in the @parse@ method:
Parses text from the beginning of the given string to produce a date. The method may not use the entire text of the given string.
From this one might of guessed that it would parse @12/12/2004T@ as a valid date, but not @12/12/200T@…
The net result is that we’ve had to change our date validation code over to a two pass system – we use a @SimpleDateFormat@ to make sure we are using sensible values (e.g. not trying to enter the 33rd of June or something) and then use a @StringTokenizer@ to break up the string and check that four digits are being entered for the year (we only have Java 1.3 available to us so no regular expressions). Just another example of why the Calendar API is perhaps the worst on the Java platform.
_Update_: We managed to find a solution to the problem with the lazy parsing – the DateFormat.setLenient()
turns on strict error checking:
Specify whether or not date/time parsing is to be lenient. With lenient parsing, the parser may use heuristics to interpret inputs that do not precisely match this object’s format. With strict parsing, inputs must match this object’s format.
Perhaps another case of me not reading the Javadoc, however this method is not described in the overviews of either @DateFormat@ or @SimpleDateFormat@.
5 Responses to “Beware using DateFormat for input validation”
Actually, I think its just SimpleDateFormat that is not thread-safe, but don’t quote me. I generally use the Jakarta commons-lang 2.0 FastDateFormat class, thread-safe and quicker than SimpleDateFormat.
What context are you entering dates? There are a number of good javascript date validation scripts around, and some free Java implementations as well.
_Edited to stop layout problem_:
“Javascript Validation Links”:http://www.google.com/search?num=50&hl=en&lr=&ie=UTF-8&oe=UTF-8&c2coff=1&q=javascriptdate validation&btnG=Search
According to the documentation, @DateFormat@ isn’t synchronized either:
bq. “Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.”
We discovered the non-threadsafeness of SimpleDateFormat a few months ago. It bit us big time…
We ended up doing this to force it….
/*
* Created on 11-Jun-04
*/
package domain;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
/**
* @author Colin Hawkett
*/
public class StrictDateFormat extends SimpleDateFormat {
public StrictDateFormat(String format) {
super(format);
}
public java.util.Date parse(String dateString) throws ParseException {
if(dateString.length() != toPattern.length()) throw new ParseException(“Input string wrong length!”, 0);
ParsePosition pos = new ParsePosition(0);
java.util.Date date = super.parse(dateString, pos);
if((date == null) || (pos.getErrorIndex() != -1)) throw new ParseException(“Dodgy date string!”, pos.getIndex());
if(pos.getIndex() != toPattern().length()) throw new ParseException(“Did not use entire string to parse date!”, pos.getIndex());
else return date;
}
}
One more note: without the dateFormat.setLenient(false), the SimpleDateFormatter will convert a date like 12/44/2004 into something like 1/13/2005. It doesn’t catch months_out_of_range or days_in_month_out_of_range errors.