Question

My program is parsing an input string to a LocalDate object. For most of the time the string looks like 30.03.2014, but occasionally it looks like 3/30/2014. Depending on which, I need to use a different pattern to call DateTimeFormatter.ofPattern(String pattern) with. Basically, I need to check if the string matches the pattern dd.MM.yyyy or M/dd/yyyy before doing the parsing.

The regex approach would be something like:

LocalDate date;
if (dateString.matches("^\\d?\\d/\\d{2}/\\d{4}$")) {
  date = LocalDate.parse(dateString, DateTimeFormatter.ofPattern("M/dd/yyyy"));  
} else {
  date = LocalDate.parse(dateString, DateTimeFormatter.ofPattern("dd.MM.yyyy"));  
}

This works, but it would be nice to use the date pattern string when matching the string also.

Are there any standard ways to do this with the new Java 8 time API, without resorting to regex matching? I have looked in the docs for DateTimeFormatter but I couldn't find anything.

Was it helpful?

Solution

Okay I'm going ahead and posting it as an answer. One way is to create the class that will holds the patterns.

public class Test {
    public static void main(String[] args){
        MyFormatter format = new MyFormatter("dd.MM.yyyy", "M/dd/yyyy");
        LocalDate  date = format.parse("3/30/2014"); //2014-03-30
        LocalDate  date2 = format.parse("30.03.2014"); //2014-03-30
    }
}

class MyFormatter {
    private final String[] patterns;

    public MyFormatter(String... patterns){
        this.patterns = patterns;
    }

    public LocalDate parse(String text){
        for(int i = 0; i < patterns.length; i++){
            try{
                return LocalDate.parse(text, DateTimeFormatter.ofPattern(patterns[i]));
            }catch(DateTimeParseException excep){}
        }
        throw new IllegalArgumentException("Not able to parse the date for all patterns given");
    }
}

You could improve this as @MenoHochschild did by directly creating an array of DateTimeFormatter from the array of String you pass in the constructor.


Another way would be to use a DateTimeFormatterBuilder, appending the formats you want. There may exists some other ways to do it, I didn't go deeply through the documentation :-)

DateTimeFormatter dfs = new DateTimeFormatterBuilder()
                           .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd"))                                                                 
                           .appendOptional(DateTimeFormatter.ofPattern("dd.MM.yyyy"))                                                                                     
                           .toFormatter();
LocalDate d = LocalDate.parse("2014-05-14", dfs); //2014-05-14
LocalDate d2 = LocalDate.parse("14.05.2014", dfs); //2014-05-14

OTHER TIPS

With DateTimeFormatter, optional patterns can be specified using square brackets.

Demo:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("[d.M.u][M/d/u][u-M-d]", Locale.ENGLISH);
        Stream.of(
                    "3/30/2014",
                    "30.03.2014",
                    "2014-05-14",
                    "14.05.2014"
        ).forEach(s -> System.out.println(LocalDate.parse(s, dtf)));
    }
}

Output:

2014-03-30
2014-03-30
2014-05-14
2014-05-14

Learn more about the the modern date-time API* from Trail: Date Time.


* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

The approach of @ZouZou is a possible solution.

In order to avoid using exceptions for program logic as much as possible (also not so nice performancewise) following alternative might be considered:

static final String[] PATTERNS = {"dd.MM.yyyy", "M/dd/yyyy"};
static final DateTimeFormatter[] FORMATTERS = new DateTimeFormatter[PATTERNS.length];

static {
  for (int i = 0; i < PATTERNS.length; i++) {
    FORMATTERS[i] = DateTimeFormatter.ofPattern(PATTERNS[i]);
  }
}

public static LocalDate parse(String input) {
  ParsePosition pos = new ParsePosition();
  for (int i = 0; i < patterns.length; i++) {
    try {
      TemporalAccessor tacc = FORMATTERS[i].parseUnresolved(input, pos);
      if (pos.getErrorIndex < 0) {
        return LocalDate.from(tacc); // possibly throwing DateTimeException => validation failure
      }
    } catch (DateTimeException ex) { // catches also possible DateTimeParseException
      // go to next pattern
    }
    pos.setIndex(0);
    pos.setErrorIndex(-1);
  }
  throw new IllegalArgumentException("Input does not match any pattern: " + input);
}

More explanation about the method parseUnresolved():

This method does only the first phase of parsing, so there is no second phase containing preliminary validation or combining effort of parsed fields. However, LocalDate.from() does validate every input, so I think this is still sufficient. And the advantage is that parseUnresolved() uses the error index of ParsePosition. This is in agreement with traditional java.text.Format-behaviour.

Unfortunately the alternative and more intuitive method DateTimeFormater.parse() first creates a DateTimeParseException and then store the error index in this exception. So I decided not to use this method in order to avoid the creation of an unnecessary exception. For me, this API-detail is a questionable design decision.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top