代码之家  ›  专栏  ›  技术社区  ›  Nikolas Charalambidis

简单的宽大会导致意想不到的行为

  •  4
  • Nikolas Charalambidis  · 技术社区  · 7 年前

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

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

    如果我把宽大处理定为 false 文档中说,通过严格的解析,输入必须与此对象的格式匹配。我习惯于 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?
    

    令我惊讶的是,事情已经过去了 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));
    

    让我们继续:

    // 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"));
    

    最后,引发异常: Unparseable date: "o3.12.1990" . 我想知道宽大的区别在哪里,为什么我的第一个代码片段的最后一行没有抛出异常?文件上说:

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

    我的意见显然没有 严格地 匹配格式-我希望这个解析非常严格。为什么会这样?

    3 回复  |  直到 7 年前
        1
  •  1
  •   Anonymous    7 年前

    为什么会这样?

    文档中没有很好的解释。

    对于宽松的解析,解析器可以使用启发式来解释 与此对象的格式不完全匹配的输入。严格地 解析时,输入必须与此对象的格式匹配。

    不过,通过提到 Calendar 反对 DateFormat 使用是宽容的。那 历法 对象不用于解析本身,而是用于将解析的值解释为日期和时间(我引用 数据格式 SimpleDateFormat 数据格式 )

    • 日期格式 ,不管是否宽大,都会接受3位数的年份,例如 199 ,即使您已指定 yyyy 在格式模式字符串中。文件上写着大约一年:

      对于解析,如果模式字母的数目大于2,则 从字面上解释,不管数字多少。所以使用 模式“mm/d d/yyy”,“01/11/12”解析为公元12年1月11日。

    • 数据格式 ,无论是否宽大,都接受并忽略解析后的文本,如小写字母 o 在你的第一个例子中。它反对在文本之前或文本内部出现意外文本,就像在上一个示例中,您将字母 o 在前面。文件 DateFormat.parse 说:

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

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

    解决方案

    vgr已经在提到的注释中 LocalDate java.time ,现代Java日期和时间API。以我的经验 Java.时间 比以前的日期和时间课程要好得多,所以让我们试一试。请先尝试正确的日期字符串:

        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.mm.yyyy");
        System.out.println(LocalDate.parse("03.12.1990", dateFormatter));
    

    我们得到:

    java.time.format.dateTimeParseException:文本“03.12.1990”无法 正在分析:无法从TemporalAccessor获取LocalDate: {year=1990,dayofmonth=3,minuteofhour=12},iso类型 已分析java.time.format.parsed

    这是因为我使用了 dd.mm.yyyy ,其中小写 mm 意思是分钟。当我们足够仔细地阅读错误消息时,它确实声明 DateTimeFormatter 把12理解为一分钟,这不是我们想要的。同时 日期格式 心照不宣地接受了这一点(即使是在严格要求的情况下)。 Java.时间 更有助于指出我们的错误。消息只是间接地说它缺少一个月值。我们需要用大写字母 MM 一个月。同时,我正在尝试输入您的日期字符串:

        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
        System.out.println(LocalDate.parse("03.12.199o", dateFormatter));
    

    我们得到:

    java.time.format.dateTimeParseException:文本“03.12.199o”无法 在索引6处解析

    索引6是 一百九十九 . 它反对,因为我们指定了4位数字,只提供3位。医生说:

    字母数决定了最小字段宽度

    它还将反对在日期之后取消分析文本。简言之,在我看来,它给了你你所期望的一切。

    链接

        2
  •  3
  •   Thomas    7 年前

    宽大并不意味着 整个的 输入匹配,但格式是否匹配。您的输入仍然可以是 3.12.1990somecrap 它会起作用的。

    实际的解析是在 parse(String, ParsePosition) 你也可以用。基本上 parse(String) 将通过 ParsePosition 设置为从索引0开始,解析完成后,将检查该位置的当前索引。

    如果仍然是0,则输入的开头与格式不匹配,即使在宽松模式下也不匹配。

    但是,对于解析器 03.12.199 是有效日期,因此它在索引8处停止-索引8不是0,因此解析成功。如果你想检查所有东西是否都被解析了,你就必须通过自己的 解析位置 并检查索引是否与输入的长度匹配。

        3
  •  1
  •   Amit Bera    7 年前

    如果你使用 setLenient(false) 它仍将解析日期,直到满足所需的模式。但是,它会检查输出日期是否为 有效的 是否约会。对你来说, 03.12.199 是有效日期,因此不会引发异常。让我们举个例子来了解 setlenient(错误) 不同于 setLenient(true)/default

    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy"); 
    System.out.println(simpleDateFormat.parse("31.02.2018"));
    

    以上将给出我的输出: Sat Mar 03 00:00:00 IST 2018

    但是下面的代码抛出parseException为 31.02.2018 不是有效/可能的日期:

    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy");
    simpleDateFormat.setLenient(false);
    System.out.println(simpleDateFormat.parse("31.02.2018"));