it-roy-ru.com

Как правильно проверить дату в Java

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

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

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

61
Bloodboiler

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

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

Calendar cal = Calendar.getInstance();
cal.setLenient(false);
cal.setTime(yourDate);
try {
    cal.getTime();
}
catch (Exception e) {
  System.out.println("Invalid date");
}
27
AdamC

Ключ 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;
        }
}
64
Aravind R. Yarram

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

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

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

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

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

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

  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. Пока не могу найти способ применить 4-значные годы (но я думаю, вы могли бы создать свой собственный 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;
}
45
tardate

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

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

boolean isLegalDate(String s) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    sdf.setLenient(false);
    return sdf.parse(s, new ParsePosition(0)) != null;
}
36
Maglob

Java.time

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

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;
}
12
Matthias Braun

тЛ; др

Используйте строгий режим on Java.time.DateTimeFormatter для разбора a 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 enum. Мы вставляем строку в приведенный выше код, чтобы попробовать каждый из режимов.

f = f.withResolverStyle ( ResolverStyle.LENIENT );

Результаты, достижения:

  • ResolverStyle.LENIENT
    лд: 2000-03-02
  • ResolverStyle.SMART
    лд: 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 framework встроен в 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? 

  • Java SE 8 , Java SE 9 и более поздние версии
    • Встроенный. 
    • Часть стандартного Java API со встроенной реализацией.
    • Java 9 добавляет некоторые незначительные функции и исправления.
  • Java SE 6 и Java SE 7
    • Большая часть функциональности Java.time перенесена в Java 6 & 7 в ThreeTen-Backport .
  • Android
    • Более поздние версии Android связывают реализации классов Java.time.
    • Для более ранних версий Android (<26) ThreeTenABP project адаптируется ThreeTen-Backport (упоминалось выше). Смотрите Как использовать ThreeTenABP… .

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

11
Basil Bourque

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

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

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

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

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

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

6
Ben

Основываясь на ответе @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
Sufian

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

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

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

4
Ziya

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

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;
}
3
mawa

Предполагая, что оба они являются строками (в противном случае они уже будут действительными датами), вот один из способов:

package cruft;

import Java.text.DateFormat;
import Java.text.ParseException;
import Java.text.SimpleDateFormat;
import Java.util.Date;

public class DateValidator
{
    private static final DateFormat DEFAULT_FORMATTER;

    static
    {
        DEFAULT_FORMATTER = new SimpleDateFormat("dd-MM-yyyy");
        DEFAULT_FORMATTER.setLenient(false);
    }

    public static void main(String[] args)
    {
        for (String dateString : args)
        {
            try
            {
                System.out.println("arg: " + dateString + " date: " + convertDateString(dateString));
            }
            catch (ParseException e)
            {
                System.out.println("could not parse " + dateString);
            }
        }
    }

    public static Date convertDateString(String dateString) throws ParseException
    {
        return DEFAULT_FORMATTER.parse(dateString);
    }
}

Вот вывод, который я получаю:

Java cruft.DateValidator 32-11-2010 31-02-2010 04-01-2011
could not parse 32-11-2010
could not parse 31-02-2010
arg: 04-01-2011 date: Tue Jan 04 00:00:00 EST 2011

Process finished with exit code 0

Как видите, он прекрасно справляется с обоими вашими делами.

2
duffymo

Это прекрасно работает для меня. Подход, предложенный выше Беном. 

private static boolean isDateValid(String s) {
    SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
    try {
        Date d = asDate(s);
        if (sdf.format(d).equals(s)) {
            return true;
        } else {
            return false;
        }
    } catch (ParseException e) {
        return false;
    }
}
2
Dresden Sparrow

Два комментария по использованию SimpleDateFormat.

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

IME, который лучше, чем создание экземпляра для каждого анализа даты.

1
Tom

Вышеуказанные методы синтаксического анализа даты хороши, я просто добавил новую проверку в существующие методы, которые дважды проверяют конвертированную дату с оригинальной датой, используя formater, так что это работает почти для каждого случая, как я проверял. например 29.02.2013 является недействительной датой . Данная функция разбирает дату в соответствии с текущими допустимыми форматами даты. Возвращает true, если дата не была успешно проанализирована.

 public final boolean validateDateFormat(final String date) {
        String[] formatStrings = {"MM/dd/yyyy"};
        boolean isInvalidFormat = false;
        Date dateObj;
        for (String formatString : formatStrings) {
            try {
                SimpleDateFormat sdf = (SimpleDateFormat) DateFormat.getDateInstance();
                sdf.applyPattern(formatString);
                sdf.setLenient(false);
                dateObj = sdf.parse(date);
                System.out.println(dateObj);
                if (date.equals(sdf.format(dateObj))) {
                    isInvalidFormat = false;
                    break;
                }
            } catch (ParseException e) {
                isInvalidFormat = true;
            }
        }
        return isInvalidFormat;
    }
0
Imran

Вот я бы проверил формат даты:

 public static boolean checkFormat(String dateTimeString) {
    return dateTimeString.matches("^\\d{4}-\\d{2}-\\d{2}") || dateTimeString.matches("^\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}")
            || dateTimeString.matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}") || dateTimeString
            .matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z") ||
            dateTimeString.matches("^\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}Z");
}
0
Dinesh Bhatia

Вот что я сделал для среды Node без внешних библиотек:

Date.prototype.yyyymmdd = function() {
   var yyyy = this.getFullYear().toString();
   var mm = (this.getMonth()+1).toString(); // getMonth() is zero-based
   var dd  = this.getDate().toString();
   return zeroPad([yyyy, mm, dd].join('-'));  
};

function zeroPad(date_string) {
   var dt = date_string.split('-');
   return dt[0] + '-' + (dt[1][1]?dt[1]:"0"+dt[1][0]) + '-' + (dt[2][1]?dt[2]:"0"+dt[2][0]);
}

function isDateCorrect(in_string) {
   if (!matchesDatePattern) return false;
   in_string = zeroPad(in_string);
   try {
      var idate = new Date(in_string);
      var out_string = idate.yyyymmdd();
      return in_string == out_string;
   } catch(err) {
      return false;
   }

   function matchesDatePattern(date_string) {
      var dateFormat = /[0-9]+-[0-9]+-[0-9]+/;
      return dateFormat.test(date_string); 
   }
}

А вот как это использовать:

isDateCorrect('2014-02-23')
true
0
TennisVisuals
// to return valid days of month, according to month and year
int returnDaysofMonth(int month, int year) {
    int daysInMonth;
    boolean leapYear;
    leapYear = checkLeap(year);
    if (month == 4 || month == 6 || month == 9 || month == 11)
        daysInMonth = 30;
    else if (month == 2)
        daysInMonth = (leapYear) ? 29 : 28;
    else
        daysInMonth = 31;
    return daysInMonth;
}

// to check a year is leap or not
private boolean checkLeap(int year) {
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.YEAR, year);
    return cal.getActualMaximum(Calendar.DAY_OF_YEAR) > 365;
}
0
ashishdhiman2007