如何防止 SimpleDateFormat 解析格式错误的日期?

How to prevent SimpleDateFormat to parse wrong formatted dates?

我使用 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 解析 'wrong' 日期?

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

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

出于某种原因,他们决定默认情况下应该宽大处理,但那是 hardly a surprise

Specify whether or not date/time parsing is to be lenient. With lenient parsing, the parser may use heuristics to interpret inputs that do not precisely match this object's format. With strict parsing, inputs must match this object's format.

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

SimpleDateFormat.setLenient(false);

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

是正确的:默认情况下解析宽松是问题所在。

java.time

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

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

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

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 ) {
    …
}