Search code examples
datetimejava-8java-timedatetime-parsingvavr

Java 8 Vavr onFailure concatenation


Im trying to parse a string that contain a date in an unknown format and the way I choose (not the best) is to try all the possible formats until parse correctlly. To do this Im using Vavr library and till now I've created something like this:

// My unknown date
    String date = "2020-11-12T15:15:15.345";


    // Date format that works for my unknown date (just for testing)
    DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
            .appendPattern("yyyy-MM-dd'T'HH:mm:ss[.SSS]")
            .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
            .toFormatter();
    OffsetDateTime value = OffsetDateTime.parse(date, FORMATTER);                   // PARSE CORRECTLY 


// Try all possible formats until one works
    Try<OffsetDateTime> myParsedDate = Try.of(()->date)
            .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd")))
            .onFailure(x->System.out.println("NO yyyy-MM-dd"))

            .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm")))
            .onFailure(x->System.out.println("NO yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm"))

            .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm")))
            .onFailure(x->System.out.println("NO yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm"))

            .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")))
            .onFailure(x->System.out.println("NO yyyy-MM-dd'T'HH:mm:ss.SSS"))

            .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")))
            .onFailure(x->System.out.println("NO yyyy-MM-dd'T'HH:mm:ss.SSSZ"))

            .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")))
            .onFailure(x->System.out.println("NO yyyy-MM-dd'T'HH:mm:ss"))

            .map(x->OffsetDateTime.parse(date, FORMATTER))                          // DOSENT WORK
            .onFailure(x->System.out.println("NO yyyy-MM-dd'T'HH:mm:ss[.SSS]"));
    if(myParsedDate.isSuccess()) {
        System.out.println("OK");
    }else {
        System.out.println("KO");
    }

Output:

NO yyyy-MM-dd
NO yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm
NO yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm
NO yyyy-MM-dd'T'HH:mm:ss.SSS
NO yyyy-MM-dd'T'HH:mm:ss.SSSZ
NO yyyy-MM-dd'T'HH:mm:ss
NO yyyy-MM-dd'T'HH:mm:ss[.SSS]

The question is: how to concatenate many try/catch or in this case using VAVR many actions that when one action fail try the next one and so one ? Thanks


Solution

  • Put your formatter into a Java Stream and try on each of them until one successful:

    import io.vavr.control.Try;
    
    import java.time.OffsetDateTime;
    import java.time.format.DateTimeFormatter;
    import java.time.format.DateTimeFormatterBuilder;
    import java.time.temporal.ChronoField;
    import java.util.Optional;
    import java.util.stream.Stream;
    
    public class Test {
        public static void main(String[] args) {
            String date = "2020-11-12T15:15:15.345";
    
            DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
                    .appendPattern("yyyy-MM-dd'T'HH:mm:ss[.SSS]")
                    .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
                    .toFormatter();
            OffsetDateTime value = OffsetDateTime.parse(date, FORMATTER);
    
            Optional<OffsetDateTime> res = Stream.concat(Stream.of(
                    "yyyy-MM-dd",
                    "yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm",
                    "yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm",
                    "yyyy-MM-dd'T'HH:mm:ss.SSS",
                    "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
                    "yyyy-MM-dd'T'HH:mm:ss")
                    .map(p -> DateTimeFormatter.ofPattern(p)), Stream.of(FORMATTER))
                    .map(fmt -> Try.of(() -> OffsetDateTime.parse(date, fmt)))
                    .filter(Try::isSuccess)
                    .map(Try::get)
                    .findFirst();
    
            System.out.println(res);  //prints Optional[2020-11-12T15:15:15.345Z]
        }
    }
    

    Stream.concat is used for adding the FORMATTER with the rest of the Formatters.

    In the end you will get an Optional<OffsetDateTime>. It will be a None if everything failed, or a Some if one of them succeeded. The Java Stream is lazy so that once one match found, it will stop the rest from being executed.

    If you want to print out all the failed cases too, you can add onFailure before filter.


    Edit: adding the case for special FORMATTER