我在工作中支持一个公共库,它对给定字符串执行许多检查,以查看它是否是有效日期。 Java API,commons-lang库和JodaTime都有方法可以解析字符串并将其转换为日期,让你知道它是否真的是一个有效的日期,但我希望有一种方法在没有实际创建日期对象的情况下进行验证(或者像JodaTime库那样使用DateTime)。例如,这是一段简单的示例代码:
public boolean isValidDate(String dateString) {
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
try {
df.parse(dateString);
return true;
} catch (ParseException e) {
return false;
}
}
这对我来说似乎很浪费,我们扔掉了生成的物体。从我的基准测试中,我们在这个公共图书馆中有大约5%的时间用于验证日期。我希望我只是错过了一个明显的API。任何建议都会很棒!
更新
假设我们始终可以使用相同的日期格式(可能是yyyyMMdd)。我确实考虑过使用正则表达式,但是需要知道每个月的天数,闰年等......
结果
解析日期1000万次
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的答案是正确的,因为它纯粹是最快的,这是我原来的问题。谢谢!
答案 0 :(得分:15)
如果您真的关心性能并且日期格式非常简单,只需预先计算所有有效字符串并将其散列在内存中。您上面的格式只有大约800万有效组合,最多2050
Slanec编辑 - 参考实施
此实现取决于您的特定日期格式。它可以适应任何特定的日期格式(就像我的第一个答案,但更好一点)。
它创建了一个从1900到2050的所有dates
的集合(存储为字符串 - 其中有54787个),然后将给定日期与存储的日期进行比较。
创建dates
集后,它就像地狱一样快。与我的第一个解决方案相比,快速微基准测试显示出了10倍的改善。
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);
}
P.S。它可以修改为使用Set<Integer>
甚至Trove的TIntHashSet
,这会大大减少内存使用量(因此允许使用更大的时间跨度),然后性能下降到一个级别在my original solution下面。
答案 1 :(得分:12)
你可以恢复你的想法 - 当String 肯定没有日期时,尝试尽快失败:
null
length
不是8(基于您的示例日期格式!)如果这些都不适用,那么尝试解析它 - 最好使用预先制作的静态Format
对象,不要在每个方法运行中创建一个。
评论后编辑
基于this neat trick,我写了一个快速验证方法。它看起来很难看,但是比通常的库方法(应该在任何标准情况下都使用它)快得多!,因为它依赖于你的特定日期格式并且没有创建Date
宾语。它将日期作为int
处理,并从中继续。
我稍微测试了daysInMonth()
方法(the leap year condition taken from Peter Lawrey),所以我希望没有明显的错误。
快速(估计!)微基准标记表示加速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;
}
P.S。上面的示例方法将返回true
为“99999999”。我只会在现有日期返回true:)。
答案 2 :(得分:4)
我认为知道某个日期是否有效的更好方法是定义一个方法,如:
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;
}
一方面,该方法检查日期是否具有正确的格式,另一方面检查日期对应于有效日期。例如,日期&#34; 2015/02/29&#34;将被解析为&#34; 2015/03/03&#34;,因此输入和输出将不同,并且该方法将返回false。
答案 3 :(得分:1)
这是检查日期格式是否正确且实际上是有效日期的方法。假设我们不需要SimpleDateFormat将错误的日期转换为正确的日期,而是方法只返回false。 输出到控制台仅用于检查方法在每个步骤中的工作方式。
$http.get('data.json')
.then(function (response) {
$scope.myData = response.data; // just access data, not data.response
console.log("sadas"); //this is ok
console.log($scope.myData.name); //you need to access $scope.myData, rather than just myData
}, function(error){
console.log(error);
});
}
JUnit测试:
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;
}
答案 4 :(得分:1)
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
答案 5 :(得分:1)
如果以下行抛出异常,那么它是无效的日期,否则这将返回有效日期。请确保在以下语句中使用适当的DateTimeFormatter。
LocalDate.parse(uncheckedStringDate,DateTimeFormatter.BASIC_ISO_DATE)
答案 6 :(得分:0)
根据dfb的答案,你可以做两步哈希。
答案 7 :(得分:0)
可以使用正则表达式和手动闰年检查的组合。因此:
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