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

时间:2017-03-28 17:09:13

标签: java validation date parsing

最近开始使用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 - 快速,有效,但也不灵活且耗费内存

  3. 是否有更容易的东西,可能在公共图书馆中提供?

    请不要建议使用SimpleDateFormat :)

    更新 对于java 8正确答案 是https://stackoverflow.com/a/43076001/1479668

4 个答案:

答案 0 :(得分:3)

如果您使用的是Java 8,那么DateTimeFormatter就是您要找的。 javadoc的链接还包含示例代码和许多预定义格式。此外,您还可以定义自己的。

这是一些代码,来自同一链接的一个例子:

LocalDate date = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MM dd");
String text = date.format(formatter);
LocalDate parsedDate = LocalDate.parse(text, formatter);

此外,这个How to parse/format dates with LocalDateTime? (Java 8)问题得到了一些很棒的答案。


编辑:感谢Basil Bourque ,了解有关ThreeTen-Backport项目的更新,以防在某些旧版本的java中需要使用java 8提供的几乎相同的功能。

答案 1 :(得分:3)

序言:

如果您不关心细节,那么建议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”(2月31天???)。

Java-8解析机制

然而,Java-8-class DateTimeFormatter只能通过解析使这些不存在的日期无效。为了详细说明,我们必须区分语法验证和日历验证。第一种语法验证由方法parseUnresolved()执行。

  

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

此方法的主要优点是不使用异常流程,这使得这种解析速度很快。但是,解析的第二步使用异常流,另请参阅方法parse(CharSequence, ParsePosition)的{​​{3}}。

  

相比之下,如果是,则此方法将抛出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

重要提示:

  • 只有在严格的解析器样式下才能进行最佳验证。
  • 建议的验证还包括检查是否存在尾随未解析的字符。
  • 由于解析的内部限制,步骤1中方法ta的结果parseUnresolved()不能用作中间结果。所以这个两步法对于性能来说也不是那么好。我没有对正常的一步法进行基准测试,但希望新API(S. Colebourne)的主要作者可能已经完成了它,也可以在他自己的javadoc中查看他的解决方案。或多或少是一种尽可能避免异常流动的hackish解决方法。
  • 对于Java 6 + 7,有一个Threeten-extra-library可用。

<强>替代

如果您寻找替代方案而不是SimpleDateFormat,那么您可能还会发现我的库backport很有趣。它支持真正的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
  • 相同的方式检查尾随字符
  • 解析后的结果可以通过LocalDate
  • 轻松转换为date.toTemporalAccessor()
  • 使用格式属性Attributes.LENIENCY会削弱验证
  • Time4J也可用于Java 6 + 7(使用版本行v3.x时)

答案 2 :(得分:1)

如果您有一个已知的格式列表,则可以创建线程安全org.joda.time.format.DateTimeFormatter的实例,将它们放入列表中,然后迭代直到其中一个可以成功解析日期。这些解析器的内存消耗可以忽略不计,一旦找到匹配的格式,就可以使用生成的日期对象。

这也有利于比正则表达式更具可读性。请注意将正则表达式用于可能含糊不清的格式,例如mm-dd-yyyydd-mm-yyyy

答案 3 :(得分:1)

您可以尝试Pojava DateTime。它启发式地分析日期和时间,而不是匹配格式,并支持各种语言(例如月份名称)和格式。见http://pojava.org/howto/datetime.html

典型用法依赖于系统的区域设置来解决格式是m / d / y与d / m / y的模糊性,因此默认情况下您通常只需要:DateTime dt1=new DateTime("01/02/2003");

如果您的服务器正在处理从多个区域设置派生的日期,并且需要解释&#34; 01/02/2003&#34; as&#34; 1月2日&#34;如果来自一个地区,并且&#34; 2月1日和#34;如果来自不同的语言环境,则可以指定从外部语言环境解析时要使用的配置对象。

DateTimeConfigBuilder builder = DateTimeConfigBuilder.newInstance();
builder.setDmyOrder(false);
builder.setInputTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
builder.setOutputTimeZone(TimeZone.getTimeZone("America/Porto_Velho"));
IDateTimeConfig config=DateTimeConfig.fromBuilder(builder);

DateTime dt1=new DateTime("01/02/2003 13:30", config)