Как правильно проверить дату в Java - PullRequest
67 голосов
/ 22 октября 2008

Мне кажется любопытным, что наиболее очевидный способ создания Date объектов в Java устарел и, по-видимому, был "заменен" не столь очевидным для использования снисходительным календарем.

Как проверить, что дата, представленная в виде комбинации дня, месяца и года, является действительной датой?

Например, 2008-02-31 (как в гггг-мм-дд) будет недействительной датой.

Ответы [ 17 ]

69 голосов
/ 24 декабря 2010

Ключ df.setLenient (false); . Этого более чем достаточно для простых случаев. Если вы ищете более надежные (я сомневаюсь) и / или альтернативные библиотеки, такие как joda-time, посмотрите на ответ пользователя "tardate"

final static String DATE_FORMAT = "dd-MM-yyyy";

public static boolean isDateValid(String date) 
{
        try {
            DateFormat df = new SimpleDateFormat(DATE_FORMAT);
            df.setLenient(false);
            df.parse(date);
            return true;
        } catch (ParseException e) {
            return false;
        }
}
45 голосов
/ 21 мая 2009

Как показывает @Maglob, основной подход заключается в тестировании преобразования из строки в дату с использованием SimpleDateFormat.parse . Это будет ловить недопустимые комбинации день / месяц, такие как 2008-02-31.

Однако на практике этого достаточно редко, поскольку SimpleDateFormat.parse чрезвычайно либерален. Вы можете быть обеспокоены двумя видами поведения:

Недопустимые символы в строке даты Удивительно, но 2008-02-2x будет «проходить» как действительная дата, например, с форматом локали = «гггг-мм-дд». Даже когда isLenient == false.

Годы: 2, 3 или 4 цифры? Возможно, вы также захотите ввести 4-значные годы вместо того, чтобы разрешить поведение SimpleDateFormat по умолчанию (которое будет интерпретировать «12-02-31» по-разному в зависимости от того, был ли у вас формат «yyyy-MM-dd» или «yy-MM-dd») )

Строгое решение со стандартной библиотекой

Таким образом, полная проверка строки на дату может выглядеть следующим образом: сочетание соответствия регулярному выражению, а затем принудительное преобразование даты. Уловка с регулярным выражением состоит в том, чтобы сделать его дружественным к локали.

  Date parseDate(String maybeDate, String format, boolean lenient) {
    Date date = null;

    // test date string matches format structure using regex
    // - weed out illegal characters and enforce 4-digit year
    // - create the regex based on the local format string
    String reFormat = Pattern.compile("d+|M+").matcher(Matcher.quoteReplacement(format)).replaceAll("\\\\d{1,2}");
    reFormat = Pattern.compile("y+").matcher(reFormat).replaceAll("\\\\d{4}");
    if ( Pattern.compile(reFormat).matcher(maybeDate).matches() ) {

      // date string matches format structure, 
      // - now test it can be converted to a valid date
      SimpleDateFormat sdf = (SimpleDateFormat)DateFormat.getDateInstance();
      sdf.applyPattern(format);
      sdf.setLenient(lenient);
      try { date = sdf.parse(maybeDate); } catch (ParseException e) { }
    } 
    return date;
  } 

  // used like this:
  Date date = parseDate( "21/5/2009", "d/M/yyyy", false);

Обратите внимание, что регулярное выражение предполагает, что строка формата содержит только день, месяц, год и символы разделителя. Кроме того, формат может быть в любом формате локали: "d / MM / yy", "yyyy-MM-dd" и так далее. Строка формата для текущей локали может быть получена следующим образом:

Locale locale = Locale.getDefault();
SimpleDateFormat sdf = (SimpleDateFormat)DateFormat.getDateInstance(DateFormat.SHORT, locale );
String format = sdf.toPattern();

Joda Time - лучшая альтернатива?

Недавно я слышал о времени йода и подумал, что сравню. Две точки:

  1. Лучше быть строгим в отношении недопустимых символов в строке даты, в отличие от SimpleDateFormat
  2. Пока не могу найти способ применения четырехзначных лет (но я думаю, вы могли бы создать для этой цели свой собственный DateTimeFormatter )

Его довольно просто использовать:

import org.joda.time.format.*;
import org.joda.time.DateTime;

org.joda.time.DateTime parseDate(String maybeDate, String format) {
  org.joda.time.DateTime date = null;
  try {
    DateTimeFormatter fmt = DateTimeFormat.forPattern(format);
    date =  fmt.parseDateTime(maybeDate);
  } catch (Exception e) { }
  return date;
}
36 голосов
/ 22 октября 2008

Вы можете использовать SimpleDateFormat

Например, что-то вроде:

boolean isLegalDate(String s) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    sdf.setLenient(false);
    return sdf.parse(s, new ParsePosition(0)) != null;
}
29 голосов
/ 22 октября 2008

Текущий способ - использовать класс календаря. У него есть метод setLenient , который будет проверять дату, исключение и исключение, если он выходит за пределы диапазона, как в вашем примере.

Забыл добавить: Если вы получаете экземпляр календаря и устанавливаете время, используя свою дату, вы получаете подтверждение.

Calendar cal = Calendar.getInstance();
cal.setLenient(false);
cal.setTime(yourDate);
try {
    cal.getTime();
}
catch (Exception e) {
  System.out.println("Invalid date");
}
14 голосов
/ 23 сентября 2016

ТЛ; др

Используйте строгий режим на java.time.DateTimeFormatter для анализа LocalDate. Ловушка для DateTimeParseException.

LocalDate.parse(                   // Represent a date-only value, without time-of-day and without time zone.
    "31/02/2000" ,                 // Input string.
    DateTimeFormatter              // Define a formatting pattern to match your input string.
    .ofPattern ( "dd/MM/uuuu" )
    .withResolverStyle ( ResolverStyle.STRICT )  // Specify leniency in tolerating questionable inputs.
)

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

birthDate.isAfter( LocalDate.now().minusYears( 100 ) )

Избегайте устаревших классов даты и времени

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

LocalDate & DateTimeFormatter & ResolverStyle

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

String input = "31/02/2000";
DateTimeFormatter f = DateTimeFormatter.ofPattern ( "dd/MM/uuuu" );
try {
    LocalDate ld = LocalDate.parse ( input , f );
    System.out.println ( "ld: " + ld );
} catch ( DateTimeParseException e ) {
    System.out.println ( "ERROR: " + e );
}

Класс java.time.DateTimeFormatter может быть настроен на анализ строк с любым из трех режимов снисхождения, определенных в перечислении ResolverStyle. Мы вставляем строку в приведенный выше код, чтобы попробовать каждый из режимов.

f = f.withResolverStyle ( ResolverStyle.LENIENT );

Результаты:

  • ResolverStyle.LENIENT
    ld: 2000-03-02
  • ResolverStyle.SMART
    ld: 2000-02-29
  • ResolverStyle.STRICT
    ОШИБКА: java.time.format.DateTimeParseException: текст '31/02/2000' не может быть проанализирован: неверная дата '31 ФЕВРАЛЯ'

Мы видим, что в режиме ResolverStyle.LENIENT недействительная дата перемещается на эквивалентное количество дней вперед. В режиме ResolverStyle.SMART (по умолчанию) принимается логическое решение сохранить дату в месяце и перейти к последнему возможному дню месяца, 29 февраля, в високосный год, поскольку нет 31-й день в этом месяце. Режим ResolverStyle.STRICT вызывает исключение, сообщающее, что такой даты нет.

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


О java.time

Фреймворк java.time встроен в Java 8 и более поздние версии. Эти классы заменяют проблемные старые устаревшие классы даты и времени, такие как java.util.Date, Calendar, & SimpleDateFormat.

Проект Joda-Time , теперь в режиме обслуживания , рекомендует перейти на классы java.time .

Чтобы узнать больше, см. Oracle Tutorial . И поиск переполнения стека для многих примеров и объяснений. Спецификация JSR 310 .

Вы можете обмениваться java.time объектами напрямую с вашей базой данных. Используйте драйвер JDBC , совместимый с JDBC 4.2 или более поздней версии. Нет необходимости в строках, нет необходимости в java.sql.* классах.

Где получить классы java.time?

Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является полигоном для возможных будущих дополнений к java.time. Здесь вы можете найти несколько полезных классов, таких как Interval, YearWeek, YearQuarter и еще .

12 голосов
/ 03 мая 2014

java.time

Используя API даты и времени ( java.time классы), встроенные в Java 8 и более поздние версии, вы можете использовать класс LocalDate.

public static boolean isDateValid(int year, int month, int day) {
    boolean dateIsValid = true;
    try {
        LocalDate.of(year, month, day);
    } catch (DateTimeException e) {
        dateIsValid = false;
    }
    return dateIsValid;
}
6 голосов
/ 29 мая 2015

Опираясь на ответ @ Pangea , чтобы исправить проблему, указанную @ ceklock , я добавил метод, чтобы убедиться, что dateString не содержит недопустимых символов.

Вот как я это делаю:

private boolean isDateCorrect(String dateString) {
    try {
        Date date = mDateFormatter.parse(dateString);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return matchesOurDatePattern(dateString);    //added my method
    }
    catch (ParseException e) {
        return false;
    }
}

/**
 * This will check if the provided string matches our date format
 * @param dateString
 * @return true if the passed string matches format 2014-1-15 (YYYY-MM-dd)
 */
private boolean matchesDatePattern(String dateString) {
    return dateString.matches("^\\d+\\-\\d+\\-\\d+");
}
6 голосов
/ 12 апреля 2010

Альтернативное строгое решение с использованием стандартной библиотеки - выполнить следующее:

1) Создайте строгий SimpleDateFormat, используя ваш шаблон

2) Попытка анализа введенного пользователем значения с использованием объекта формата

3) В случае успеха переформатируйте дату, полученную из (2), используя тот же формат даты (из (1))

4) Сравните переформатированную дату с исходным введенным пользователем значением. Если они равны, то введенное значение строго соответствует вашему шаблону.

Таким образом, вам не нужно создавать сложные регулярные выражения - в моем случае мне нужно было поддерживать весь синтаксис шаблона SimpleDateFormat, а не ограничиваться определенными типами, такими как дни, месяцы и годы.

4 голосов
/ 18 июля 2013

Предлагаю вам использовать org.apache.commons.validator.GenericValidator класс из apache.

GenericValidator.isDate(String value, String datePattern, boolean strict);

Примечание: строгое - иметь или нет точное соответствие datePattern.

3 голосов
/ 04 февраля 2016

Я думаю, что самое простое - просто преобразовать строку в объект даты и преобразовать ее обратно в строку. Данная строка даты подходит, если обе строки совпадают.

public boolean isDateValid(String dateString, String pattern)
{   
    try
    {
        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
        if (sdf.format(sdf.parse(dateString)).equals(dateString))
            return true;
    }
    catch (ParseException pe) {}

    return false;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...