How to parse dates in multiple formats using SimpleDateFormat

If working in Java 1.8 you can leverage the DateTimeFormatterBuilder

public static boolean isTimeStampValid(String inputString)
{
    DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ofPattern("" + "[yyyy-MM-dd'T'HH:mm:ss.SSSZ]" + "[yyyy-MM-dd]"));

    DateTimeFormatter dateTimeFormatter = dateTimeFormatterBuilder.toFormatter();

    try {
        dateTimeFormatter.parse(inputString);
        return true;
    } catch (DateTimeParseException e) {
        return false;
    }
}

See post: Java 8 Date equivalent to Joda's DateTimeFormatterBuilder with multiple parser formats?


What about just defining multiple patterns? They might come from a config file containing known patterns, hard coded it reads like:

List<SimpleDateFormat> knownPatterns = new ArrayList<SimpleDateFormat>();
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"));
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm.ss'Z'"));
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"));
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss"));
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"));

for (SimpleDateFormat pattern : knownPatterns) {
    try {
        // Take a try
        return new Date(pattern.parse(candidate).getTime());

    } catch (ParseException pe) {
        // Loop on
    }
}
System.err.println("No known Date format found: " + candidate);
return null;

Matt's approach above is fine, but please be aware that you will run into problems if you use it to differentiate between dates of the format y/M/d and d/M/y. For instance, a formatter initialised with y/M/d will accept a date like 01/01/2009 and give you back a date which is clearly not what you wanted. I fixed the issue as follows, but I have limited time and I'm not happy with the solution for 2 main reasons:

  1. It violates one of Josh Bloch's quidelines, specifically 'don't use exceptions to handle program flow'.
  2. I can see the getDateFormat() method becoming a bit of a nightmare if you needed it to handle lots of other date formats.

If I had to make something that could handle lots and lots of different date formats and needed to be highly performant, then I think I would use the approach of creating an enum which linked each different date regex to its format. Then use MyEnum.values() to loop through the enum and test with if(myEnum.getPattern().matches(date)) rather than catching a dateformatexception.

Anway, that being said, the following can handle dates of the formats 'y/M/d' 'y-M-d' 'y M d' 'd/M/y' 'd-M-y' 'd M y' and all other variations of those which include time formats as well:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtil {
    private static final String[] timeFormats = {"HH:mm:ss","HH:mm"};
    private static final String[] dateSeparators = {"/","-"," "};

    private static final String DMY_FORMAT = "dd{sep}MM{sep}yyyy";
    private static final String YMD_FORMAT = "yyyy{sep}MM{sep}dd";

    private static final String ymd_template = "\\d{4}{sep}\\d{2}{sep}\\d{2}.*";
    private static final String dmy_template = "\\d{2}{sep}\\d{2}{sep}\\d{4}.*";

    public static Date stringToDate(String input){
    Date date = null;
    String dateFormat = getDateFormat(input);
    if(dateFormat == null){
        throw new IllegalArgumentException("Date is not in an accepted format " + input);
    }

    for(String sep : dateSeparators){
        String actualDateFormat = patternForSeparator(dateFormat, sep);
        //try first with the time
        for(String time : timeFormats){
        date = tryParse(input,actualDateFormat + " " + time);
        if(date != null){
            return date;
        }
        }
        //didn't work, try without the time formats
        date = tryParse(input,actualDateFormat);
        if(date != null){
        return date;
        }
    }

    return date;
    }

    private static String getDateFormat(String date){
    for(String sep : dateSeparators){
        String ymdPattern = patternForSeparator(ymd_template, sep);
        String dmyPattern = patternForSeparator(dmy_template, sep);
        if(date.matches(ymdPattern)){
        return YMD_FORMAT;
        }
        if(date.matches(dmyPattern)){
        return DMY_FORMAT;
        }
    }
    return null;
    }

    private static String patternForSeparator(String template, String sep){
    return template.replace("{sep}", sep);
    }

    private static Date tryParse(String input, String pattern){
    try{
        return new SimpleDateFormat(pattern).parse(input);
    }
    catch (ParseException e) {}
    return null;
    }


}

You'll need to use a different SimpleDateFormat object for each different pattern. That said, you don't need that many different ones, thanks to this:

Number: For formatting, the number of pattern letters is the minimum number of digits, and shorter numbers are zero-padded to this amount. For parsing, the number of pattern letters is ignored unless it's needed to separate two adjacent fields.

So, you'll need these formats:

  • "M/y" (that covers 9/09, 9/2009, and 09/2009)
  • "M/d/y" (that covers 9/1/2009)
  • "M-d-y" (that covers 9-1-2009)

So, my advice would be to write a method that works something like this (untested):

// ...
List<String> formatStrings = Arrays.asList("M/y", "M/d/y", "M-d-y");
// ...

Date tryParse(String dateString)
{
    for (String formatString : formatStrings)
    {
        try
        {
            return new SimpleDateFormat(formatString).parse(dateString);
        }
        catch (ParseException e) {}
    }

    return null;
}