共计 4513 个字符,预计需要花费 12 分钟才能阅读完成。
前两天线上出了个小问题,有个统计页面报错了。
简单一看,原来是前端传了个无效日期,2020-06-31。
代码抛异常在这一行
LocalDate.parse(param.getEndDate())
错误信息如下:
java.time.format.DateTimeParseException: Text '2020-06-31' could not be parsed: Invalid date 'JUNE 31'
先不管为啥前端传了个 0631,为啥我这转换日期会报错呢?已经加了校验了啊。
public static final DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static boolean isDateTimeFormat2(String date) {String regex = "[0-9]{4}-[0-9]{2}-[0-9]{2}";
Pattern pattern = Pattern.compile(regex);
Matcher m = pattern.matcher(date);
boolean dateFlag = m.matches();
if (!dateFlag) {return false;} else {
try {LocalDate.parse(date, dateTimeFormat);
return true;
} catch (DateTimeParseException var6) {return false;}
}
}
上面就是校验代码,用了好久了,debug 了一下,发现确实校验通过了。
嗯?等等,我明明传的【2020-06-31】,怎么变成【2020-06-30】了,咋回事?
看看源码吧,
/**
* Obtains an instance of {@code LocalDate} from a text string such as {@code 2007-12-03}.
* <p>
* The string must represent a valid date and is parsed using
* {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE}.
*
* @param text the text to parse such as "2007-12-03", not null
* @return the parsed local date, not null
* @throws DateTimeParseException if the text cannot be parsed
*/
public static LocalDate parse(CharSequence text) {return parse(text, DateTimeFormatter.ISO_LOCAL_DATE);
}
/**
* Obtains an instance of {@code LocalDate} from a text string using a specific formatter.
* <p>
* The text is parsed using the formatter, returning a date.
*
* @param text the text to parse, not null
* @param formatter the formatter to use, not null
* @return the parsed local date, not null
* @throws DateTimeParseException if the text cannot be parsed
*/
public static LocalDate parse(CharSequence text, DateTimeFormatter formatter) {Objects.requireNonNull(formatter, "formatter");
return formatter.parse(text, LocalDate::from);
}
卖关子好累,不卖了。
LocalDate.parse 方法有两个,区别就是指没指定 DateTimeFormatter。
很明显上面的没指定,下面那个指定了
/**
* Creates a formatter using the specified pattern.
* <p>
* This method will create a formatter based on a simple
* <a href="#patterns">pattern of letters and symbols</a>
* as described in the class documentation.
* For example, {@code d MMM uuuu} will format 2011-12-03 as '3 Dec 2011'.
* <p>
* The formatter will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}.
* This can be changed using {@link DateTimeFormatter#withLocale(Locale)} on the returned formatter
* Alternatively use the {@link #ofPattern(String, Locale)} variant of this method.
* <p>
* The returned formatter has no override chronology or zone.
* It uses {@link ResolverStyle#SMART SMART} resolver style.
*
* @param pattern the pattern to use, not null
* @return the formatter based on the pattern, not null
* @throws IllegalArgumentException if the pattern is invalid
* @see DateTimeFormatterBuilder#appendPattern(String)
*/
public static DateTimeFormatter ofPattern(String pattern) {return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter();}
画重点:
It uses {@link ResolverStyle#SMART SMART} resolver style.
SMART(聪明的,智能的),话说我经历好几个叫 SMART 的项目了。。。
DateTimeFormatter.ofPattern,使用了智能解析模式
public enum ResolverStyle {
/**
* Style to resolve dates and times strictly.
* <p>
* Using strict resolution will ensure that all parsed values are within
* the outer range of valid values for the field. Individual fields may
* be further processed for strictness.
* <p>
* For example, resolving year-month and day-of-month in the ISO calendar
* system using strict mode will ensure that the day-of-month is valid
* for the year-month, rejecting invalid values.
*/
STRICT,
/**
* Style to resolve dates and times in a smart, or intelligent, manner.
* <p>
* Using smart resolution will perform the sensible default for each
* field, which may be the same as strict, the same as lenient, or a third
* behavior. Individual fields will interpret this differently.
* <p>
* For example, resolving year-month and day-of-month in the ISO calendar
* system using smart mode will ensure that the day-of-month is from
* 1 to 31, converting any value beyond the last valid day-of-month to be
* the last valid day-of-month.
*/
SMART,
/**
* Style to resolve dates and times leniently.
* <p>
* Using lenient resolution will resolve the values in an appropriate
* lenient manner. Individual fields will interpret this differently.
* <p>
* For example, lenient mode allows the month in the ISO calendar system
* to be outside the range 1 to 12.
* For example, month 15 is treated as being 3 months after month 12.
*/
LENIENT;
}
怎么个智能法呢:
1 to 31, converting any value beyond the last valid day-of-month to be the last valid day-of-month.
超出这个月的最后有效日,会被转化为这个月的最后有效日。
就是说 31 就变成 30 了,但是 32 不会,因为不在 1~31 之间。
现在我们知道了,为啥会开篇所提的 2020-06-31 会通过了校验,因为它是 SMART 模式。。
public static final DateTimeFormatter ISO_LOCAL_DATE;
static {ISO_LOCAL_DATE = new DateTimeFormatterBuilder()
.appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
.appendLiteral('-')
.appendValue(MONTH_OF_YEAR, 2)
.appendLiteral('-')
.appendValue(DAY_OF_MONTH, 2)
.toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
}
而没指定 DateTimeFormatter 的,则使用了默认的 ISO_LOCAL_DATE,可以看出,它使用了 ResolverStyle.STRICT,严格模式。
到这里,就是全部真相了,看来 jdk smart 与否,还得看使用者啊
更多精彩内容欢迎点击我的博客 Telami