How to check if string matches date pattern using time API?
Asked Answered
A

3

18

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.

Afterword answered 6/5, 2014 at 7:39 Comment(7)
Why dont you string replace("//", ".") ?Abrams
I don't think there is a specific method to check if a date follows a given pattern. I would use the SimpleDateFormat.parse method and check for a ParseException. In case you are sure that the date is correct and it is in one of the two patterns, you could just check if there is '.' in the string.Ogpu
@RobertNiestroj Because month and day are not positioned the same place in both patterns.Afterword
@HermanTorjussen I'm not posting this as an answer because I don't know if it will be a viable option for you to create a class for this. Is it? Like this: tryjava8.com/app/snippets/5368a343e4b0753f2af0f806Spangler
@Max The class SimpleDateFormat cannot directly parse to a LocalDate, only to java.util.Date which is quite different.Pollard
@ZouZou That's a possible solution, if no other solutions exists already. Please do post it as an answer anyway. BTW, that site you linked failed when I ran the example.Afterword
@HermanTorjussen Look at my updated answer, it may be simple than creating a class for that.Spangler
S
18

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
Spangler answered 6/5, 2014 at 13:44 Comment(1)
Your second Options works brilliant, without handling Exception etc.Nesbitt
A
1

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.

Accumulator answered 8/5, 2021 at 12:5 Comment(0)
P
0

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.

Pollard answered 6/5, 2014 at 12:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.