【问题标题】:DateTimeFormatter Cannot Parse Dates Without Leading Zeroes in GermanDateTimeFormatter 无法解析没有德语前导零的日期
【发布时间】:2017-12-12 12:52:18
【问题描述】:

我们的客户今天发现了一个有趣的错误。考虑以下方法:

final DateTimeFormatter englishFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(Locale.ENGLISH);
System.out.println(LocalDate.parse("04/01/17", englishFormatter));
System.out.println(LocalDate.parse("4/1/17", englishFormatter));

final DateTimeFormatter germanFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(Locale.GERMAN);
System.out.println(LocalDate.parse("01.04.17", germanFormatter));
System.out.println(LocalDate.parse("1.4.17", germanFormatter));

(如果您不知道,这些确实是英语和德语的正确日期。我什至会说它们是相同的日期 [2017 年 4 月 1 日]。花点时间考虑一下您对这个应用程序的看法应该返回。)

它返回的内容如下:

Exception in thread "main" java.time.format.DateTimeParseException: Text '1.4.17' could not be parsed at index 0
    at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
    at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
    at java.time.LocalDate.parse(LocalDate.java:400)
    at Main.main(Main.java:20)

英文日期格式可以带前导零也可以不带前导零。德语格式仅适用于前导零。

我似乎无法找到一个属性来更改此行为以使其正常工作。

如何让DateTimeFormatter 理解没有前导零的德国日期?

注意:我们的应用程序支持多种语言环境,因此使用特定的DateTimeFormatter(如DateTimeFormatter.ofPattern("d.M.yy"))是完全不可能的,尤其是因为我们要解析默认值 日期格式。

【问题讨论】:

  • @vinS 这与我想要的完全相反。我想解析标准的德语日期。
  • 我用 JDK-9 试过你的代码。它返回了类似的异常,但有三个细微差别: java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1988) java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1890) java.base/java.time.LocalDate.parse(LocalDate.java:428) 我认为你不能不检查你的字符串来解决这个问题。
  • 不只有一个标准的德国日期。实际上,德国人使用多种形式,因此您不能完全依赖单一格式。

标签: java localization java-time


【解决方案1】:

一种解决方案是捕获 DateTimeParseException,然后使用修改/减少的日期模式重试。

import java.text.Format;
import java.time.LocalDate;
import java.time.chrono.IsoChronology;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.format.FormatStyle;
import java.util.ListResourceBundle;
import java.util.Locale;

public class Demo {
    public static void main(String[] args) {

        Locale[] locales = {Locale.ENGLISH, Locale.GERMAN};
        for (Locale locale : locales)
        {
            DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(locale);
            String [] englishDates = {"01/04/17","1/4/17"};
            String [] germanDates = {"01.04.17","1.4.17"};

            String datePattern = DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.SHORT,null, IsoChronology.INSTANCE, locale);
            System.out.println("Locale " + locale.getDisplayName() + ": Default Date Pattern (short): " + datePattern);

            String[] dates = null;

            if (locale == Locale.ENGLISH)
                dates = englishDates;
            else
                dates = germanDates;

            for (String date : dates) {
                try {
                    System.out.printf("  " + date + " -> ");
                    System.out.println(LocalDate.parse(date, formatter));
                }
                catch (DateTimeParseException e)
                {
                    System.out.println("Error!");
                    // Try alternate pattern
                    datePattern = datePattern.replace("dd", "d").replace("MM", "M");
                    System.out.println("  Modified Date Pattern (short): " + datePattern);
                    // Allow single digits in day and month
                    DateTimeFormatter modifiedFormatter = DateTimeFormatter.ofPattern(datePattern);
                    System.out.println("  " + date + " -> " + LocalDate.parse(date, modifiedFormatter));  // OK
                }
            }
        }
    }
}

这给出了:

Locale English: Default Date Pattern (short): M/d/yy
  01/04/17 -> 2017-01-04
  1/4/17 -> 2017-01-04
Locale German: Default Date Pattern (short): dd.MM.yy
  01.04.17 -> 2017-04-01
  1.4.17 -> Error!
  Modified Date Pattern (short): d.M.yy
  1.4.17 -> 2017-04-01

【讨论】:

  • 我什至怀疑从一开始就使用缩减模式会起作用。
  • 这适用于所有使用两位数日-OG-月和/或月(不仅是德语)的语言环境。 OP 必须决定是否需要这样做;这可能是一个优势。如果确实需要,您的解决方案似乎不如我的解决方案。
【解决方案2】:

我尝试了相反的方法:使用本地化格式化程序格式化日期。

    LocalDate testDate = LocalDate.of(2017, Month.APRIL, 1);

    final DateTimeFormatter englishFormatter 
            = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
                .withLocale(Locale.ENGLISH);
    System.out.println("English: " + testDate.format(englishFormatter));

    final DateTimeFormatter germanFormatter 
            = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
                .withLocale(Locale.GERMAN);
    System.out.println("German: " + testDate.format(germanFormatter));

我明白了

English: 4/1/17
German:  01.04.17

很明显,Java 认为标准的德语日期格式使用前导零。如果您确定这是错误的,您可以考虑向 Oracle 提交错误。

为了规避您不喜欢的行为,在多个语言环境中,恐怕您需要某种 hack。我能想到的最好的 hack 如下。这不漂亮。它有效。

private static final Map<Locale, DateTimeFormatter> STEFFI_S_LOCALIZED_FORMATTERS
        = createSteffiSFormatters();

private static Map<Locale, DateTimeFormatter> createSteffiSFormatters() {
    Map<Locale, DateTimeFormatter> formatters = new HashMap<>(2);
    formatters.put(Locale.GERMAN, DateTimeFormatter.ofPattern("d.M.uu"));
    return formatters;
}

public static DateTimeFormatter getLocalizedFormatter(Locale formattingLocale) {
    DateTimeFormatter localizedFormatter
            = STEFFI_S_LOCALIZED_FORMATTERS.get(formattingLocale);
    if (localizedFormatter == null) {
        localizedFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
                                              .withLocale(formattingLocale);
    }
    return localizedFormatter;
}

现在你可以这样做了:

    final DateTimeFormatter englishFormatter = getLocalizedFormatter(Locale.ENGLISH);
    System.out.println(LocalDate.parse("04/01/17", englishFormatter));
    System.out.println(LocalDate.parse("4/1/17", englishFormatter));

    final DateTimeFormatter germanFormatter = getLocalizedFormatter(Locale.GERMAN);
    System.out.println(LocalDate.parse("01.04.17", germanFormatter));
    System.out.println(LocalDate.parse("1.4.17", germanFormatter));

打印出来:

2017-04-01
2017-04-01
2017-04-01
2017-04-01

【讨论】:

  • 确实,这似乎是对德国文化规范的假设,包括填充零。一些互联网搜索表明,德国本土的现代方法通常是在以所有数字表示时包含填充零(没有月份名称)。过去的传统习惯可能有所不同,其他与德国有关的地方也是如此。因此,Java 中的这种行为似乎是一种特性,而不是错误——但有些人可能会期待其他行为。维基百科:herehere
  • @BasilBourque 确实,这只是一个功能而不是错误。 Java 在这里依赖于 CLDR 数据,他们不得不选择一种广泛使用的变体,而不是 German DIN-norm 5008。这也是我觉得最常见的(因为我的母语是德语)。
  • @BasilBourque 我不知道这个功能 - 旧 API 支持两种日期格式:DateFormat.getDateInstance(DateFormat.SHORT, Locale.GERMAN).parse("1.4.17") 并且 Wiki 文章都指出零仅用作填充,两者都是可能的。
  • @SteffiS。旧的DateFormat 接受了大量不正确的输入,这通常很麻烦,因为您没有发现错误。您已经找到了将其作为一项功能加以利用的方法。
  • @OleV.V. “利用”是“允许大多数人自然而然地使用日期格式”的一个相当苛刻的词。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-30
相关资源
最近更新 更多