DateTimeFormatter Accepting Multiple Dates and Converting to One (java.time library)
Asked Answered
C

4

9

I am trying to write a DateTimeFormatter that will allow me to take in multiple different String formats, and then convert the String formats to a specific type. Due to the scope of the project and the code that already exists, I cannot use a different type of formatter.

E.g., I want to accept MM/dd/yyyy as well as yyyy-MM-dd'T'HH:mm:ss but then when I print I only want to print to MM/dd/yyyy format and have it in the format when I call LocalDate.format(formatter);

Could someone suggest ideas on how to do this with the java.time.format.*;

Here is how I could do it in org.joda:

// MM/dd/yyyy format
DateTimeFormatter monthDayYear = DateTimeFormat.forPattern("MM/dd/yyyy");
// array of parsers, with all possible input patterns
DateTimeParser[] parsers = {
        // parser for MM/dd/yyyy format
        monthDayYear.getParser(),
        // parser for yyyy-MM-dd'T'HH:mm:ss format
        DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss").getParser()
};
DateTimeFormatter parser = new DateTimeFormatterBuilder()
    // use the monthDayYear formatter for output (monthDayYear.getPrinter())
    // and parsers array for input (parsers)
    .append(monthDayYear.getPrinter(), parsers)
    // create formatter (using UTC to avoid DST problems)
    .toFormatter()
    .withZone(DateTimeZone.UTC);

I have not found a good/working example of this online.

Craggy answered 17/6, 2017 at 3:18 Comment(0)
A
31

I've tested this with JDK 1.8.0_131 for Mac OS X and JDK 1.8.0111 for Windows (both worked).

I've created a DateTimeFormatter with optional sections (delimited by []), to parse both cases (MM/dd/yyyy and yyyy-MM-dd'T'HH:mm:ss).

The same formatter worked for your case (LocalDate), but there are some considerations below.

// parse both formats (use optional section, delimited by [])
DateTimeFormatter parser = DateTimeFormatter.ofPattern("[MM/dd/yyyy][yyyy-MM-dd'T'HH:mm:ss]");

// parse MM/dd/yyyy
LocalDate d1 = LocalDate.parse("10/16/2016", parser);
// parse yyyy-MM-dd'T'HH:mm:ss
LocalDate d2 = LocalDate.parse("2016-10-16T10:20:30", parser);

// parser.format(d1) is the same as d1.format(parser)
System.out.println(parser.format(d1));
System.out.println(parser.format(d2));

The output is:

10/16/2016
10/16/2016


PS: this works only with LocalDate. If I try to format an object with time fields (like LocalDateTime), both formats are used:

System.out.println(parser.format(LocalDateTime.now()));

This prints:

06/18/20172017-06-18T07:40:55

Note that it formatted with both patterns. My guess is that the formatter checks if the object has the fields in each optional section. As the LocalDate has no time fields (hour/minute/second), the second pattern fails and it prints only the first one (MM/dd/yyyy). But the LocalDateTime has all the time fields, and both patterns are valid, so both are used to format.

My conclusion is: this isn't a general solution (like the Joda-Time's version), it's more like a "lucky" case where the patterns involved created the desired situation. But I wouldn't rely on that for all cases.

Anyway, if you are only using LocalDate, you can try to use this code. But if you're working with another types, then you'll probably have to use another formatter for the output, like this:

// parser/formatter for month/day/year
DateTimeFormatter mdy = DateTimeFormatter.ofPattern("MM/dd/yyyy");
// parser for both patterns
DateTimeFormatter parser = new DateTimeFormatterBuilder()
    // optional MM/dd/yyyy
    .appendOptional(mdy)
    // optional yyyy-MM-dd'T'HH:mm:ss (use built-in formatter)
    .appendOptional(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
    // create formatter
    .toFormatter();

// parse MM/dd/yyyy
LocalDate d1 = LocalDate.parse("10/16/2016", parser);
// parse yyyy-MM-dd'T'HH:mm:ss
LocalDate d2 = LocalDate.parse("2016-10-16T10:20:30", parser);

// use mdy to format
System.out.println(mdy.format(d1));
System.out.println(mdy.format(d2));

// format object with time fields: using mdy formatter to avoid multiple pattern problem
System.out.println(mdy.format(LocalDateTime.now()));

The output is:

10/16/2016
10/16/2016
06/18/2017

Ankh answered 18/6, 2017 at 10:59 Comment(1)
Tanks! The one that should be at the topFroma
T
2

What you're asking is not possible.

DateTimeFormatter is a final class, so you cannot subclass it to implement your own behavior.

The constructor is package-private, so you can't call it yourself. The only way to create a DateTimeFormatter is by using a DateTimeFormatterBuilder. Note that the static helper methods for creating a DateTimeFormatter are internally using DateTimeFormatterBuilder, e.g.

public static DateTimeFormatter ofPattern(String pattern) {
    return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter();
}

DateTimeFormatterBuilder is also a final class, and cannot be subclassed, and it doesn't provide any methods for supplying multiple alternate formats to use, like you want.

In short, DateTimeFormatter is closed and cannot be extended. If your code can only use a DateTimeFormatter, then you are out of luck.

Toffeenosed answered 17/6, 2017 at 4:50 Comment(3)
I want to be able to allow multiple different types of input.. I know for the org.joda I can do this what I listed above.Craggy
I know it's for sure possible in org.joda.. is the java library just directly inferior and less useful than the joda one?Craggy
@AlexKornhauser It is neither inferior nor less useful than Joda-Time. It's just slightly different. I believe some of the people that wrote Joda-Time was part of the group writing the java.time classes, applying lessons learned from Joda-Time, which means it's actually better than Joda-Time. The Joda-Time website even encourages people to switch from Joda-Time to java.time: Joda-Time is the de facto standard date and time library for Java prior to Java SE 8. Users are now asked to migrate to java.time (JSR-310).Toffeenosed
O
2

The Answer by Andreas is correct and should be accepted.

Check length of string

As an alternative, you can simply test the length of your string and apply one of two formatters.

DateTimeFormatter fDateOnly = DateTimeFormatter.ofPattern( "MM/dd/uuuu" ) ;
DateTimeFormatter fDateTime = DateTimeFormatter.ISO_LOCAL_DATE_TIME ;

LocalDate ld = null ;
if( input.length() == 10 ) {
    try {
        ld = LocalDate.parse( input , fDateOnly ) ;
    } catch (DateTimeParseException  e ) {
        …
    }
} else if ( input.length() == 19 ) {
    try {
        LocalDateTime ldt = LocalDateTime.parse( input , fDateTime ) ;
        ld = ldt.toLocalDate() ;
    } catch (DateTimeParseException  e ) {
        …
    }
} else {
    // Received unexpected input.
    …
}

String output = ld.format( fDateOnly ) ;

Be aware that you can let java.time automatically localize when generating a string representing the value of your date-time rather than hard-code a specific format. See DateTimeFormatter.ofLocalizedDate.

Off answered 17/6, 2017 at 5:5 Comment(0)
D
2

The parsing part can be written, and has been added in the ThreeTen-Extra library. The relevant code is here and is included below for clarity. The key trick is using parseUnresolved() to find out which format is correct:

public static <T> T parseFirstMatching(CharSequence text, TemporalQuery<T> query, DateTimeFormatter... formatters) {
    Objects.requireNonNull(text, "text");
    Objects.requireNonNull(query, "query");
    Objects.requireNonNull(formatters, "formatters");
    if (formatters.length == 0) {
        throw new DateTimeParseException("No formatters specified", text, 0);
    }
    if (formatters.length == 1) {
        return formatters[0].parse(text, query);
    }
    for (DateTimeFormatter formatter : formatters) {
        try {
            ParsePosition pp = new ParsePosition(0);
            formatter.parseUnresolved(text, pp);
            int len = text.length();
            if (pp.getErrorIndex() == -1 && pp.getIndex() == len) {
                return formatter.parse(text, query);
            }
        } catch (RuntimeException ex) {
            // should not happen, but ignore if it does
        }
    }
    throw new DateTimeParseException("Text '" + text + "' could not be parsed", text, 0);
}

Unfortunately, there is no way to write a single DateTimeFormatter that supports flexible parsing and prints using a specific output format as per Joda-Time.

Delly answered 19/6, 2017 at 8:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.