Вопрос Java 8 DateTimeFormatter с необязательной частью уже существует, но ответ на него не сработает, если необязательная часть составляет только часы и минуты без секунд:
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
Это потому, что LocalDateTime.from(TemporalAccessor temporal) использует TemporalQueries.LOCAL_TIME для запроса temporal. TemporalQueries.LOCAL_TIME, в свою очередь, требует, чтобы ChronoField.NANO_OF_DAY был доступен из temporal.
System.out.println(patternWithOptional.parse("4/11/2020 1:20"));
// prints:
{HourOfAmPm=1, MinuteOfHour=20},ISO resolved to 2020-04-11
Поскольку доступны HourOfAmPm и MinuteOfHour, самый простой способ добиться того, что мне нужно, я мог придумать:
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.
);
}
Есть ли более простой способ сделать это без явных проверок isSupported?
Очень хорошо проработанный вопрос, одно удовольствие.




В строке шаблона формата вам нужно использовать заглавные буквы H для часа дня:
.appendPattern("M/d/yyyy[ H:mm]")
С этим изменением ваши первые отпечатки сниппета
2020-04-11T01:20
LocalDateTime
h в нижнем регистре означает час в пределах AM или PM. Таким образом, ваше время могло означать 01:20 или 13:20, то есть 13:20. Java не могла решить и поэтому предпочла не интерпретировать время дня, а только выдать вам LocalDate.
Если вам всегда нужен LocalDateTime, вы также можете использовать 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));
Выход:
2020-04-11T01:20
2020-04-11T00:00
С другой стороны, если вам делать нужен либо LocalDate, либо LocalDateTime, в зависимости от того, доступно ли в строке время дня, вам не нужен DateTimeFormatterBuilder:
DateTimeFormatter patternWithOptional = DateTimeFormatter.ofPattern("M/d/yyyy[ H:mm]");
О, HourOfAmPm на выходе должен был меня предупредить. У меня была склонность попробовать разобрать что-то> 12, но я не довел до конца. H - это действительно то, что мне нужно в моем случае, но parseDefaulting в билдере - это именно то, о чем я задал.
Чтобы уточнить: мне нужно было исправить шаблон и добавить значения по умолчанию.
Это действительно дубликат Разница между java HH: mm и hh: mm в SimpleDateFormat и Сравнение времени некорректно при выборе 12:00., хотя это может быть немного сложно увидеть?