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

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

  1. 匹配 – 不完整validation,不灵活。

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

  2. 有一个post,家伙建议用可能的日期字符串预初始化Set – 快速,有效,但也不灵活和内存消耗

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

请不要建议SimpleDateFormat 🙂

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

如果您使用的是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); 

另外,这个如何使用LocalDateTime解析/格式化日期? (Java 8)问题得到了一些很棒的答案。

编辑:感谢Basil Bourque关于ThreeTen-Backport项目的更新,以防一些人需要在一些旧版本的java中使用与java 8提供的几乎相同的function。

前言:

如果您不关心细节,那么接受的答案建议DateTimeFormatter.ofPattern("yyyy MM dd"); 很好。 否则,如果您对解析的棘手细节感兴趣,请进一步阅读:


常用表达

正如您已经认识到的那样,使用正则表达式(19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])(19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])无法进行完整的validation(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类DateTimeFormatter只能通过解析使这些不存在的日期无效。 为了详细说明,我们必须区分语法validation和日历validation。 第一种语法validation由parseUnresolved()方法执行。

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

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

相反,如果发生错误,此方法将抛出DateTimeParseException,exception包含错误索引。 由于此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 

重要:

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

替代

如果您寻找替代方案而不是SimpleDateFormat ,那么您可能还会发现我的库Time4J很有趣。 它支持真正的OR逻辑并尽可能地避免exception流逻辑(仅在一步中高度调整解析)。 例:

  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会削弱validation
  • Time4J也可用于Java 6 + 7(使用版本行v3.x时)

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

这也具有比正则表达式更易读的优点。 请谨慎使用正则表达式,这些格式可能是模糊的,如mm-dd-yyyydd-mm-yyyy

您可以尝试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)