【问题标题】:What is best ways to validate string date to be valid date according to format?根据格式验证字符串日期是否为有效日期的最佳方法是什么?
【发布时间】:2017-03-28 17:09:12
【问题描述】:

最近开始使用 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 :)

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

【问题讨论】:

  • "请不要建议 SimpleDateFormat" -- 为什么不呢?
  • “根据格式验证字符串日期是否为有效日期的最佳方法是什么?” 解析它。
  • 您的第一段说“dd-mm-yyyy”,但您的示例正则表达式匹配“yyyy-mm-dd”。您要验证哪一个?
  • @azurefrog 因为它会在解析时不包含“55-55-2015”或“12-12-201O”等异常字符串
  • 任何用户界面都不应将“12-12-201O”视为有效日期。字母不是数字的有效替代品,因为它们看起来很相似。

标签: java validation date parsing


【解决方案1】:

序言:

如果您不关心细节,那么建议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 时)

【讨论】:

    【解决方案2】:

    如果您使用的是 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 提供的几乎相同的功能。 p>

    【讨论】:

    • 谢谢,我不知道 Java 8 修复了这个问题,尽管我仍然需要它来支持 6。
    • 其他答案中提出了许多很棒的库。我对版本 joda.org/joda-time
    • @ShubhamChaurasia 不,对于 Java 6 和 Java 7,请在您的项目中使用 ThreeTen-Backport 库。那里有很多 java.time 功能。 java.time 的前身 Joda-Time 项目现在处于维护模式,并建议迁移到 java.time。我建议将这些事实添加到您的答案中。
    【解决方案3】:

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

    这还具有比正则表达式更具可读性的好处。谨防将正则表达式用于可能模棱两可的格式,例如 mm-dd-yyyydd-mm-yyyy

    【讨论】:

    • 请注意,对于
    • 谢谢,会用这个,已经有 Joda 在依赖项中。
    【解决方案4】:

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

    典型用法依赖于您系统的语言环境来解决格式是 m/d/y 还是 d/m/y 的歧义,因此默认情况下您通常只需要:DateTime dt1=new DateTime("01/02/2003");

    如果您的服务器正在处理源自多个语言环境的日期,并且如果来自一个语言环境,需要将“01/02/2003”解释为“1 月 2 日”,如果来自不同的语言环境,则需要将“2 月 1 日”解释为,那么您可以指定从外部语言环境解析时要使用的配置对象。

    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)
    

    【讨论】:

      猜你喜欢
      • 2016-11-23
      • 1970-01-01
      • 2011-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-16
      相关资源
      最近更新 更多