简体   繁体   English

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

[英]What is best ways to validate string date to be valid date according to format?

Started working with WEB UI recently. 最近开始使用WEB UI。 And encountered a problem of date string parsing/validation. 并遇到了日期字符串解析/验证的问题。 "dd-mm-yyyy" Some approaches I found are: 我发现的“ dd-mm-yyyy”是:

  1. Matching - not complete validation, not flexible. 匹配-不完全验证,不灵活。

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

  2. There was a post where guy suggest to preinitialize Set with possible date string - fast, valid, but also not flexible and memory consuming 有人在帖子中建议使用可能的日期字符串对Set进行预初始化-快速,有效,但不灵活且占用内存

Is there something easier, maybe available in public libs ? 有没有更简单的方法,也许可以在公共库中使用?

Please don't suggest SimpleDateFormat :) 请不要建议SimpleDateFormat :)

UPDATE for java 8 correct answer is https://stackoverflow.com/a/43076001/1479668 Java 8的更新正确答案是https://stackoverflow.com/a/43076001/1479668

If you are using java 8 then DateTimeFormatter is what you are looking for. 如果您使用的是Java 8,那么DateTimeFormatter就是您想要的。 The link to javadoc also contains sample code and a number of predefined formats. javadoc的链接还包含示例代码和许多预定义的格式。 Besides you can also define your own. 此外,您还可以定义自己的。


Here is some code, an example from the same link: 这是一些代码,来自同一链接的示例:

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

Also, this How to parse/format dates with LocalDateTime? 另外,此如何使用LocalDateTime解析/格式化日期? (Java 8) question has got some fantastic answers. (Java 8)问题得到了一些出色的答案。


EDIT: Thanks Basil Bourque for the updates about ThreeTen-Backport project in case one needs to use almost the same features as provided by java 8 in some older versions of java. 编辑:感谢Basil Bourque提供有关ThreeTen-Backport项目的更新,以防万一需要使用与Java 8在某些较早版本的Java中提供的功能几乎相同的功能。

Preamble: 前言:

If you don't care about details then the accepted answer suggesting DateTimeFormatter.ofPattern("yyyy MM dd"); 如果您不关心细节,则建议使用DateTimeFormatter.ofPattern("yyyy MM dd");答案DateTimeFormatter.ofPattern("yyyy MM dd"); is fine. 很好 Otherwise if you are interested in the tricky details of parsing then read further: 否则,如果您对解析的棘手细节感兴趣,请进一步阅读:


Regular expressions 常用表达

As you have already recognized, a complete validation is not possible by using regular expressions like (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]) ,使用正则表达式(19|20)\\d\\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01]) For example, this expression would accept "2017-02-31" (February with 31 days???). 例如,此表达式将接受“ 2017-02-31”(2月31天???)。

Java-8-parsing mechanism Java-8解析机制

The Java-8-class DateTimeFormatter however, can invalidate such non-existing dates just by parsing. 但是,Java-8类的DateTimeFormatter可以仅通过解析使这些不存在的日期无效。 To go into the details, we have to differentiate between syntactic validation and calendrical validation. 要详细介绍,我们必须区分句法验证和日历验证。 The first kind of syntactic validation is performed by the method parseUnresolved() . 第一种语法验证是通过parseUnresolved()方法执行的。

Parsing is implemented as a two-phase operation. 解析实现为两阶段操作。 First, the text is parsed using the layout defined by the formatter, producing a Map of field to value, a ZoneId and a Chronology. 首先,使用格式化程序定义的布局来解析文本,从而生成字段到值的映射,ZoneId和年代。 Second, the parsed data is resolved, by validating, combining and simplifying the various fields into more useful ones. 其次,通过验证,组合和简化各个字段以使其成为更有用的字段来解析解析的数据。 This method performs the parsing stage but not the resolving stage. 此方法执行解析阶段,但不执行解析阶段。

The main advantage of this method is to not use exception flow which makes this kind of parsing fast. 该方法的主要优点是不使用异常流,从而使这种解析速度更快。 However, the second step of parsing uses exception flow, see also the javadoc of the method parse(CharSequence, ParsePosition) . 但是,解析的第二步使用异常流,另请参见方法parse(CharSequence, ParsePosition)javadoc

By contrast, this method will throw a DateTimeParseException if an error occurs, with the exception containing the error index. 相反,如果发生错误,则此方法将引发DateTimeParseException,但异常包含错误索引。 This change in behavior is necessary due to the increased complexity of parsing and resolving dates/times in this API. 由于此API中解析和解析日期/时间的复杂性增加,因此必须进行行为上的更改。

IMHO a performancewise limitation. 恕我直言,性能方面的限制。 Another drawback is also that the currently available API does not allow to specify a dot OR a hyphen as you have done in your regular expression. 另一个缺点是,当前可用的API不允许像在正则表达式中一样指定点或连字符。 The API only offers a construct like "[.][-]" (using optional sections), but the problem is that an input sequence of ".-" would also be okay for Java-8. API仅提供类似“ [。] [-]”的结构(使用可选部分),但是问题在于,对于Java-8,输入序列“ .-”也可以。

Well, these minor disadvantages are mentioned here for completeness. 好吧,这里为了完整起见提到了这些次要缺点。 A final almost-perfect solution would be in 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

Important: 重要:

  • The best possible validation is only possible in strict resolver style. 最好的验证只有在严格的解析器样式中才有可能。
  • The validation proposed also includes a check if there are trailing unparsed chars. 建议的验证还包括检查是否存在尾部未解析的字符。
  • The result ta of method parseUnresolved() in step 1 cannot be used as intermediate result due to internal limitations of resolving. 由于解析的内部限制,无法将步骤1中方法parseUnresolved()的结果ta用作中间结果。 So this 2-step-approach is also not so overly good for performance. 因此,这种两步走方法在性能上也不太好。 I have not benchmarked it against a normal 1-step-approach but hope that the main author of the new API (S. Colebourne) might have done it, see also for comparison his solution in his own Threeten-extra-library . 我没有将其作为标准的一站式方法进行基准测试,但希望新API的主要作者(S. Colebourne)可以做到这一点,另请参阅他在自己的Threeten-extra-library库中进行比较的解决方案。 More or less a hackish workaround to avoid exception flow as much as possible. 一种或多或少的变通办法,旨在尽可能避免异常流。
  • For Java 6+7, there is a backport available. 对于Java 6 + 7,有一个反向端口

Alternative 另类

If you look for an alternative but not for SimpleDateFormat , then you might also find my library Time4J interesting. 如果您寻找替代方法而不是SimpleDateFormat ,那么您可能还会发现我的Time4J有趣。 It supports real OR-logic and avoids exception flow logic as much as possible (highly tuned parsing only in one step). 它支持真实的OR逻辑,并尽可能避免异常流逻辑(仅一步调优地解析)。 Example: 例:

    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);
    }

Notes: 笔记:

  • A check of trailing characters can be included in the same way as in Java-8 可以用与Java-8中相同的方式包括对结尾字符的检查
  • The parsed result is easily convertible to LocalDate via date.toTemporalAccessor() 解析结果可以通过date.toTemporalAccessor()轻松转换为LocalDate
  • Using the format attribute Attributes.LENIENCY would weaken the validation 使用格式属性Attributes.LENIENCY将削弱验证
  • Time4J is also available for Java 6+7 (when using version line v3.x) Time4J也可用于Java 6 + 7(使用版本行v3.x时)

If you have a known list of formats you want to support, you can create instances of the thread-safe org.joda.time.format.DateTimeFormatter , place them into a list, and iterate until one of them can successfully parse the date. 如果您有要支持的已知格式列表,则可以创建线程安全的org.joda.time.format.DateTimeFormatter实例,将其放入列表中,并进行迭代,直到其中一个可以成功解析日期为止。 Memory consumption for these parsers is negligible, and you can use the resulting date object once you find the matching format. 这些解析器的内存消耗可以忽略不计,找到匹配的格式后就可以使用结果日期对象。

This also has the benefit of being far more readable than regex. 这也具有比正则表达式更具可读性的优点。 Beware of using regex for formats that can be ambiguous like mm-dd-yyyy vs. dd-mm-yyyy . 提防将正则表达式用于可能不明确的格式,例如mm-dd-yyyydd-mm-yyyy

You might try Pojava DateTime. 您可以尝试Pojava DateTime。 It parses dates and times heuristically, rather than matching formats, and supports a wide variety of languages (eg for month names) and formats. 它启发式地解析日期和时间,而不是匹配格式,并且支持多种语言(例如,月份名称)和格式。 See http://pojava.org/howto/datetime.html 参见http://pojava.org/howto/datetime.html

Typical usage relies on your system's locale to resolve the ambiguity of whether a format is m/d/y vs d/m/y, so by default you usually just need: DateTime dt1=new DateTime("01/02/2003"); 典型用法取决于系统的语言环境,以解决格式是m / d / y还是d / m / y的歧义,因此默认情况下,您通常只需要: DateTime dt1=new DateTime("01/02/2003");

If your server is processing dates derived from multiple locales, and need to interpret "01/02/2003" as "January 2" if from one locale, and "February 1" if from a different locale, then you can specify a configuration object to be used when parsing from the foreign locale. 如果您的服务器正在处理来自多个语言环境的日期,并且需要将“ 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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM