当我将 LocalDate 格式化为 dd.MMM.YYYY 时,我得到带有两个点的 01.Jan..2000

When I format a LocalDate to dd.MMM.YYYY I get 01.Jan..2000 with two dots

我正在尝试将 LocalDate 变量格式化为 dd.MMM.YYYY,方法是:

DateTimeFormatter.ofPattern("dd.MMM.yyyy")

问题是我有一半以上的时间会得到两个点。例如 01-01-2000 转到 01.Jan..2000.

我知道为什么我有这个问题,因为三个M。当我使用 dd.MM.yyyy 时,我可以毫无问题地到达 01.01.2000。第三个M就是问题所在

我该如何解决这个问题?

问题的原因是月份的缩写是特定于语言环境的:

  • 在某些语言环境中,有一个点(句点)表示缩写1Locale.CANADA 例如。在其他人那里没有; Locale.ENGLISH 例如。
  • 在点表示缩写的语言环境中,当月份名称不需要缩写时,您可能会发现也可能找不到点。例如月份名称 May 只有三个字母,所以 May. 表示这是一个缩写是不合逻辑的。

有多种方法可以解决这个问题,包括:

  1. 不要修复它。在某些情况下双点输出在逻辑上是正确的(通过某种逻辑)2,即使它看起来很奇怪。

  2. 我的首选方式是使用不同的输出格式。 不要使用点作为分隔符。

    使用点字符作为分隔符......非常规......当你将它与缩写月份名称结合使用时,你会遇到这种尴尬的边缘情况。

    当然有办法解决这个问题,但考虑到如果其他人需要解析 你的 中的日期,他们可能会 运行 陷入同样的​​问题 他们的代码库。

  3. 将您的 DateTimeFormatter 硬连接到现有的 Locale,其中缩写名称中没有点。

    理论上他们可能会决定更改标准中的缩写 Locale。但怀疑他们会这样做,因为这样的更改可能会破坏隐式依赖于语言环境的客户代码......就像你的代码一样。

  4. 创建自定义 Locale 并在创建 DateTimeFormatter.

    时使用它
  5. 使用 DateTimeFormatterBuilder 创建格式化程序。要处理月份,请将 appendText(TemporalField field, Map<Long,String> textLookup) 与查找 table 一起使用,其中 恰好 [=7​​3=] 您要使用的缩写。

    根据您“附加”其他字段的方式,您的格式化程序可以完全或部分独立于区域设置。


在我看来,其中 2. 和 5. 是最“正确”的。 Ole 的 用代码说明了这些选项。


1 - 请参阅这篇关于美国英语语法的文章 - When you need periods after abbreviations
2 - 问题是让人们相信“看起来很奇怪但合乎逻辑”比“看起来不错但不合逻辑”要好。我不认为你会赢得这场争论......

Stephen C. 写了一个答案,很好地涵盖了您的选择。作为补充,因为我同意选项2和5是最正确的,所以我想把这两个拼出来。

选项 2:使用不同的格式

大多数可用语言环境的本地化日期格式已内置到 Java 中。这些通常没有得到充分利用。依靠 Java 知道如何为我们的观众和他们的地区设置日期格式,我们可以节省很多麻烦。我以德语为例,因为它是那些始终在日期部分和缩写部分之间包含点的语言环境之一。以下内容也适用于您的语言环境,即使它不是德语(如果您替换 Locale.getDefault(Locale.Category.FORMAT) 或您用户的语言环境)。

private static final Locale LOCALE = Locale.GERMAN;

private static final DateTimeFormatter DATE_FORMATTER
        = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
                .withLocale(LOCALE);

为了演示,我正在格式化当年每个月的一天:

    LocalDate date = LocalDate.of(2021, Month.JANUARY, 16);
    for (int i = 0; i < 12; i++) {
        System.out.println(date.format(DATE_FORMATTER));
        date = date.plusMonths(1).minusDays(1);
    }

输出为:

16.01.2021
15.02.2021
14.03.2021
13.04.2021
12.05.2021
11.06.2021
10.07.2021
09.08.2021
08.09.2021
07.10.2021
06.11.2021
05.12.2021

对于德语语言环境,我们在这里得到数字月份。其他语言环境可能会给出其他结果,例如月份缩写。

如果您想要不使用数字月份的较长格式,请指定 FormatStyle.LONG 而不是 FormatStyle.MEDIUM

private static final DateTimeFormatter DATE_FORMATTER
        = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)
                .withLocale(LOCALE);

16. Januar 2021
15. Februar 2021
14. März 2021
13. April 2021
12. Mai 2021
11. Juni 2021
10. Juli 2021
9. August 2021
8. September 2021
7. Oktober 2021
6. November 2021
5. Dezember 2021

我建议您的用户会对上述其中一项感到满意。

选项 5:DateTimeFormatterBuilder.appendText(TemporalField, Map)

如果您的用户告诉您他们不想要上面的本地化格式,他们确实想要您的带有月份缩写和单点的格式 — 它越来越长,但结果很漂亮,每个人都会很高兴。

private static final DateTimeFormatter DATE_FORMATTER = new DateTimeFormatterBuilder()
        .appendPattern("dd.")
        .appendText(ChronoField.MONTH_OF_YEAR, getMonthAbbreviations())
        .appendPattern(".uuuu")
        .toFormatter(LOCALE);

private static Map<Long, String> getMonthAbbreviations() {
    return Arrays.stream(Month.values())
            .collect(Collectors.toMap(m -> Long.valueOf(m.getValue()),
                    MyClass::getDisplayNameWithoutDot));
}

private static String getDisplayNameWithoutDot(Month m) {
    return m.getDisplayName(TextStyle.SHORT, LOCALE)
            .replaceFirst("\.$", "");
}

来自与上述相同循环的输出:

16.Jan.2021
15.Feb.2021
14.März.2021
13.Apr.2021
12.Mai.2021
11.Juni.2021
10.Juli.2021
09.Aug.2021
08.Sep.2021
07.Okt.2021
06.Nov.2021
05.Dez.2021

每次一个点。核心技巧是使用 Java 的月份缩写,如果有则从中删除点(Jan. 变为 Jan),如果没有点则按原样使用它( Mai 保持 Mai)。我的 getDisplayNameWithoutDot 方法就是这样做的。我依次使用此方法来构建双参数 appendText(TemporalField, Map<Long, String>) 方法需要并用于格式化的映射。