判断字符串是否为有效日期的最快方法

我在工作中支持一个公共库,它对给定字符串执行许多检查以查看它是否是有效日期。 Java API,commons-lang库和JodaTime都有方法可以解析字符串并将其转换为日期,让您知道它是否实际上是一个有效的日期,但我希望有一种方法在没有实际创建日期对象(或JodaTime库的情况下的DateTime)的情况下进行validation。 例如,这是一段简单的示例代码:

public boolean isValidDate(String dateString) { SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd"); try { df.parse(dateString); return true; } catch (ParseException e) { return false; } } 

这对我来说似乎很浪费,我们扔掉了最终的对象。 从我的基准测试中,我们在这个公共图书馆中有大约5%的时间用于validation日期。 我希望我只是错过了一个明显的API。 任何建议都会很棒!

UPDATE

假设我们始终可以使用相同的日期格式(可能是yyyyMMdd)。 我确实考虑过使用正则表达式,但是它需要知道每个月的天数,闰年等等……


结果

解析了一千万次

 Using Java's SimpleDateFormat: ~32 seconds Using commons-lang DateUtils.parseDate: ~32 seconds Using JodaTime's DateTimeFormatter: ~3.5 seconds Using the pure code/math solution by Slanec: ~0.8 seconds Using precomputed results by Slanec and dfb (minus filling cache): ~0.2 seconds 

有一些非常有创意的答案,我很感激! 我想现在我只需要决定我需要多少灵活性,我希望代码看起来像。 我要说dfb的答案是正确的,因为它纯粹是最快的,这是我原来的问题。 谢谢!

如果您真的关心性能并且日期格式非常简单,只需预先计算所有有效字符串并将其散列在内存中。 您上面的格式在2050年之前只有大约800万个有效组合


编辑Slanec – 参考实施

此实现取决于您的特定日期格式。 它可以适应任何特定的日期格式(就像我的第一个答案,但更好一点)。

它创建了一组从1900年到2050年的所有dates (存储为字符串 – 其中有54787个),然后将给定日期与存储的日期进行比较。

创建dates ,它就像地狱一样快。 快速微基准测试显示,与我的第一个解决方案相比,改进了10倍。

 private static Set dates = new HashSet(); static { for (int year = 1900; year < 2050; year++) { for (int month = 1; month <= 12; month++) { for (int day = 1; day <= daysInMonth(year, month); day++) { StringBuilder date = new StringBuilder(); date.append(String.format("%04d", year)); date.append(String.format("%02d", month)); date.append(String.format("%02d", day)); dates.add(date.toString()); } } } } public static boolean isValidDate2(String dateString) { return dates.contains(dateString); } 

PS它可以修改为使用Set甚至Trove的TIntHashSet ,它可以大大减少内存使用量(因此允许使用更大的时间跨度),然后性能下降到原来的解决方案之下。

您可以恢复您的想法 – 当String 绝对没有日期时尽量尝试失败:

  • 它是null
  • 它的length不是8(基于您的示例日期格式!)
  • 它包含任何其他数字 (如果您的日期格式仅用于数字日期)

如果这些都不适用,那么尝试解析它 – 最好使用预先制作的静态Format对象,不要在每个方法运行时创建一个。


评论后编辑

基于这个巧妙的技巧 ,我写了一个快速validation方法。 它看起来很丑,但是比通常的库方法(应该在任何标准情况下都使用它)快得多,因为它依赖于你的特定日期格式而不会创建Date对象。 它将日期作为int并从中继续。

daysInMonth()测试了daysInMonth()方法( 闰年条件取自Peter Lawrey ),所以我希望没有明显的bug。

快速(估计!)微基准标记表示加速30倍。

 public static boolean isValidDate(String dateString) { if (dateString == null || dateString.length() != "yyyyMMdd".length()) { return false; } int date; try { date = Integer.parseInt(dateString); } catch (NumberFormatException e) { return false; } int year = date / 10000; int month = (date % 10000) / 100; int day = date % 100; // leap years calculation not valid before 1581 boolean yearOk = (year >= 1581) && (year <= 2500); boolean monthOk = (month >= 1) && (month <= 12); boolean dayOk = (day >= 1) && (day <= daysInMonth(year, month)); return (yearOk && monthOk && dayOk); } private static int daysInMonth(int year, int month) { int daysInMonth; switch (month) { case 1: // fall through case 3: // fall through case 5: // fall through case 7: // fall through case 8: // fall through case 10: // fall through case 12: daysInMonth = 31; break; case 2: if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) { daysInMonth = 29; } else { daysInMonth = 28; } break; default: // returns 30 even for nonexistant months daysInMonth = 30; } return daysInMonth; } 

PS上面的示例方法将为“99999999”返回true 。 我只会在现有日期返回true :)。

我认为知道某个日期是否有效的更好方法是定义一个方法,如:

 public static boolean isValidDate(String input, String format) { boolean valid = false; try { SimpleDateFormat dateFormat = new SimpleDateFormat(format); String output = dateFormat.parse(input).format(format); valid = input.equals(output); } catch (Exception ignore) {} return valid; } 

一方面,该方法检查日期是否具有正确的格式,另一方面检查日期对应于有效日期。 例如,日期“2015/02/29”将被解析为“2015/03/01”,因此输入和输出将不同,并且该方法将返回false。

这是我检查日期格式是否正确并且实际上是有效日期的方法。 假设我们不需要SimpleDateFormat将错误的日期转换为正确的,但是方法只返回false。 输出到控制台仅用于检查方法在每个步骤上的工作方式。

 public class DateFormat { public static boolean validateDateFormat(String stringToValidate){ String sdf = "yyyy-MM-dd HH:mm:ss"; SimpleDateFormat format=new SimpleDateFormat(sdf); String dateFormat = "[12]{1,1}[0-9]{3,3}-(([0]{0,1}[1-9]{1,1})|([1]{0,1}[0-2]{1,1}))-(([0-2]{0,1}[1-9]{1,1})|([3]{0,1}[01]{1,1}))[ ](([01]{0,1}[0-9]{1,1})|([2]{0,1}[0-3]{1,1}))((([:][0-5]{0,1}[0-9]{0,1})|([:][0-5]{0,1}[0-9]{0,1}))){0,2}"; boolean isPassed = false; isPassed = (stringToValidate.matches(dateFormat)) ? true : false; if (isPassed){ // digits are correct. Now, check that the date itself is correct // correct the date format to the full date format String correctDate = correctDateFormat(stringToValidate); try { Date d = format.parse(correctDate); isPassed = (correctDate.equals(new SimpleDateFormat(sdf).format(d))) ? true : false; System.out.println("In = " + correctDate + "; Out = " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(d) + " equals = " + (correctDate.equals(new SimpleDateFormat(sdf).format(d)))); // check that are date is less than current if (!isPassed || d.after(new Date())) { System.out.println(new SimpleDateFormat(sdf).format(d) + " is after current day " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); isPassed = false; } else { isPassed = true; } } catch (ParseException e) { System.out.println(correctDate + " Exception! " + e.getMessage()); isPassed = false; } } else { return false; } return isPassed; } /** * method to fill up the values that are not full, like 2 hours -> 02 hours * to avoid undesirable difference when we will compare original date with parsed date with SimpleDateFormat */ private static String correctDateFormat(String stringToValidate) { String correctDate = ""; StringTokenizer stringTokens = new StringTokenizer(stringToValidate, "-" + " " + ":", false); List tokens = new ArrayList<>(); System.out.println("Inside of recognizer"); while (stringTokens.hasMoreTokens()) { String token = stringTokens.nextToken(); tokens.add(token); // for debug System.out.print(token + "|"); } for (int i=0; i 

}

一个JUnit测试:

 import static org.junit.Assert.*; import junitparams.JUnitParamsRunner; import junitparams.Parameters; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(JUnitParamsRunner.class) public class DateFormatTest { @Parameters private static final Object[] getCorrectDate() { return new Object[] { new Object[]{"2014-12-13 12:12:12"}, new Object[]{"2014-12-13 12:12:1"}, new Object[]{"2014-12-13 12:12:01"}, new Object[]{"2014-12-13 12:1"}, new Object[]{"2014-12-13 12:01"}, new Object[]{"2014-12-13 12"}, new Object[]{"2014-12-13 1"}, new Object[]{"2014-12-31 12:12:01"}, new Object[]{"2014-12-30 23:59:59"}, }; } @Parameters private static final Object[] getWrongDate() { return new Object[] { new Object[]{"201-12-13 12:12:12"}, new Object[]{"2014-12- 12:12:12"}, new Object[]{"2014- 12:12:12"}, new Object[]{"3014-12-12 12:12:12"}, new Object[]{"2014-22-12 12:12:12"}, new Object[]{"2014-12-42 12:12:12"}, new Object[]{"2014-12-32 12:12:12"}, new Object[]{"2014-13-31 12:12:12"}, new Object[]{"2014-12-31 32:12:12"}, new Object[]{"2014-12-31 24:12:12"}, new Object[]{"2014-12-31 23:60:12"}, new Object[]{"2014-12-31 23:59:60"}, new Object[]{"2014-12-31 23:59:50."}, new Object[]{"2014-12-31 "}, new Object[]{"2014-12 23:59:50"}, new Object[]{"2014 23:59:50"} }; } @Test @Parameters(method="getCorrectDate") public void testMethodHasReturnTrueForCorrectDate(String dateToValidate) { assertTrue(DateFormat.validateDateFormatSimple(dateToValidate)); } @Test @Parameters(method="getWrongDate") public void testMethodHasReturnFalseForWrongDate(String dateToValidate) { assertFalse(DateFormat.validateDateFormat(dateToValidate)); } } 

如果跟随行抛出exception,那么它是无效的日期,否则这将返回有效日期。 请确保在以下语句中使用适当的DateTimeFormatter。

LocalDate.parse(uncheckedStringDate,DateTimeFormatter.BASIC_ISO_DATE)

在dfb的答案的基础上,你可以做两步哈希。

  1. 创建表示日期的简单对象(日,月,年)。 计算未来50年的每个日历日,应该少于20k个不同的日期。
  2. 创建一个正则表达式,确认您的输入字符串是否与yyyyMMdd匹配,但不检查该值是否为有效日期(例如,99999999将通过)
  3. 检查函数将首先执行正则表达式,如果成功 – 将其传递给哈希函数检查。 假设您的日期对象是8位+ 8位+ 8位(1900年后的年份),然后是24位* 20k,那么整个哈希表应该非常小…当然低于500Kb,并且如果序列化并且非常快速地从磁盘加载压缩。

可以使用正则表达式和手动闰年检查的组合。 从而:

 if (matches ^\d\d\d\d((01|03|05|07|08|10|12)(30|31|[012]\d)|(04|06|09|11)(30|[012]\d)|02[012]\d)$) if (endsWith "0229") return true or false depending on the year being a leap year return true return false 
  public static int checkIfDateIsExists(String d, String m, String y) { Integer[] array30 = new Integer[]{4, 6, 9, 11}; Integer[] array31 = new Integer[]{1, 3, 5, 7, 8, 10, 12}; int i = 0; int day = Integer.parseInt(d); int month = Integer.parseInt(m); int year = Integer.parseInt(y); if (month == 2) { if (isLeapYear(year)) { if (day > 29) { i = 2; // false } else { i = 1; // true } } else { if (day > 28) { i = 2;// false } else { i = 1;// true } } } else if (month == 4 || month == 6 || month == 9 || month == 11) { if (day > 30) { i = 2;// false } else { i = 1;// true } } else { i = 1;// true } return i; } 

如果它返回i = 2表示日期无效,如果日期有效则返回1