Мне кажется любопытным, что наиболее очевидный способ создания объектов Date в Java устарел и, похоже, был «заменен» не столь очевидным для использования мягким календарем.
Как вы проверяете, что дата, заданная как комбинация дня, месяца и года, является действительной датой?
Например, 31 февраля 2008 г. (как в формате гггг-мм-дд) будет недопустимой датой.
@JasonC Я полностью согласен с вами, их объединение выглядит лучшим способом.




Текущий способ - использовать класс календаря. У него есть метод setLenient, который будет проверять дату и генерировать исключение, если он выходит за пределы диапазона, как в вашем примере.
Забыл добавить: Если вы получаете экземпляр календаря и устанавливаете время, используя вашу дату, это то, как вы получаете проверку.
Calendar cal = Calendar.getInstance();
cal.setLenient(false);
cal.setTime(yourDate);
try {
cal.getTime();
}
catch (Exception e) {
System.out.println("Invalid date");
}
не думаю, что это работает как есть. Calendar.setTime принимает java.util.Date, поэтому преобразование из строки уже произошло к тому моменту, когда вы получите объект yourDate.
3 проблемы с примером кода: 1. После получения экземпляра Calendar вы должны вызвать cal.setLenient (false). В противном случае дата, подобная 31 февраля 2007 г., будет считаться действительной. 2. Cal.setTime () не генерирует исключение. Вы должны вызвать cal.getTime () после вызова setTime (), который вызывает исключение в недопустимую дату. 3. Опечатка: отсутствует символ "}" перед уловкой.
Это также медленнее, чем другие подходы. См. stackoverflow.com/questions/2149680/…
К вашему сведению, неприятные старые классы даты и времени, такие как java.util.Date, java.util.Calendar и java.text.SimpleDateFormat, теперь являются наследие, вытесненными классами java.time, встроенными в Java 8 и новее. См. Руководство от Oracle.
не проверяет такие даты, как 31 февраля
Вы можете использовать SimpleDateFormat
Например что-то вроде:
boolean isLegalDate(String s) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setLenient(false);
return sdf.parse(s, new ParsePosition(0)) != null;
}
Одна из проблем с этим подходом заключается в том, что он будет принимать 0003-0002-001.
Другая проблема заключается в том, что установка 13 в месяце возвращает дату, в которой месяц равен 01 в следующем году.
К вашему сведению, ужасно неприятные старые классы даты и времени, такие как java.util.Date, java.util.Calendar и java.text.SimpleDateFormat, теперь являются наследие, вытесненными классами java.time, встроенными в Java 8 и новее. См. Руководство от Oracle.
Как показывает @Maglob, основной подход состоит в том, чтобы протестировать преобразование строки в дату с помощью SimpleDateFormat.parse. Это будет ловить недопустимые комбинации дня / месяца, например, 2008-02-31.
Однако на практике этого бывает достаточно редко, поскольку SimpleDateFormat.parse чрезвычайно либерален. Есть два варианта поведения, которые могут вас заинтересовать:
Недействительные символы в строке даты Удивительно, но 2008-02-2x будет "проходить" как допустимую дату с locale format = "yyyy-MM-dd", например. Даже когда isLenient == false.
Годы: 2, 3 или 4 цифры? Вы также можете использовать 4-значные годы вместо того, чтобы разрешать поведение SimpleDateFormat по умолчанию (которое будет интерпретировать «12-02-31» по-разному в зависимости от того, был ли ваш формат «гггг-ММ-дд» или «гг-ММ-дд». )
Таким образом, полная проверка строки на дату может выглядеть так: комбинация совпадения регулярного выражения и последующего принудительного преобразования даты. Хитрость с регулярным выражением заключается в том, чтобы сделать его адаптированным к локали.
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);
Обратите внимание, что регулярное выражение предполагает, что строка формата содержит только символы дня, месяца, года и разделителя. Кроме того, формат может быть в любом формате локали: «д / ММ / гг», «гггг-ММ-дд» и так далее. Строку формата для текущей локали можно получить следующим образом:
Locale locale = Locale.getDefault();
SimpleDateFormat sdf = (SimpleDateFormat)DateFormat.getDateInstance(DateFormat.SHORT, locale );
String format = sdf.toPattern();
Я недавно слышал о время йода и подумал, что сравню. Два момента:
Использовать довольно просто:
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;
}
Я закончил с альтернативой joda и сам проверил, что значение соответствует длине шаблона ...
Обновление: ужасные старые устаревшие классы (Date, SimpleDateFormat и т. д.) Теперь заменены современными классами java.time. Точно так же проект Джода-Тайм находится в режиме обслуживания и рекомендует переход на классы java.time.
Два комментария по использованию SimpleDateFormat.
it should be declared as a static instance if declared as static access should be synchronized as it is not thread safe
TIME, что лучше, чем создание экземпляра для каждого синтаксического анализа даты.
хорошее замечание Том. Я обновил приведенный мной пример, чтобы постоянно использовать статический экземпляр.
Использование синхронизированной функции может не масштабироваться для некоторых проектов. Я бы рекомендовал поместить SimpleDateFormat в переменную ThreadLocal вместо статической переменной, доступ к которой осуществляется через синхронизированную функцию.
Альтернативное строгое решение с использованием стандартной библиотеки - выполнить следующее:
1) Создайте строгий SimpleDateFormat, используя свой шаблон
2) Попытка проанализировать введенное пользователем значение с помощью объекта формата
3) В случае успеха переформатируйте дату, полученную из (2), используя тот же формат даты (из (1)).
4) Сравните переформатированную дату с исходным значением, введенным пользователем. Если они равны, то введенное значение строго соответствует вашему шаблону.
Таким образом, вам не нужно создавать сложные регулярные выражения - в моем случае мне нужно было поддерживать весь синтаксис шаблона SimpleDateFormat, а не ограничиваться определенными типами, такими как дни, месяцы и годы.
Это определенно правильный путь, см. Также пример кода на dreamincode.net/forums/topic/…
Предполагая, что оба они являются строками (иначе они уже были бы действительными датами), вот один способ:
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
Как видите, он отлично справляется с обоими вашими случаями.
@duffymo второй оператор не генерирует исключение ... :(
@Pangea Не совсем .... попробуйте эту дату "31-01-2010", это не вызовет никаких исключений .......... Просто запустите его на своей машине и посмотрите ... это не так Работа....
Виноват. Я удалил свой комментарий. Но второе утверждение вызывает для меня исключение. @duffymo - какой jvm вы используете?
@Pangea, я использую jdk1.6.0_23 Я пробовал это с этим, даже joda-time.sourceforge.net, но даже это не сработало ....
Что не сработало? Попробуйте выполнить sys out of date 2 и разместить здесь то, что печатается. Пробовал с 1.6.0_20-b02.
Исключение в потоке «main» java.text.ParseException: неразборчивая дата: «31-02-2010» в java.text.DateFormat.parse (DateFormat.java:337)
@maximus: это потому, что первый сгенерировал исключение, и, следовательно, остаток кода не будет выполнен. @sasidhar: это потому, что 31 января - допустимая дата. @duffymo: подумайте о том, чтобы поместить каждого в try-catch, чтобы оба были выполнены;)
Я понимаю, что это всего лишь простой тест, но чтобы просто убедиться, что люди не копируют это дословно, я хочу сказать, что «DateFormat» НЕ БЕЗОПАСЕН ДЛЯ ПОТОКОВ. Поэтому всегда создавайте его как локальную переменную или используйте с локальным потоком.
@Pangea только что выяснила, что 31 февраля не генерирует исключение, если мы используем формат даты как «дд-мм-гггг», это должно быть «дд-мм-гггг», это была мелочь, которая беспокоила меня в течение нескольких дней. , только что заметил, что я использую маленькую букву в течение месяца, в любом случае спасибо. Есть идеи, что означает маленькая м?
маленький m используется для обозначения минут. кажется, SimpleDateFormat более снисходителен, чем хотелось бы ;-). Посмотрите здесь поддерживаемые символы и их интерпретации: mboshart.dyndns.org/boshart/documentation/APIdocs/api/java/t ext /…
Ключ - df.setLenient (ложь);. Для простых случаев этого более чем достаточно. Если вы ищете более надежные (я сомневаюсь) и / или альтернативные библиотеки, такие как 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;
}
}
Попробуйте с «09-04-201а». Это создаст безумное свидание.
@ceklock, как это работает, независимо от того, используете ли вы setLenient или нет: SimpleDateFormat всегда будет анализировать, пока шаблон не будет сопоставлен, и игнорирует остальную часть строки, таким образом, вы получаете 201 как год.
@ceklock Я только что обратился к нему в мое решение. Это может сэкономить пару минут для кого-то.
Включение обработки исключений влечет за собой большой удар производительности, поэтому это, вероятно, плохой дизайн, ЕСЛИ вы ожидаете неправильного ввода при нормальной работе (например, при проверке пользовательского ввода). Но если метод используется для двойной проверки входных данных, которые должны быть действительными все время (за исключением ошибок), это нормально.
К вашему сведению, ужасно неприятные старые классы даты и времени, такие как java.util.Date, java.util.Calendar и java.text.SimpleDateFormat, теперь являются наследие, вытесненными классами java.time, встроенными в Java 8 и новее. См. Руководство от Oracle.
Вышеупомянутые методы синтаксического анализа даты хороши, я просто добавил новую проверку в существующие методы, которые дважды проверяют преобразованную дату с исходной датой с помощью средства форматирования, поэтому он работает почти для каждого случая, как я проверял. например 29.02.2013 неверная дата. Данная функция анализирует дату в соответствии с текущими допустимыми форматами даты. Он возвращает истину, если дата не была успешно проанализирована.
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;
}
Предлагаю вам использовать класс org.apache.commons.validator.GenericValidator от apache.
GenericValidator.isDate(String value, String datePattern, boolean strict);
Примечание: strict - должно ли быть точное совпадение datePattern.
Это отлично работает для меня. Подход, предложенный выше Беном.
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;
}
}
не могу распознать asDate. Это что?
Благодаря классам Дата и время 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;
}
По умолчанию в этом коде используется ResolverStyle.SMART, который настраивает полученное значение на допустимую дату, а не генерирует исключение. Таким образом, этот код не достигнет цели Вопроса. См., Например, мой ответ и решение с использованием ResolverStyle.STRICT.
Основываясь на Ответ Аравинда, чтобы исправить проблему, указанную 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+");
}
Я думаю, что проще всего просто преобразовать строку в объект даты и преобразовать ее обратно в строку. Данная строка даты подходит, если обе строки все еще совпадают.
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;
}
Вот что я сделал для среды 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
Используйте строгий режим на 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.LENIENTResolverStyle.SMARTResolverStyle.STRICTМы видим, что в режиме ResolverStyle.LENIENT недопустимая дата перемещается вперед на эквивалентное количество дней. В режиме ResolverStyle.SMART (по умолчанию) принимается логическое решение сохранить дату в пределах месяца и использовать последний возможный день месяца, 29 февраля високосного года, поскольку в этом месяце нет 31-го дня. Режим ResolverStyle.STRICT выдает исключение с жалобой на отсутствие такой даты.
Все три варианта разумны в зависимости от вашей бизнес-задачи и политики. Похоже, в вашем случае вы хотите, чтобы строгий режим отклонял недопустимую дату, а не настраивал ее.
Фреймворк java.time встроен в Java 8 и новее. Эти классы заменяют неудобные старые классы даты и времени наследие, такие как java.util.Date, Calendar, & SimpleDateFormat.
Чтобы узнать больше, см. Учебник Oracle. И поищите в Stack Overflow множество примеров и объяснений. Спецификация - JSR 310.
Проект Джода-Тайм, теперь в Режим обслуживания, советует перейти на классы java.time.
Вы можете обмениваться объектами java.time напрямую с вашей базой данных. Используйте Драйвер JDBC, совместимый с JDBC 4.2 или новее. Нет необходимости в строках, нет необходимости в классах java.sql.*.
Где взять классы java.time?
Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является испытательной площадкой для возможных будущих дополнений к java.time. Здесь вы можете найти несколько полезных классов, таких как Interval, YearWeek, YearQuarter и более.
Что ж, предположим, что проект компилируется с использованием java 8+, тогда ваш ответ правильный. Я тоже использую эти классы, когда проект находится на java 8. Но иногда мне приходится трогать эти уродливые древние скриптлеты jsp (которые даже не поддерживают java 7). Проверка дат - это сложная задача. Значит, я здесь. И все же ваш ответ - наиболее правильный подход ... Вывод: я облажался.
@KarelG Перечитайте предпоследний абзац о бэк-порт на Java 6 и Java 7. Я не проверял это поведение в бэк-порте, но предлагаю вам попробовать.
// 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;
}
Вот я бы проверил формат даты:
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");
}
Для этой работы у нас есть современный java.time классы. Написание сложного регулярного выражения - вряд ли лучшее использование вашего времени. См. Однострочник в мой ответ.
public static String detectDateFormat(String inputDate, String requiredFormat) {
String tempDate = inputDate.replace("/", "").replace("-", "").replace(" ", "");
String dateFormat;
if (tempDate.matches("([0-12]{2})([0-31]{2})([0-9]{4})")) {
dateFormat = "MMddyyyy";
} else if (tempDate.matches("([0-31]{2})([0-12]{2})([0-9]{4})")) {
dateFormat = "ddMMyyyy";
} else if (tempDate.matches("([0-9]{4})([0-12]{2})([0-31]{2})")) {
dateFormat = "yyyyMMdd";
} else if (tempDate.matches("([0-9]{4})([0-31]{2})([0-12]{2})")) {
dateFormat = "yyyyddMM";
} else if (tempDate.matches("([0-31]{2})([a-z]{3})([0-9]{4})")) {
dateFormat = "ddMMMyyyy";
} else if (tempDate.matches("([a-z]{3})([0-31]{2})([0-9]{4})")) {
dateFormat = "MMMddyyyy";
} else if (tempDate.matches("([0-9]{4})([a-z]{3})([0-31]{2})")) {
dateFormat = "yyyyMMMdd";
} else if (tempDate.matches("([0-9]{4})([0-31]{2})([a-z]{3})")) {
dateFormat = "yyyyddMMM";
} else {
return "Pattern Not Added";
//add your required regex
}
try {
String formattedDate = new SimpleDateFormat(requiredFormat, Locale.ENGLISH).format(new SimpleDateFormat(dateFormat).parse(tempDate));
return formattedDate;
} catch (Exception e) {
//
return "";
}
}
setLenient в false, если вам нравится строгая проверка
public boolean isThisDateValid(String dateToValidate, String dateFromat){
if (dateToValidate == null){
return false;
}
SimpleDateFormat sdf = new SimpleDateFormat(dateFromat);
sdf.setLenient(false);
try {
//if not valid, it will throw ParseException
Date date = sdf.parse(dateToValidate);
System.out.println(date);
} catch (ParseException e) {
e.printStackTrace();
return false;
}
return true;
}
похоже, что SimpleDateFormat не проверяет шаблон строго даже после применения к нему метода setLenient (ложь);, поэтому я использовал метод ниже, чтобы проверить, является ли введенная дата действительной датой или нет в соответствии с предоставленным шаблоном.
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
public boolean isValidFormat(String dateString, String pattern) {
boolean valid = true;
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
try {
formatter.parse(dateString);
} catch (DateTimeParseException e) {
valid = false;
}
return valid;
}
С «устаревшим» форматом даты мы можем отформатировать результат и сравнить его с исходным.
public boolean isValidFormat(String source, String pattern) {
SimpleDateFormat sd = new SimpleDateFormat(pattern);
sd.setLenient(false);
try {
Date date = sd.parse(source);
return date != null && sd.format(date).equals(source);
} catch (Exception e) {
return false;
}
}
В этом отрывке указано «false» для source = 01.01.04 с шаблоном «01. 01.2004».
Если у вас возникнет подобный вопрос, подумайте, нужна ли вам поддержка не григорианского календаря.