Я использую SimpleDateFormat для синтаксического анализа строки к объектам Date, и мне интересно, почему результаты не такие, как я ожидал.

Например:

DateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd");

Date date = yyyyMMdd.parse("20100725");
System.out.println(date);

работает должным образом и выводит

Sun Jul 25 00:00:00 CEST 2010

Но

Date date = yyyyMMdd.parse("2010-07-25");
System.out.println(date);

Также работает и выводит

Mon Dec 07 00:00:00 CET 2009

Я ожидал ParseException, но похоже, что SimpleDateFormat интерпретирует часть месяца -07 и часть дня -25 как отрицательное число. Сначала я не мог понять, как это происходит до 7 декабря. Поэтому я попробовал другое значение:

Date date = yyyyMMdd.parse("2010-7-25");
System.out.println(date);

И он выводит

Sun Apr 05 00:00:00 CEST 2009

Таким образом, кажется, что он каким-то образом вычитает 7 месяц из года 2010, который был бы 1 мая, и 25 дней, поэтому результат - 5 апреля 2009 года.

Изображение, что вы используете шаблон yyyyMMdd в реализации службы, и какой-то клиент случайно отправляет дату как yyyy-MM-dd. Вы не получите исключения. Вместо этого вы получите совершенно другие даты. Я думаю, это не то, чего вы ожидаете.

Например.

String clientData = "2010-05-23";

DateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd");
Date parsedDate = yyyyMMdd.parse(clientData);

System.out.println("Client  : " + clientData);
System.out.println("Service : " + yyyyMMdd.format(parsedDate));

Я что-то упускаю?

Как мне предотвратить синтаксический анализ "неправильных" дат SimpleDateFormat?

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

1
René Link 5 Сен 2016 в 15:05

3 ответа

принятый ответ Каймана верен: проблема заключается в снисходительности при синтаксическом анализе по умолчанию.

Java.time

Вы используете неудобные старые классы даты и времени, которые теперь вытесняются java.time.

В java.time нет такой стандартной проблемы с снисходительностью. Если ввод не строго соответствует шаблону форматирования, DateTimeParseException выброшено.

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

Формат ISO 8601

Для стандартных входных данных в формате ISO 8601 ГГГГ-ММ-ДД просто вызовите parse прямо.

String input = "2010-05-23";
try {
    LocalDate  ld = LocalDate.parse( input ); // Expects standard ISO 8601 input format.
} catch ( DateTimeParseException e ) {
    …
}

«Базовый» формат ISO 8601

Стандарт ISO 8601 допускает «базовые» форматы, минимизирующие использование разделителей. Не то чтобы я рекомендовал эти варианты, но они существуют.

В настоящее время java.time предопределяет только один из этих «базовых» вариантов, DateTimeFormatter.BASIC_ISO_DATE.

String input = "20100725";
try {
    LocalDate  ld = LocalDate.parse( input , DateTimeFormatter.BASIC_ISO_DATE ); 
} catch ( DateTimeParseException e ) {
    …
}

Пользовательский формат

Для других форматов укажите средство форматирования.

String input = "2010/07/25";
try {
    DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu/MM/dd" );
    LocalDate  ld = LocalDate.parse( input , f ); // Custom format.
} catch ( DateTimeParseException e ) {
    …
}

Локализованный формат

Или пусть java.time определяет локализованный формат.

String input = … ;
try {
    Locale l = Locale.CANADA_FRENCH ; 
    DateTimeFormatter f = DateTimeFormatter.ofLocalizedDate( FormatStyle.MEDIUM ).withLocale( l );
    LocalDate  ld = LocalDate.parse( input , f ); // Localized format.
} catch ( DateTimeParseException e ) {
    …
}
2
Community 23 Май 2017 в 12:16

Во-первых, если вы хотите проанализировать строку «2010-05-23», ваша маска должна быть «yyyy-MM-dd», а не «yyyyMMdd». Второй SimpleDateFormat имеет серьезные проблемы, поскольку он не является потокобезопасным. Если вы используете java 8, воспользуйтесь обучением и используйте новый пакет «java.time». Если вы используете Java более ранней версии, чем версия 8, тогда используйте другие фреймворки для анализа даты. Одним из самых популярных является время Йода. Работает намного лучше.

0
Michael Gantman 5 Сен 2016 в 12:12
SimpleDateFormat.setLenient(false);

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

2
Arnav Borborah 5 Сен 2016 в 12:14