Простота SimpleDateFormat приводит к неожиданному поведению - PullRequest
0 голосов
/ 07 июня 2018

Я обнаружил, что поведение SimpleDateFormat::parse(String source) (к сожалению) по умолчанию установлено как снисходительное: setLenient(true).

По умолчаниюразбор снисходительный: если ввод не в форме, используемой методом форматирования этого объекта, но все еще может быть проанализирован как дата, тогда синтаксический анализ завершается успешно.

Если я установил снисхождение на falseВ документации сказано, что при строгом разборе входные данные должны соответствовать формату этого объекта.Я использовал paring с SimpleDateFormat без снисходительного режима и по ошибке у меня была опечатка в дате (буква o вместо числа 0).(Вот краткий рабочий код:)

// PASSED (year 199)
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.mm.yyyy");
System.out.println(simpleDateFormat.parse("03.12.199o"));
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("03.12.199o"));        //WTF?

К моему удивлению, это прошло, и ParseException не было брошено.Я бы пошел дальше:

// PASSED (year 1990)
String string = "just a String to mess with SimpleDateFormat";

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.mm.yyyy");
System.out.println(simpleDateFormat.parse("03.12.1990" + string));
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("03.12.1990" + string));

Давайте продолжим:

// FAILED on the 2nd line
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.mm.yyyy");
System.out.println(simpleDateFormat.parse("o3.12.1990"));
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("o3.12.1990"));

Наконец, исключение выдается: Unparseable date: "o3.12.1990".Интересно, где разница в снисхождении и почему последняя строка моего первого фрагмента кода не вызвала исключение?Документация гласит:

При строгом разборе входные данные должны соответствовать формату этого объекта.

Мой ввод явно не строго соответствует формату -Я ожидаю, что этот анализ будет очень строгим.Почему это (не) происходит?

Ответы [ 3 ]

0 голосов
/ 07 июня 2018

Если вы используете setLenient(false), он все равно будет анализировать дату, пока не будет достигнут желаемый шаблон.Тем не менее, он проверит, что выходная дата является действительной датой или нет.В вашем случае 03.12.199 является действительной датой, поэтому исключение не выдается.Давайте рассмотрим пример, чтобы понять, где setLenient(false) отличается от setLenient(true)/default.

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy"); 
System.out.println(simpleDateFormat.parse("31.02.2018"));

Вышеприведенное даст мне вывод: Sat Mar 03 00:00:00 IST 2018

Но приведенный ниже код создает ParseException, поскольку 31.02.2018 не является допустимой / возможной датой:

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy");
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("31.02.2018"));
0 голосов
/ 08 июня 2018

Почему это (не) происходит?

Это не очень хорошо объяснено в документации.

При мягком анализе синтаксический анализатор может использовать эвристикуинтерпретировать входные данные, которые точно не соответствуют формату этого объекта.При строгом разборе входные данные должны соответствовать формату этого объекта.

Документация немного помогает, хотя, упоминая, что это объект Calendar, который использует DateFormat, является мягким.Этот объект Calendar используется не для самого анализа, а для интерпретации проанализированных значений в дату и время (я цитирую документацию DateFormat, поскольку SimpleDateFormat является подклассом DateFormat).

  • SimpleDateFormat, независимо от того, снисходительно или нет, примет трехзначный год, например 199, даже если вы указали yyyy в строке шаблона формата.Документация говорит о годе:

    Для синтаксического анализа, если количество букв шаблона превышает 2, год интерпретируется буквально, независимо от количества цифр.Таким образом, используя шаблон «ММ / ДД / ГГГГ», «01/11/12» анализирует до 11 января 12 года нашей эры.

  • DateFormat, независимо от того, снисходительно илинет, принимает и игнорирует текст после проанализированного текста, как маленькая буква o в вашем первом примере.Он возражает против неожиданного текста до или внутри текста, как, например, когда в последнем примере вы поставили букву o впереди.Документация DateFormat.parse гласит:

    Метод не может использовать весь текст данной строки.

  • Как я косвенно сказал,снисходительность имеет значение при интерпретации проанализированных значений в дату и время.Таким образом, снисходительный SimpleDateFormat будет интерпретировать 29.02.2019 как 01.03.2019, потому что в феврале 2019 года только 28 дней. Строгий SimpleDateFormat откажется сделать это и выдаст исключение.Мягкое поведение по умолчанию может привести к очень неожиданным и совершенно необъяснимым результатам.В качестве простого примера, приведение дня, месяца и года в неправильном порядке: 1990.03.12 приведет к 11 августа 17 года нашей эры (2001 год назад).

Решение

VGR уже в комментарии упоминает LocalDate от java.time, современного Java-API даты и времени.По моему опыту java.time намного приятнее работать, чем старые классы даты и времени, поэтому давайте попробуем.Сначала попробуйте ввести правильную строку даты:

    DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.mm.yyyy");
    System.out.println(LocalDate.parse("03.12.1990", dateFormatter));

Получено:

java.time.format.DateTimeParseException: текст '03 .12.1990 'не может быть проанализирован: невозможно получитьLocalDate из TemporalAccessor: {Year = 1990, DayOfMonth = 3, MinuteOfHour = 12}, ISO типа java.time.format.Parsed

Это потому, что я использовал вашу строку шаблона формата dd.mm.yyyyгде строчные буквы mm означают минуты.Когда мы достаточно внимательно читаем сообщение об ошибке, оно говорит о том, что DateTimeFormatter интерпретировал 12 как минуту часа, что было не тем, что мы намеревались.Хотя SimpleDateFormat молчаливо принял это (даже если строго), java.time более полезно указать на нашу ошибку.В сообщении только косвенно говорится, что в нем отсутствует значение месяца.Нам нужно использовать прописные буквы MM для месяца.В то же время я пробую вашу строку даты с опечаткой:

    DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
    System.out.println(LocalDate.parse("03.12.199o", dateFormatter));

Мы получаем:

java.time.format.DateTimeParseException: Text '03 .12.199o 'могла быне может быть проанализирован с индексом 6

Индекс 6 - это то, где написано 199.Он возражает, потому что мы указали 4 цифры и поставляем только 3. Документы говорят:

Количество букв определяет минимальную ширину поля…

Это также будет возражатьчтобы разобрать текст после даты.Короче говоря мне кажется, что он дает вам все, что вы ожидали.

Ссылки

0 голосов
/ 07 июня 2018

Терпеливость не в том, совпадает ли вход весь , а в том, совпадает ли формат.Ваш ввод все еще может быть 3.12.1990somecrap, и он будет работать.

Фактический анализ выполняется в parse(String, ParsePosition), который вы также можете использовать.В основном parse(String) передаст ParsePosition, который настроен для запуска с индексом 0, и когда синтаксический анализ завершен, проверяется текущий индекс этой позиции.

Если значение равно 0, начало ввода не соответствует формату, даже в мягком режиме.

Однако для анализатора 03.12.199 является допустимой датой и, следовательно, останавливаетсяв индексе 8 - который не равен 0, и, следовательно, синтаксический анализ завершился успешно.Если вы хотите проверить, все ли было проанализировано, вам нужно будет передать свой собственный ParsePosition и проверить, совпадает ли индекс с длиной ввода.

...