Home > Software design >  Check what type of Temporal a given format string can be used to parse
Check what type of Temporal a given format string can be used to parse

Time:11-04

I'm given some datetime format string that user entered, and need to check into what java.time temporals I can parse data in that format (within reason, I indend to support only the simpler cases for now).

Something along the lines of this table:

Input Format Expected Answer
yyyy-MM java.time.YearMonth
MM/dd/yyyy java.time.LocalDate
yyyy:DD java.time.LocalDate (because of day-of-year data)
HH:mm:ss java.time.LocalTime
dd MM yyyy hh:mm:ssV java.time.ZonedDateTime

Keeping in mind, that both the date format and the date are entered by the user, so these input formats are just examples, and they obviously can contain literals (but not optional parts, that's a relief).

So far I've only been able to come up with this tornado of ifs as a solution:

private static final ZonedDateTime ZDT = ZonedDateTime.of(1990, 10, 26, 14, 40, 59, 123456, ZoneId.of("Europe/Oslo"));
...
String formatted = externalFormatter.format(ZDT);
Class<?> type;
TemporalAccessor parsed = externalFormatter.parse(formatted);
if (parsed.isSupported(YEAR)) {
    if (parsed.isSupported(MONTH_OF_YEAR)) {
        if (parsed.query(TemporalQueries.localDate()) != null) {
            if (parsed.query(TemporalQueries.localTime()) != null) {
                if (parsed.query(TemporalQueries.zone()) != null) {
                    type = ZonedDateTime.class;
                }
                else if (parsed.query(TemporalQueries.offset()) != null) {
                    type = OffsetDateTime.class;
                }
                else {
                    type = LocalDateTime.class;
                }
            }
            else {
                type = LocalDate.class;
            }
        }
        else {
            type = YearMonth.class;
        }
    }
    else {
        type = Year.class;
    }
}
else if (parsed.query(TemporalQueries.localTime()) != null) {
    if (parsed.query(TemporalQueries.offset()) != null) {
       type = OffsetTime.class;
    }
    else {
        type = LocalTime.class;
    }
}

Surely, there must be some better way, at least marginally? I will not limit myself to just using java.time, I also have the Joda-Time library available to me (although it's technically on legacy status), and I will not turn down a simpler code that uses the SimpleDateFormat if there is such an option.

CodePudding user response:

DateTimeFormatter::parseBest()

I am taking your word for it:

Keeping in mind, that both the date format and the date are entered by the user, …

So I am assuming that I may use both the format pattern and the date string entered for seeing what I can make of it. DateTimeFormatter::parseBest() is the method we need for that.

public static TemporalAccessor parse(String formatPattern, String toBeParsed) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatPattern, Locale.ROOT);
    return formatter.parseBest(toBeParsed, ZonedDateTime::from,
            LocalDate::from, LocalTime::from, YearMonth::from);
}

For demonstrating the power of the above method I am using the following auxiliary method:

public static void demo(String formatPattern, String toBeParsed) {
    TemporalAccessor result = parse(formatPattern, toBeParsed);
    System.out.format("%-21s %s%n", formatPattern, result.getClass().getName());
}

Let’s call it using your examples:

    demo("yyyy-MM", "2017-11");
    demo("MM/dd/yyyy", "10/21/2023");
    demo("yyyy:DD", "2021:303");
    demo("HH:mm:ss", "23:34:45");
    demo("dd MM yyyy HH:mm:ssVV", "05 09 2023 14:01:55Europe/Oslo");

I have corrected a couple of errors in the last format pattern string. Output is:

yyyy-MM               java.time.YearMonth
MM/dd/yyyy            java.time.LocalDate
yyyy:DD               java.time.LocalDate
HH:mm:ss              java.time.LocalTime
dd MM yyyy HH:mm:ssVV java.time.ZonedDateTime

In the list of TemporalQuery arguments to parseBest() it’s essential to put the classes that hold the most information first. Or which classes you prefer, but I assumed you wanted as much information as you can have. parseBest() uses the first query that works. So put ZonedDateTime first and Year, Month and DayOfWeek last if you intend to support any of the last three.

Remember to decide which locale you want.

With only the format pattern string

If you are supposed to do the trick without the string to be parsed, it’s not badly more complicated. Start by formatting some ZonedDateTime using the formatter that you have constructed from the format pattern string. Then use parseBest() to parse the resulting string back. You must use ZonedDateTime because it’s the only class that holds about every field that could thinkably be in the format pattern string, so with some other class, formatting could break. On the other hand the information coming into the formatted string is limited by the pattern, so in most cases you won’t be able to parse a ZonedDateTime back. Which is exactly what we want: parseBest() will tell us which class we can parse into.

public static Class<? extends TemporalAccessor> getParseableType(String formatPattern) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatPattern, Locale.ROOT);
    String example = ZonedDateTime.now(ZoneId.systemDefault()).format(formatter);
    TemporalAccessor parseableTemporalAccessor = formatter.parseBest(example, ZonedDateTime::from,
            LocalDate::from, LocalTime::from, YearMonth::from);
    return parseableTemporalAccessor.getClass();
}

Documentation link

parseBest​(CharSequence text, TemporalQuery<?>... queries)

  • Related