【问题标题】:How to prevent SimpleDateFormat to parse wrong formatted dates?如何防止 SimpleDateFormat 解析错误格式的日期?
【发布时间】:2016-09-05 12:05:30
【问题描述】:

我使用SimpleDateFormat 将字符串解析为Date 对象,我想知道为什么结果不是我所期望的。

例如:

DateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd");

Date date = yyyyMMdd.parse("20100725");
System.out.println(date);

按预期工作并输出

Sun Jul 25 00:00:00 CEST 2010

但是

Date date = yyyyMMdd.parse("2010-07-25");
System.out.println(date);

也可以工作和输出

Mon Dec 07 00:00:00 CET 2009

我期待ParseException,但似乎SimpleDateFormat 将月份部分-07 和日期部分-25 解释为负数。首先,我不知道 12 月 7 日是怎么来的。所以我尝试了另一个值:

Date date = yyyyMMdd.parse("2010-7-25");
System.out.println(date);

它超过了

Sun Apr 05 00:00:00 CEST 2009

所以它似乎以某种方式从年份 2010 中减去了 7 月,这应该是 5 月 1 日,而 25 天,所以结果是 2009 年 4 月 5 日。

您在服务实现中使用模式yyyyMMdd 并且某些客户端意外将日期发送为yyyy-MM-dd 的图像。你不会得到例外。相反,您将获得完全不同的日期。我想这不是你所期望的。

例如

String clientData = "2010-05-23";

DateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd");
Date parsedDate = yyyyMMdd.parse(clientData);

System.out.println("Client  : " + clientData);
System.out.println("Service : " + yyyyMMdd.format(parsedDate));

我错过了什么吗?

如何防止 SimpleDateFormat 解析“错误”日期?

当然可以先用正则表达式检查,但是有更好的方法吗?

【问题讨论】:

  • 你试过用setLenient(false)吗?
  • 不,我没有。谢谢。至少它是如此简单。但我仍然想知道为什么默认启用 lenient 。因为它可能导致误解和后期错误。
  • 这个问题你要问Java的设计者;)
  • 我必须承认我以前从未使用过setLenient,但我想我会更频繁地使用它。 :)
  • @RenéLink 这很容易被遗忘(这就是为什么它默认开启特别令人发指的原因)。我刚刚检查了我们的代码库,但我并不满意。

标签: java date simpledateformat


【解决方案1】:

使用SimpleDateFormat.setLenient(false); 获取异常。否则它将尝试尽可能地解析输入,这通常是错误的。

出于某种原因,他们认为 leniency 应该默认为 true,但那是 hardly a surprise。

指定日期/时间解析是否宽松。和 宽松的解析,解析器可以使用启发式来解释输入 与此对象的格式不完全匹配。以严格 解析,输入必须匹配这个对象的格式。

【讨论】:

    【解决方案2】:

    accepted Answer by Cayman 是正确的:默认情况下解析的宽松是问题。

    java.time

    您正在使用麻烦的旧日期时间类,现在已被 java.time 类所取代。

    在 java.time 中没有这样的默认宽大问题。如果输入与格式化模式不严格匹配,则抛出DateTimeParseException。

    LocalDate 类表示没有时间和时区的仅日期值。

    ISO 8601 格式

    对于 YYYY-MM-DD 的标准 ISO 8601 格式输入,只需直接调用 parse。

    String input = "2010-05-23";
    try {
        LocalDate  ld = LocalDate.parse( input ); // Expects standard ISO 8601 input format.
    } catch ( DateTimeParseException e ) {
        …
    }
    

    “基本”ISO 8601 格式

    ISO 8601 标准允许“基本”格式最大限度地减少分隔符的使用。并不是我推荐这些变体,但它们确实存在。

    目前 java.time 只预定义了这些“基本”变体中的一个,DateTimeFormatter.BASIC_ISO_DATE。

    String input = "20100725";
    try {
        LocalDate  ld = LocalDate.parse( input , DateTimeFormatter.BASIC_ISO_DATE ); 
    } catch ( DateTimeParseException e ) {
        …
    }
    

    自定义格式

    对于其他格式,请指定格式化程序。

    String input = "2010/07/25";
    try {
        DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu/MM/dd" );
        LocalDate  ld = LocalDate.parse( input , f ); // Custom format.
    } catch ( DateTimeParseException e ) {
        …
    }
    

    本地化格式

    或者让 java.time 确定本地化格式。

    String input = … ;
    try {
        Locale l = Locale.CANADA_FRENCH ; 
        DateTimeFormatter f = DateTimeFormatter.ofLocalizedDate( FormatStyle.MEDIUM ).withLocale( l );
        LocalDate  ld = LocalDate.parse( input , f ); // Localized format.
    } catch ( DateTimeParseException e ) {
        …
    }
    

    【讨论】:

      【解决方案3】:
      SimpleDateFormat.setLenient(false);
      

      是需要做什么,否则输入将被尝试很好地解析,正如您所知,这并不总是有效。使用上面的函数,编译器会对格式有严格要求。

      【讨论】:

      • 哪个编译器?
      【解决方案4】:

      首先,如果您想解析字符串“2010-05-23”,您的掩码应该是“yyyy-MM-dd”而不是“yyyyMMdd”。第二个 SimpleDateFormat 存在严重问题,因为它不是线程安全的。如果您使用 java 8,则使用学习并使用新包“java.time”。如果您使用任何早于版本 8 的 java,则使用其他一些框架来解析日期。最受欢迎的之一是 Joda time。效果更好。

      【讨论】:

      • 但是如果我期待yyyyMMdd 并且有人给我发送了格式错误的日期。例如。在 http 请求中我不能使用yyyy-MM-dd。请参阅我的问题底部的示例。
      • SimpleDateFormat.setLenient(false);会有所帮助,我仍然说转移到 java.time 包或 joda.time 包。另请参阅这篇文章,了解如何将任何字符串解析为日期:linkedin.com/pulse/…
      猜你喜欢
      • 1970-01-01
      • 2011-04-30
      • 2012-11-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多