Reputation: 2349
There already exists question Java 8 DateTimeFormatter with optional part, but the answer for it won't work when optional part is only hours and minutes without seconds:
DateTimeFormatter patternWithOptional = new DateTimeFormatterBuilder()
.appendPattern("M/d/yyyy[ h:mm]")
.toFormatter();
TemporalAccessor tmp = patternWithOptional.parseBest("4/11/2020 1:20", LocalDateTime::from, LocalDate::from);
System.out.println(tmp);
System.out.println(tmp.getClass().getSimpleName());
// prints 2020-04-11, without time
// class is LocalDate in this case
This is because LocalDateTime.from(TemporalAccessor temporal)
uses TemporalQueries.LOCAL_TIME
to query the temporal
. TemporalQueries.LOCAL_TIME
, in turn, requires ChronoField.NANO_OF_DAY
to be available from temporal
.
System.out.println(patternWithOptional.parse("4/11/2020 1:20"));
// prints:
{HourOfAmPm=1, MinuteOfHour=20},ISO resolved to 2020-04-11
Since HourOfAmPm
and MinuteOfHour
are available, the simplest way to achieve what I need, that I could come up with:
private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
.appendPattern("M/d/yyyy[ h:mm]")
.toFormatter();
public static void main(String[] args) {
// demo
System.out.println(parse("3/20/1995")); // without time
System.out.println(parse("4/11/2020 1:20")); // with time
}
private static LocalDateTime parse(String s) {
TemporalAccessor parsed = FORMATTER.parse(s);
return LocalDateTime.of(
LocalDate.from(parsed),
LocalTime.of(
parsed.isSupported(ChronoField.HOUR_OF_AMPM)
? (int) ChronoField.HOUR_OF_AMPM.getFrom(parsed)
: 0,
parsed.isSupported(ChronoField.MINUTE_OF_HOUR)
? (int) ChronoField.MINUTE_OF_HOUR.getFrom(parsed)
: 0
) // minor note: this part could also be used as a lambda argument for parseBest method.
);
}
Is there a simpler way to do this without explicit isSupported
checks?
Upvotes: 2
Views: 4766
Reputation: 86262
In your format pattern string you need to use uppercase H
for hour of day:
.appendPattern("M/d/yyyy[ H:mm]")
With this change your first snippet prints
2020-04-11T01:20
LocalDateTime
Lowercase h
is for hour within AM or PM. So your time might have meant 01:20 AM or 01:20 PM, that is, 13:20. Java could not decide and therefore chose not to interpret the time of day and only give you a LocalDate
.
If you always want a LocalDateTime
, you may also use parseDefaulting
:
DateTimeFormatter patternWithOptional = new DateTimeFormatterBuilder()
.appendPattern("M/d/yyyy[ H:mm]")
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.toFormatter();
System.out.println(LocalDateTime.parse("4/11/2020 1:20", patternWithOptional));
System.out.println(LocalDateTime.parse("4/11/2020", patternWithOptional));
Output:
2020-04-11T01:20
2020-04-11T00:00
On the other hand, if you do want either a LocalDate
or a LocalDateTime
depending on whether time of day is available in the string, you don’t need a DateTimeFormatterBuilder
:
DateTimeFormatter patternWithOptional = DateTimeFormatter.ofPattern("M/d/yyyy[ H:mm]");
Upvotes: 4