How to get date pattern for a date in Java

649 Views Asked by At
public void parsedate(String date) {
    DateTimeFormatter formatter = null;

    formatter = new DateTimeFormatterBuilder()
            .appendOptional(DateTimeFormatter.ofPattern("yyyyMMdd"))
            .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
            .appendOptional(DateTimeFormatter.ofPattern("MM/dd/yyyy")).toFormatter();
    TemporalAccessor parse = formatter.parse(date);
    System.out.println("original" + date);
    
    return LocalDateTime.parse(date, formatter);
}

    String[] strDates = new String[]{
            "20180429",
            "2018-04-29",
            "04/29/2018",
            "01/20/1999",
            "1899-12-25",
            "2020-02-29", // leap year future
            "00010101",
            "19160229" // leap year past
    };

    for(String date: strDates) {
        parsedate(date);
    }

I am parsing dates with multiple date patterns. I will pass the date string to parsedate method. My date string is parsed. But I want to get the pattern of the date which is parsed.

Example:

Input date: "2018-04-29" Process: parsedate("2018-04-29") Output: date parsed successfully

My expectation is get the pattern as "yyyy-MM-dd" for Input date: "2018-04-29". How to get it?

3

There are 3 best solutions below

0
On

After looking at the documentation, there seems to be no way to see which of the optional formats succeeded. So the easy way out is to build a collection of formatters to try, and to use them in order until one succeeds. However, this is ugly: you are using exceptions for flow-control

LocalDateTime time = null;
DateTimeFormatter goodFormatter = null;
for (DateTimeFormatter formatter : formats) {
    try {
        time = LocalDateTime.parse(date, formatter);
        goodFormatter = formatter;
        break;
    } catch (DateTimeParseException dtpe) {
        // not really exceptional - bad coding practice
    }
}
// if goodFormatter != null, there you have it

An existing question on SO deals with this problem. My suggested answer goes along the lines of one of its answers.

A cleaner option may be to use your formats both as regular expressions (to only examine likely candidates) and as actual date formats (to parse only the good candidates). This comes at a cost in readability, and will still throw exceptions when parsing ambiguous formats, because the format-to-regex code is very simplistic:

enum FormatCandidate {
    YMD("yyyyMMdd"),
    YMD_DASHED("yyyy-MM-dd"),
    MDY_SLASHED("MM/dd/yyyy");

    private final Pattern pattern;
    public final DateTimeFormatter formatter;  // immutable & thread safe

    FormatCandidate(String format) {
        // THIS ONLY WORKS FOR SIMPLE EXAMPLES; modify for more complex ones!
        this.pattern = Pattern.compile(format
              .replaceAll("[-/]", "\\\0")  // preserved characters
              .replaceAll("[yMd]","\\d")   // digits
        ));
        this.formatter = new DateTimeFormatterBuilder()
              .appendPattern(format).toFormatter();
    }

    // this should really be available in the DateTimeFormatter class
    public boolean canParse(String date) {
        return pattern.matcher(date).matches();
    }
}

The initial code could now be written as:

DateTimeFormatter goodFormatter = null;
for (FormatCandidate candidate : FormatCandidate.values()) {
    if (candidate.canParse(date)) {
       goodFormatter = candidate.format;
       break;
    }
}

LocalDateTime time = null;   
try {
    time = LocalDateTime.(date, candidate.formatter);
} catch (DateTimeParseException dtpe) {
    // now we can complain, saying that date does not match this particular
    // expected output
}

Before complicating this further, I would probably just go with ugly exceptions-as-control-flow (1st code snippet) as a lesser evil to re-implementing a time-parsing library.

disclaimer: above code is un-tested and may not compile and/or perform as expected

1
On

I think I agree with Andreas in the comment: It’s quite simple to take a taste of the date string to determine its format.

public static String getPattern(String dateString) {
    if (dateString.length() == 8) { // compact format
        return "uuuuMMdd";
    } else if (dateString.charAt(2) == '/') {
        return "MM/dd/uuuu";
    } else {
        return "uuuu-MM-dd";
    }
}

Try it with the sample strings from your question:

    String[] strDates = new String[]{
            "20180429",
            "2018-04-29",
            "04/29/2018",
            "01/20/1999",
            "1899-12-25",
            "2020-02-29", // leap year this year
            "00010101",
            "19160229" // leap year past
    };

    for(String date: strDates) {
        String pattern = getPattern(date);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        LocalDate parsedDate = LocalDate.parse(date, formatter);
        System.out.format("%-10s pattern: %-10s parsed: %s%n", date, pattern, parsedDate);
    }

Output is:

20180429   pattern: uuuuMMdd   parsed: 2018-04-29
2018-04-29 pattern: uuuu-MM-dd parsed: 2018-04-29
04/29/2018 pattern: MM/dd/uuuu parsed: 2018-04-29
01/20/1999 pattern: MM/dd/uuuu parsed: 1999-01-20
1899-12-25 pattern: uuuu-MM-dd parsed: 1899-12-25
2020-02-29 pattern: uuuu-MM-dd parsed: 2020-02-29
00010101   pattern: uuuuMMdd   parsed: 0001-01-01
19160229   pattern: uuuuMMdd   parsed: 1916-02-29

If you prefer a more dynamic, automatic and extensible solution:

private static final String[] formatPatterns = { "uuuuMMdd", "uuuu-MM-dd", "MM/dd/uuuu" };

public static String getPattern(String dateString) {
    for (String pattern : formatPatterns) {
        try {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
            LocalDate.parse(dateString, formatter);
            // If we ended up here, using the pattern was successful
            return pattern;
        } catch (DateTimeParseException dtpe) {
            // Do nothing, try next pattern
        }
    }
    throw new IllegalArgumentException("We don’t know the format pattern string for " + dateString);
}
3
On

FTA, of which I am the author, (https://github.com/tsegall/fta) is designed to solve exactly this problem (among others). It currently parses thousands of formats and does not do it via a predefined set, so typically runs extremely quickly. Here is an example, the first line is approximately the provided set of samples, the second line has a set of additional samples:

    public static void main(final String[] args) {
    String[] strDates = {
            "20180429", "2018-04-29", "04/29/2018", "01/20/1999", "1899-12-25", "2020-02-29", "20010101", "19160229",
            "2018.04.29", "2018.29.04", "04.29.2018", "29.04.2018", "04.2018.29", "29.2018.04", "2020/2/29",
    };


    final DateTimeParser dtp = new DateTimeParser();

    for(String date: strDates) {
        String pattern = dtp.determineFormatString(date);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        LocalDate parsedDate = LocalDate.parse(date, formatter);
        System.out.format("%-10s pattern: %-10s parsed: %s%n", date, pattern, parsedDate);
    }
}

Which will give the following output:

20180429   pattern: yyyyMMdd   parsed: 2018-04-29
2018-04-29 pattern: yyyy-MM-dd parsed: 2018-04-29
04/29/2018 pattern: MM/dd/yyyy parsed: 2018-04-29
01/20/1999 pattern: MM/dd/yyyy parsed: 1999-01-20
1899-12-25 pattern: yyyy-MM-dd parsed: 1899-12-25
2020-02-29 pattern: yyyy-MM-dd parsed: 2020-02-29
20010101   pattern: yyyyMMdd   parsed: 2001-01-01
19160229   pattern: yyyyMMdd   parsed: 1916-02-29
2018.04.29 pattern: yyyy.MM.dd parsed: 2018-04-29
2018.29.04 pattern: yyyy.dd.MM parsed: 2018-04-29
04.29.2018 pattern: MM.dd.yyyy parsed: 2018-04-29
29.04.2018 pattern: dd.MM.yyyy parsed: 2018-04-29
04.2018.29 pattern: MM.yyyy.dd parsed: 2018-04-29
29.2018.04 pattern: dd.yyyy.MM parsed: 2018-04-29
2020/2/29  pattern: yyyy/M/dd  parsed: 2020-02-29