Использование нового Java 8 DateTimeFormatter для строгого синтаксического анализа даты

У меня есть простая проблема: я хочу строго разбирать строки Java в формате "yyyyMMdd", поэтому "19800229" является допустимой датой, но "19820229" - нет. Предположим, что это даты AD из нормального григорианского календаря.

Я пытаюсь использовать новый пакет java.time от JDK 8 для решения этой проблемы, но он оказывается более сложным, чем можно надеяться. Мой текущий код:

private static final DateTimeFormatter FORMAT = DateTimeFormatter
        .ofPattern("yyyyMMdd").withChronology(IsoChronology.INSTANCE)
        .withResolverStyle(STRICT);

public static LocalDate parse(String yyyyMMdd) {
    return LocalDate.parse(yyyyMMdd, FORMAT);
}

Однако разбор допустимой даты, такой как "19800228", создает то, что для меня является непонятной ошибкой:

java.time.format.DateTimeParseException: текст '19820228' не может быть проанализирован: невозможно получить LocalDate из TemporalAccessor: {MonthOfYear = 2, DayOfMonth = 28, YearOfEra = 1982}, ISO типа java.time.format. Проанализированные

Как использовать java.time.format.DateTimeFormatter для решения моего простого варианта использования?

Ответ 1

Я редактирую, чтобы ограничить, какая строка будет считаться действительной, используя собственный форматировщик, созданный с помощью DateTimeFormatterBuilder.

public class DateFormmaterTest {

    static DateTimeFormatter CUSTOM_BASIC_ISO_DATE = new DateTimeFormatterBuilder()
            .parseCaseInsensitive().appendValue(YEAR, 4)
            .appendValue(MONTH_OF_YEAR, 2).appendValue(DAY_OF_MONTH, 2)
            .optionalStart().toFormatter()
            .withResolverStyle(ResolverStyle.STRICT)
            .withChronology(IsoChronology.INSTANCE);

    public static void main(String[] args) {

        LocalDate date1 = LocalDate.parse("19800228-5000",
                CUSTOM_BASIC_ISO_DATE);

        System.out.println(date1);

    }
}

2/29/1982 недействителен и будет вызывать следующее:

Caused by: java.time.DateTimeException: Invalid date 'February 29' as '1982' is not a leap year
    at java.time.LocalDate.create(LocalDate.java:429)

Дата 19800228-5000 будет работать с BASIC_ISO_DATE, потому что она позволяет дополнительное смещение, которое вы не хотите разрешать. Мой формат CUSTOM_BASIC_ISO_DATE не позволяет этого и выдает следующее:

Exception in thread "main" java.time.format.DateTimeParseException: Text '19800228-5000' could not be parsed, unparsed text found at index 8. 

Обратите внимание, что если вы уверены в длине строки, yyyyMMdd, тогда вы всегда можете работать с подстрокой первых 8 символов, чтобы отрицать необходимость в распознавателе. Однако это две разные вещи. Резольвер будет отмечать недопустимые форматы даты на входе, и подстрока, конечно, просто разделит лишние символы.

Ответ 2

В Java 8 используется uuuu год, а не yyyy. В Java 8 yyyy означает "год эры" (BC или AD), и сообщение об ошибке жалуется, что MonthOfYear, DayOfMonth и YearOfEra недостаточно информации для создания даты, потому что эпоха неизвестна.

Чтобы исправить это, используйте uuuu в строке формата, например. DateTimeFormatter.ofPattern("uuuuMMdd")

Или, если вы хотите продолжать использовать yyyy, вы можете установить эру по умолчанию, например.

new DateTimeFormatterBuilder()
        .appendPattern("yyyyMMdd")
        .parseDefaulting(ChronoField.ERA, 1 /* era is AD */)
        .toFormatter()

Ответ 3

Попробуйте использовать формат "uuuuMMdd".