使用点“.”时,Java 无法解析日期而不是破折号“-”

问题描述

我有一个令人困惑的问题,请考虑以下工作代码

package com.mycompany.mavenproject2;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Main {
  public static void main(String[] args) throws ParseException {
    SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
    
    Date date = formatter.parse("01-Jan-2017 00:47:13");
    System.out.println(date);
  }
}

按预期执行时打印 Sun Jan 01 00:47:13 CET 2017

但是,当我用日期中的 - 点替换每个 . 时:

package com.mycompany.mavenproject2;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Main {
  public static void main(String[] args) throws ParseException {
    SimpleDateFormat formatter = new SimpleDateFormat("dd.MMM.yyyy HH:mm:ss");
    
    Date date = formatter.parse("01.Jan.2017 00:47:13");
    System.out.println(date);
  }
}

代码在执行时抛出异常:

Exception in thread "main" java.text.ParseException: Unparseable date: "01.Jan.2017 00:47:13"
    at java.base/java.text.DateFormat.parse(DateFormat.java:395)
    at com.mycompany.mavenproject2.Main.main(Main.java:11)

我使用的是 Java 版本 12 和德语语言环境。

有人有想法吗?

解决方法

java.time

我非常同意为您的日期和时间工作推荐 java.time(现代 Java 日期和时间 API)的评论。

使用这个格式化程序:

private static final DateTimeFormatter FORMATTER
        = DateTimeFormatter.ofPattern("dd.MMMuuuu HH:mm:ss",Locale.GERMAN);

做:

    LocalDateTime dateTime = LocalDateTime.parse("01.Jan.2017 00:47:13",FORMATTER);
    System.out.println(dateTime);

在具有德语语言环境的 Java 11 上运行时的输出:

2017-01-01T00:47:13

您可能想知道为什么与您的格式模式字符串相比,我遗漏了第二个点?在 Java 11(也可能接近 Java 版本,可能从 Java 9 到 16)上,德语月份缩写用点表示缩写,因此 Jan. 表示 Januar(一月)等。所以在我的格式中 {{1 }} 匹配 MMM,然后 Jan. 匹配 2017。

对于更完整的故事,Java 从多达四个来源获取其语言环境数据,包括在不同语言环境中使用的月份缩写,并非所有来源都同意德语月份缩写的样子。由于 Java 9 的默认值是 uuuu,这意味着首选来自 CLDR(Unicode 通用语言环境数据存储库)的语言环境数据。这些包括我提到的点。您可以通过将系统属性 CLDR,COMPAT 设置为不以 java.locale-providers 开头的值来获得不同的结果。

您的代码出了什么问题?

我已经给你提示了:在某些 Java 版本中,德语月份的缩写带有点。因此,在您使用点作为分隔符的示例中,您的 CLDRSimpleDateFormat (没有第二个点)匹配到 dd.MMM (有第二个点)。根据格式,现在应该出现一个点,但由于该点已被消耗,01.Jan. 看着 SimpleDateFOrmat,认为它不是一个点并抛出您看到的异常。

真正令人惊讶的行为是在您的第一个示例中,其中 2017 能够解析没有任何点的 SimpleDateFormat,即使它认为月份缩写应该以点结尾。我以前见过数百个 01-Jan-2017 00:47:13 令人惊讶的行为示例,但从未见过与此类似的示例。

所有这些惊喜让我说:一定要避免使用 SimpleDateFormat

如果你持怀疑态度,我不怪你。所以为了演示:

SimpleDateFormat

输出,仍在 Java 11 上:

    SimpleDateFormat formatter = new SimpleDateFormat("MMMyyyy",Locale.GERMAN);
    
    System.out.println(formatter.format(0L));
    
    System.out.println(formatter.parse("Jan2017"));
    System.out.println(formatter.parse("Jan.2017"));

我们看到 Jan.1970 Sun Jan 01 00:00:00 CET 2017 Sun Jan 01 00:00:00 CET 2017 使用点格式化月份,并且能够解析带点和不带点的字符串。

这还不是全部。

SimpleDateFormat

输出:

    SimpleDateFormat formatter = new SimpleDateFormat("MMM",Locale.GERMAN);

    System.out.println(formatter.format(0L));

这次月份缩写的格式没有点。我不知道发生了什么。我再说一遍,忘记令人困惑的 Jan 类。这是一个臭名昭著的麻烦制造者。

链接