Как вменяемость проверить дату в Java


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

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

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

16 57

16 ответов:

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

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

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

ключ 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;
        }
}

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

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

недопустимые символы в строке даты Удивительно, но 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();

Время Джоды-Лучшая Альтернатива?

Я слышал о Джода времени недавно и подумал, что я бы сравнил. Два момента:

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

можно использовать SimpleDateFormat

что-то вроде:
boolean isLegalDate(String s) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    sdf.setLenient(false);
    return sdf.parse(s, new ParsePosition(0)) != null;
}

java.время

С дата и время API ( java.время классы) встроенный в 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;
}

tl; dr

использовать строгого режима on 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.время классы.

LocalDate & DateTimeFormatter & ResolverStyle

The 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 );
}

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

f = f.withResolverStyle ( ResolverStyle.LENIENT );

результаты:

  • ResolverStyle.LENIENT
    ld: 2000-03-02
  • ResolverStyle.SMART
    ld: 2000-02-29
  • ResolverStyle.STRICT
    ошибка: java.время.формат.DateTimeParseException: текст '31/02/2000' не удалось разобрать: недопустимая дата '31 февраля'

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

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


о java.время

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

The Joda Времени, теперь режим обслуживания, советует миграция в java.время классы.

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

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

где получить java.время занятий?

  • 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 bundle реализации java.время занятий.
    • для более ранних Android ( ThreeTenABP адаптируется ThreeTen-Backport (упоминалось выше). Смотрите как использовать ThreeTenABP....

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

основываясь на ответе @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+");
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

это отлично работает для меня. Подход, предложенный выше Беном.

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

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

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

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

выше методы разбора даты хороши, я просто добавил новые проверки существующих методов, которые дважды проверяют преобразованную дату с исходной датой с помощью форматера, поэтому он работает почти для каждого случая, как я проверил. например, 02/29/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;
    }

вот что я сделал для среды узла, не используя внешних библиотек:

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
// 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;
}