序言:
如果您不关心细节,那么建议DateTimeFormatter.ofPattern("yyyy MM dd"); 的接受答案就可以了。否则,如果您对解析的棘手细节感兴趣,请进一步阅读:
正则表达式
正如您已经认识到的那样,使用像(19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01]) 这样的正则表达式是不可能进行完整验证的。例如,此表达式将接受“2017-02-31”(二月有 31 天???)。
Java-8 解析机制
然而,Java-8 类 DateTimeFormatter 可以通过解析使这些不存在的日期无效。为了深入细节,我们必须区分句法验证和日历验证。第一种语法验证通过parseUnresolved()方法进行。
解析被实现为一个两阶段的操作。首先,文字是
使用格式化程序定义的布局进行解析,生成一个 Map
字段值,ZoneId 和 Chronology。二、解析出来的数据是
通过验证、组合和简化各个领域来解决
变成更有用的。此方法执行解析阶段但不执行
解决阶段。
这种方法的主要优点是不使用异常流,这使得这种解析速度很快。但是第二步解析使用异常流,另见parse(CharSequence, ParsePosition)方法的javadoc。
相比之下,此方法将抛出 DateTimeParseException 如果
发生错误,但包含错误索引的异常除外。这
由于行为的复杂性增加,行为的改变是必要的
在此 API 中解析和解析日期/时间。
恕我直言,性能方面的限制。另一个缺点是当前可用的 API 不允许像您在正则表达式中所做的那样指定点或连字符。 API 仅提供类似“[.][-]”的结构(使用可选部分),但问题是“.-”的输入序列也适用于 Java-8。
为了完整起见,这里提到了这些小缺点。最后一个几乎完美的解决方案是在 Java-8 中:
String input = "2017-02.-31";
DateTimeFormatter dtf =
DateTimeFormatter.ofPattern("yyyy[.][-]MM[.][-]dd").withResolverStyle(
ResolverStyle.STRICT // smart mode truncates to Feb 28!
);
ParsePosition pos = new ParsePosition(0);
TemporalAccessor ta = dtf.parseUnresolved(input, pos); // step 1
LocalDate date = null;
if (pos.getErrorIndex() == -1 && pos.getIndex() == input.length()) {
try {
date = LocalDate.parse(input, dtf); // step 2
} catch (DateTimeException dte) {
dte.printStackTrace(); // in strict mode (see resolver style above)
}
}
System.out.println(date); // 2017-02-28 in smart mode
重要:
- 只有在严格的解析器样式中才能实现最佳验证。
- 提议的验证还包括检查尾随是否有未解析的字符。
- 由于解析的内部限制,方法
parseUnresolved()在步骤1中的结果ta不能用作中间结果。因此,这种两步法对性能也不是太好。我没有将它与正常的 1 步方法进行基准测试,但希望新 API (S. Colebourne) 的主要作者可能已经完成了它,另请参阅他自己的解决方案Threeten-extra-library 以进行比较。或多或少是一种骇人听闻的解决方法,以尽可能避免异常流。
- 对于 Java 6+7,有一个 backport 可用。
替代方案
如果您正在寻找替代方案而不是 SimpleDateFormat,那么您可能还会发现我的库 Time4J 很有趣。它支持真正的 OR 逻辑,并尽可能避免异常流逻辑(仅一步进行高度调整的解析)。示例:
String input = "2017-02-31";
ParseLog plog = new ParseLog();
PlainDate date =
ChronoFormatter.ofDatePattern(
"uuuu-MM-dd|uuuu.MM.dd", PatternType.CLDR, Locale.ROOT)
.parse(input, plog); // uses smart mode by default and rejects feb 31 in this mode
if (plog.isError()) {
System.out.println(plog.getErrorMessage());
} else {
System.out.println(date);
}
注意事项:
- 可以采用与 Java-8 中相同的方式对尾随字符进行检查
- 解析结果很容易通过
date.toTemporalAccessor()转换为LocalDate
- 使用格式属性
Attributes.LENIENCY 会削弱验证
- Time4J 也可用于 Java 6+7(使用版本行 v3.x 时)