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 if
s 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();
}