As shown by @Maglob, the basic approach is to test the conversion from string to date using SimpleDateFormat.parse. That will catch invalid day/month combinations like 2008-02-31.
However, in practice that is rarely enough since SimpleDateFormat.parse is exceedingly liberal. There are two behaviours you might be concerned with:
Invalid characters in the date string
Surprisingly, 2008-02-2x will "pass" as a valid date with locale format = "yyyy-MM-dd" for example. Even when isLenient==false.
Years: 2, 3 or 4 digits?
You may also want to enforce 4-digit years rather than allowing the default SimpleDateFormat behaviour (which will interpret "12-02-31" differently depending on whether your format was "yyyy-MM-dd" or "yy-MM-dd")
A Strict Solution with the Standard Library
So a complete string to date test could look like this: a combination of regex match, and then a forced date conversion. The trick with the regex is to make it locale-friendly.
Date parseDate(String maybeDate, String format, boolean lenient) {
Date date = null;
// test date string matches format structure using regex
// - weed out illegal characters and enforce 4-digit year
// - create the regex based on the local format string
String reFormat = Pattern.compile("d+|M+").matcher(Matcher.quoteReplacement(format)).replaceAll("\\\\d{1,2}");
reFormat = Pattern.compile("y+").matcher(reFormat).replaceAll("\\\\d{4}");
if ( Pattern.compile(reFormat).matcher(maybeDate).matches() ) {
// date string matches format structure,
// - now test it can be converted to a valid date
SimpleDateFormat sdf = (SimpleDateFormat)DateFormat.getDateInstance();
sdf.applyPattern(format);
sdf.setLenient(lenient);
try { date = sdf.parse(maybeDate); } catch (ParseException e) { }
}
return date;
}
// used like this:
Date date = parseDate( "21/5/2009", "d/M/yyyy", false);
Note that the regex assumes the format string contains only day, month, year, and separator characters. Aside from that, format can be in any locale format: "d/MM/yy", "yyyy-MM-dd", and so on. The format string for the current locale could be obtained like this:
Locale locale = Locale.getDefault();
SimpleDateFormat sdf = (SimpleDateFormat)DateFormat.getDateInstance(DateFormat.SHORT, locale );
String format = sdf.toPattern();
Joda Time - Better Alternative?
I've been hearing about joda time recently and thought I'd compare. Two points:
- Seems better at being strict about invalid characters in the date string, unlike SimpleDateFormat
- Can't see a way to enforce 4-digit years with it yet (but I guess you could create your own DateTimeFormatter for this purpose)
It's quite simple to use:
import org.joda.time.format.*;
import org.joda.time.DateTime;
org.joda.time.DateTime parseDate(String maybeDate, String format) {
org.joda.time.DateTime date = null;
try {
DateTimeFormatter fmt = DateTimeFormat.forPattern(format);
date = fmt.parseDateTime(maybeDate);
} catch (Exception e) { }
return date;
}