SimpleDateFormat 宽大处理会导致意外行为

Nik*_*las 5 java parsing date string-formatting simpledateformat

我发现 的SimpleDateFormat::parse(String source)行为(不幸的是)默认设置为 lenient: setLenient(true)

默认情况下,解析是宽松的:如果输入不是该对象的 format 方法使用的形式,但仍然可以解析为日期,则解析成功。

如果我将宽大程度设置为false,则文档表示,通过严格解析,输入必须与该对象的格式匹配。我在没有宽松模式的情况下使用了 paring SimpleDateFormat,并且错误地在日期中出现了拼写错误(字母o而不是数字0)。(这是简短的工作代码:)

// PASSED (year 199)
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.mm.yyyy");
System.out.println(simpleDateFormat.parse("03.12.199o"));
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("03.12.199o"));        //WTF?
Run Code Online (Sandbox Code Playgroud)

令我惊讶的是,这已经过去了,并且没有ParseException被抛出。我想更进一步:

// PASSED (year 1990)
String string = "just a String to mess with SimpleDateFormat";

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.mm.yyyy");
System.out.println(simpleDateFormat.parse("03.12.1990" + string));
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("03.12.1990" + string));
Run Code Online (Sandbox Code Playgroud)

我们继续:

// FAILED on the 2nd line
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.mm.yyyy");
System.out.println(simpleDateFormat.parse("o3.12.1990"));
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("o3.12.1990"));
Run Code Online (Sandbox Code Playgroud)

最后抛出异常:Unparseable date: "o3.12.1990"。我想知道宽大程度的差异在哪里,为什么我的第一个代码片段的最后一行没有抛出异常?文档说:

通过严格的解析,输入必须与该对象的格式匹配。

我的输入显然与格式不严格匹配 - 我希望这种解析非常严格。为什么会发生(不)发生这种情况?

Ole*_*.V. 3

\n

为什么会发生(不)发生这种情况?

\n
\n\n

它\xe2\x80\x99s 在文档中没有很好地解释。

\n\n
\n

通过宽松的解析,解析器可以使用启发式方法来解释与该对象的格式不精确匹配的输入。通过严格解析,输入必须匹配该对象的格式。

\n
\n\n

不过,该文档确实有所帮助,因为它提到使用Calendar的对象DateFormat是宽松的。该Calendar对象不用于解析本身,而是用于将解析的值解释为日期和时间(我引用DateFormat文档,因为SimpleDateFormat它是 的子类DateFormat)。

\n\n
    \n
  • SimpleDateFormat,无论宽松与否,都将接受 3 位数的年份,例如199,即使您已yyyy在格式模式字符串中指定。该文档提到了年份:

    \n\n
    \n

    对于解析,如果模式字母的数量超过 2,则无论位数多少,年份\n 都会按字面解释。因此,使用模式“MM/dd/yyyy”,“01/11/12”解析为公元 12 年 1 月 11 日

    \n
  • \n
  • DateFormat,无论是否宽松,都会接受并忽略解析文本后的文本,例如o第一个示例中的小写字母。它反对文本之前或内部的意外文本,就像在上一个示例中将字母放在o前面一样。的文档DateFormat.parse说:

    \n\n
    \n

    该方法可能不会使用给定字符串的整个文本。

    \n
  • \n
  • 正如我间接所说,在将解析的值解释为日期和时间时,宽大处理会产生影响。因此,宽松模式SimpleDateFormat会将 29.02.2019 解释为 01.03.2019,因为 2019 年 2 月只有 28 天。严格模式SimpleDateFormat将拒绝这样做,并会抛出异常。默认的宽松行为可能会导致非常令人惊讶和完全无法解释的结果。举一个简单的例子,以错误的顺序给出日、月和年:1990.03.12将得到公元 17 年 8 月 11 日(2001 年前)。

  • \n
\n\n

解决方案

\n\n

VGR 已经在现代 Java 日期和时间 API 的评论中提到LocalDatejava.time根据我的经验,java.time它比旧的日期和时间类好用得多,所以让\xe2\x80\x99s 尝试一下。首先尝试正确的日期字符串:

\n\n
    DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.mm.yyyy");\n    System.out.println(LocalDate.parse("03.12.1990", dateFormatter));\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们得到:

\n\n
\n

java.time.format.DateTimeParseException: 无法解析文本 \'03.12.1990\': 无法从 TemporalAccessor 获取 LocalDate:\n {Year=1990, DayOfMonth=3, MinuteOfHour=12},ISO 类型\ n java.time.format.Parsed

\n
\n\n

这是因为我使用了您的格式模式字符串dd.mm.yyyy,其中小写mm表示分钟。当我们足够仔细地阅读错误消息时,它确实指出将DateTimeFormatter12 解释为小时中的分钟,这不是我们想要的。虽然SimpleDateFormat默认接受了这一点(即使是严格的),java.time但更有助于指出我们的错误。该消息只是间接说明它缺少月份值。我们需要使用大写字母MM表示月份。与此同时,我正在尝试输入错误的日期字符串:

\n\n
    DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");\n    System.out.println(LocalDate.parse("03.12.199o", dateFormatter));\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们得到:

\n\n
\n

java.time.format.DateTimeParseException:无法在索引 6 处解析文本 \'03.12.199o\'

\n
\n\n

索引 6 是说的地方199。它反对,因为我们指定了 4 位数字,但只提供了 3 位。文档说:

\n\n
\n

字母数决定最小字段宽度 \xe2\x80\xa6

\n
\n\n

它还会反对该日期之后未解析的文本。简而言之,在我看来,它给了你所期望的一切。

\n\n

链接

\n\n\n