Fastest way to tell if a string is a valid date
我在工作中支持一个公共库,该库对给定的字符串执行许多检查以查看其是否为有效日期。 Java API,commons-lang库和JodaTime都具有可以解析字符串并将其转换为日期的方法,以便让您知道它是否实际上是一个有效的日期,但是我希望有一种方法无需实际创建日期对象(或使用JodaTime库的DateTime)而进行验证的方法。例如,这是一段简单的示例代码:
1 2 3 4 5 6 7 8 9 | public boolean isValidDate(String dateString) { SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd"); try { df.parse(dateString); return true; } catch (ParseException e) { return false; } } |
这对我来说似乎很浪费,我们正在丢弃最终的对象。根据我的基准,在这个公共库中大约有5%的时间用于验证日期。我希望我只是缺少一个明显的API。任何建议都很好!
UPDATE
假设我们始终可以始终使用相同的日期格式(可能是yyyyMMdd)。我确实也考虑过使用正则表达式,但随后需要知道每个月的天数,leap年等等。
结果
解析日期一千万次
1 2 3 4 5 | 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年的所有
一旦创建了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | private static Set<String> dates = new HashSet<String>(); 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); } |
附:可以对其进行修改以使用
您可以重新思考-当String绝对不是日期时,尝试尽快失败:
-
它是
null -
其
length 不是8(根据您的示例日期格式!) - 它包含数字以外的任何内容(如果您的日期格式仅用于数字日期)
如果这些都不适用,则尝试解析它-最好使用预制的静态
评论后编辑
基于这个巧妙的技巧,我编写了一种快速验证方法。它看起来很丑陋,但比通常的库方法(在任何标准情况下都应使用!)要快得多,因为它依赖于您特定的日期格式,并且不会创建
我只测试了
快速(估计!)微基准测试表明速度提高了30倍。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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; } |
附:上面的示例方法将为" 99999999"返回
我认为,了解某个日期是否有效的更好方法是定义一个方法,例如:
1 2 3 4 5 6 7 8 9 10 11 | 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。
输出到控制台仅用于检查该方法在每个步骤中的工作方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | 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<String> 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<tokens.size(); i++){ if (tokens.get(i).length() % 2 != 0){ String element = tokens.get(i); element ="0" + element; tokens.set(i, element); } } // build a correct final string // 6 elements in the date: yyyy-MM-dd hh:mm:ss // come through and add mandatory 2 elements for (int i=0; i<2; i++){ correctDate = correctDate + tokens.get(i) +"-"; } // add mandatory 3rd (dd) and 4th elements (hh) correctDate = correctDate + tokens.get(2) +"" + tokens.get(3); if (tokens.size() == 4){ correctDate = correctDate +":00:00"; } else if (tokens.size() == 5){ correctDate = correctDate +":" + tokens.get(4) +":00"; } else if (tokens.size() == 6){ correctDate = correctDate +":" + tokens.get(4) +":" + tokens.get(5); } System.out.println("The full correct date format is" + correctDate); return correctDate; } |
}
为此的JUnit测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | 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)); } } |
If following line throws exception then it is invalid date else this will return valid date. Please make sure you use appropriate DateTimeFormatter in the following statement.
LocalDate.parse(uncheckedStringDate,DateTimeFormatter.BASIC_ISO_DATE)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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
可以结合使用正则表达式和手动leap年检查。从而:
1 2 3 4 5 | 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 |
根据dfb的答案,您可以执行两步哈希。