根据格式验证字符串日期为有效日期的最佳方法是什么?

mag*_*lla 1 java validation parsing date

最近开始使用WEB UI。并且遇到了日期字符串解析/验证的问题。“dd-mm-yyyy”我发现的一些方法是:

  1. 匹配 - 不完整的验证,不灵活。

    (19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])

  2. 有一个帖子,有人建议用可能的日期字符串预初始化 Set - 快速,有效,但也不灵活且消耗内存

有没有更简单的东西,也许在公共库中可用?

请不要建议 SimpleDateFormat :)

UPDATE 对Java 8正确答案是/sf/answers/3015320101/

Men*_*ild 5

前言:

如果您不关心细节,那么接受的答案建议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 解析机制

DateTimeFormatter然而,Java-8 类可以仅通过解析使这种不存在的日期无效。为了深入细节,我们必须区分句法验证和日历验证。第一种语法验证由方法parseUnresolved() 执行

解析是作为两阶段操作实现的。首先,使用格式化程序定义的布局解析文本,生成字段到值的映射、ZoneId 和 Chronology。其次,通过验证、组合和简化各个字段为更有用的字段来解析解析的数据。此方法执行解析阶段,但不执行解析阶段。

这种方法的主要优点是不使用异常流,这使得这种解析速度很快。但是,解析的第二步使用了异常流,另见方法的javadocparse(CharSequence, ParsePosition)

相比之下,如果发生错误,此方法将抛出 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
Run Code Online (Sandbox Code Playgroud)

重要的:

  • 最好的验证只能在严格的解析器样式中实现。
  • 提议的验证还包括检查是否有尾随未解析的字符。
  • 由于解析的内部限制,步骤 1ta中方法的结果parseUnresolved()不能用作中间结果。所以这种两步法对性能也不是太好。我没有将它与正常的 1-step-approach 进行基准测试,但希望新 API 的主要作者(S. Colebourne)可能已经做到了,另请参阅他自己的Threeten-extra-library 中的解决方案进行比较。或多或少是一种黑客解决方法,以尽可能避免异常流。
  • 对于 Java 6+7,有一个向后移植可用。

选择

如果您寻找替代方案而不是 for 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);
    }
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 可以以与 Java-8 中相同的方式包含对尾随字符的检查
  • 解析结果很容易转换为LocalDateviadate.toTemporalAccessor()
  • 使用 format 属性Attributes.LENIENCY会削弱验证
  • Time4J 也可用于 Java 6+7(使用版本行 v3.x 时)