【问题标题】:SimpleDateFormat with German Locale - Java 8 vs Java 10+带有德语区域设置的 SimpleDateFormat - Java 8 与 Java 10+
【发布时间】:2018-10-28 21:07:14
【问题描述】:

我在一个legacy应用程序中有代码和一个测试用例,可以总结如下:

@Test
public void testParseDate() throws ParseException {
    String toParse = "Mo Aug 18 11:25:26 MESZ +0200 2014";
    String pattern = "EEE MMM dd HH:mm:ss z Z yyyy";

    DateFormat dateFormatter = new SimpleDateFormat(pattern, Locale.GERMANY);
    Date date = dateFormatter.parse(toParse);

    //skipped assumptions
}

此测试在 Java 8 及更低版本中通过。然而,随着 Java 10 向上,这会导致 java.text.ParseException: Unparseable date: "Mo Aug 18 11:25:26 MESZ +0200 2014"

记录在案: 除了de_DE,对于语言环境也会抛出异常 de_CH, de_AT, de_LU.

我知道日期格式是changed with JDK 9 (JEP 252)。但是,我认为这是破坏向后兼容性的破坏性更改。摘录:

在 JDK 9 中,Unicode 联盟的通用语言环境数据存储库 (CLDR) 数据被启用为默认语言环境数据,因此您可以使用标准语言环境数据而无需任何进一步的操作。

在 JDK 8 中,虽然 CLDR 语言环境数据与 JRE 捆绑在一起,但默认情况下并未启用。

使用区域设置敏感服务(例如日期、时间和数字格式)的代码可能会与 CLDR 区域设置数据产生不同的结果。

为星期几添加. (Mo.) 可以弥补这一点,测试将通过。但是,对于旧数据(以 XML 等序列化形式),这并不是真正的解决方案。

检查此stackoverflow post,似乎该行为是针对德语区域设置的,可以通过使用COMPAT 模式指定java.locale.providers 来缓解。但是,我不喜欢依赖某些系统属性值的想法,原因可能有两个:

  1. JDK 下一版本中的更改。
  2. 在不同的环境中被遗忘。

我的问题是:

  • 如何在不重写/修改现有序列化数据或添加/更改系统属性(如java.locale.providers)的情况下保持遗留代码与此特定日期模式的向后兼容性,这些属性可能会在不同的环境中被遗忘(应用程序服务器, 独立 jars, ...) ?

【问题讨论】:

  • 也许有一个丑陋的解决方法:拦截调用并在传递数据之前检查/修改数据?
  • 您可以在 Java 中设置系统属性:System.setProperty("java.locale.providers", "COMPAT,CLDR");。这将防止它在任何环境中被遗忘。当然,它仍然不能保证 Java 11 及更高版本的任何内容。您可能需要考虑一个将所有旧日期时间数据转换为 ISO 8601 的项目(这似乎是面向未来的):
  • 可以将 EEE 更改为 EE,但这对于其他语言环境可能会变得丑陋。也许你想要一些宽大处理,无论是 Mo 还是 Mon。
  • @JoopEggen EE MMM dd HH:mm:ss z Z yyyy 不起作用。它导致java.text.ParseException: Unparseable date: "Mo Aug 18 11:25:26 MESZ +0200 2014"
  • @rzo 我很困惑为什么您遇到兼容性问题但拒绝使用 Oracle 明确提供的 compatibility solution 作为解决方案:java.locale.providersCOMPAT 或 @987654326 @API?

标签: java java-8 simpledateformat java-9 java-10


【解决方案1】:

我并不是说这是一个很好的解决方案,但它似乎是一种解决方法。

    Map<Long, String> dayOfWeekTexts = Map.of(1L, "Mo", 2L, "Di", 
            3L, "Mi", 4L, "Do", 5L, "Fr", 6L, "Sa", 7L, "So");
    Map<Long, String> monthTexts = Map.ofEntries(Map.entry(1L, "Jan"), 
            Map.entry(2L, "Feb"), Map.entry(3L, "Mär"), Map.entry(4L, "Apr"),
            Map.entry(5L, "Mai"), Map.entry(6L, "Jun"), Map.entry(7L, "Jul"),
            Map.entry(8L, "Aug"), Map.entry(9L, "Sep"), Map.entry(10L, "Okt"),
            Map.entry(11L, "Nov"), Map.entry(12L, "Dez"));

    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .appendText(ChronoField.DAY_OF_WEEK, dayOfWeekTexts)
            .appendLiteral(' ')
            .appendText(ChronoField.MONTH_OF_YEAR, monthTexts)
            .appendPattern(" dd HH:mm:ss z Z yyyy")
            .toFormatter(Locale.GERMANY);

    String toParse = "Mo Aug 18 11:25:26 MESZ +0200 2014";
    OffsetDateTime odt = OffsetDateTime.parse(toParse, formatter);
    System.out.println(odt);
    ZonedDateTime zdt = ZonedDateTime.parse(toParse, formatter);
    System.out.println(zdt);

在我的 Oracle JDK 10.0.1 上运行的输出:

2014-08-18T11:25:26+02:00
2014-08-18T11:25:26+02:00[Europe/Berlin]

再一次,可能不存在好的解决方案。

java.time,现代 Java 日期和时间 API,允许我们指定用于格式化和解析字段的文本。因此,我在星期几和月份都利用它,指定用于旧 COMPAT 或 JRE 语言环境数据的不带点的缩写。我使用 Java 9 Map.ofMap.ofEntries 来构建我们需要的地图。如果这也适用于 Java 8,您必须找到其他方法来填充这两个映射,我相信您会这样做。

如果您确实需要老式的 java.util.Date(可能在旧代码库中),请像这样转换:

    Date date = Date.from(odt.toInstant());
    System.out.println("As legacy Date: " + date);

在我的时区输出(欧洲/哥本哈根,可能与你的大致一致):

As legacy Date: Mon Aug 18 11:25:26 CEST 2014

策略建议

我在想,如果是我,我会考虑这样做:

  1. 等待。在 Java 中设置相关的系统属性:System.setProperty("java.locale.providers", "COMPAT,CLDR");,这样它就不会在任何环境中被遗忘。 COMPAT 语言环境数据从 1.0 开始就已经存在(我相信,至少很接近),所以很多代码都依赖于它(不仅仅是你的)。在 Java 9 中,名称从 JRE 更改为 COMPAT。对我来说,这听起来像是将数据保留很长一段时间的计划。根据the early access documentation,它仍将在 Java 11(下一个“长期支持”Java 版本)中可用,并且没有弃用警告等。如果在将来的某个 Java 版本中将其删除,您可能很快就会发现您可以在升级之前处理该问题。
  2. 使用我上面的解决方案
  3. 使用 Basil Bourque 链接到的 the locale service provider interface。毫无疑问,这是一个很好的解决方案,以防 COMPAT 数据在未来某个未知时间被删除。您甚至可以将 COMPAT 区域设置数据复制到您自己的文件中,这样他们就无法将它们从您手中夺走,在您这样做之前只需检查是否存在版权问题。我最后提到这个好的解决方案的原因是你说你不满意必须在你的程序可能运行的每个可能的环境中设置系统属性。据我所知,通过区域设置服务提供者接口使用您自己的区域设置数据仍然需要您设置相同的系统属性(仅设置为不同的值)。

【讨论】:

  • “Mar”宁愿写成“Mär”(有时甚至写成“Mrz”)。
  • Danke so sehr,@Meno。你信不信由你,但我在阅读你的评论之前更正了(并且还删除了“请检查我的德语拼写”;不过你仍然可以这样做,永远不知道是否还有更多错误)。
【解决方案2】:

顺便提一下:SimpleDateFormat 是格式化日期的旧方法,顺便说一句,它不是线程安全的。从 Java 8 开始,有名为 java.timejava.time.format 的新包,您应该使用它们来处理日期。出于您的目的,您应该使用DateTimeFormatter 类。

【讨论】:

  • 这是真的,但这是一个巨大的遗留代码库。但是,问题在于使用相同遗留类的 Java 8 和 Java 10 之间的格式差异。
  • 是的,我理解这一点,我理解你在遗留代码和向后兼容性方面的问题。我只是想我提到了这一点,以防万一。
  • 这似乎是我的一个选项。谢谢提示。
【解决方案3】:

java 8 中的格式化值为 Fr Juni 15 00:20:21 MESZ +0900 2018 但它改为神父。 2018 年 6 月 15 日 00:20:21 MESZ +0900 EEE 包括 .这是兼容性问题,旧版本的代码在新版本中不起作用并不重要。(对不起翻译)如果日期字符串是您的,您应该为新版本用户添加点。或者让用户使用 Java 8 来使用您的软件。

可以让软件变慢,使用子串的方法也不错。

    String toParse = "Mo Aug 18 11:25:26 MESZ +0200 2014";
    String str = toParse.substring(0, 2) + "." + toParse.substring(2);
    String pattern = "EEE MMM dd HH:mm:ss z Z yyyy";

    DateFormat dateFormatter = new SimpleDateFormat(pattern, Locale.GERMANY);
    System.out.println(dateFormatter.format(System.currentTimeMillis()));
    Date date = dateFormatter.parse(str);

再次抱歉我的英语不好。

【讨论】:

    【解决方案4】:

    这是一个有效但丑陋的解决方法。这很丑,因为您必须在自己的地图中重新定义所有单词,但您仍然拥有高效灵活的默认解析器的所有好处。

    String dateString = "Mi Mai 09 09:17:24 2018";
    
    Map<Long, String> dayOfWeekTexts =
        Map.of(1L, "Mo", 2L, "Di", 3L, "Mi", 4L, "Do", 5L, "Fr", 6L, "Sa", 7L, "So");
    Map<Long, String> monthTexts =
        Map.ofEntries(
            Map.entry(1L, "Jan"),
            Map.entry(2L, "Feb"),
            Map.entry(3L, "Mär"),
            Map.entry(4L, "Apr"),
            Map.entry(5L, "Mai"),
            Map.entry(6L, "Jun"),
            Map.entry(7L, "Jul"),
            Map.entry(8L, "Aug"),
            Map.entry(9L, "Sep"),
            Map.entry(10L, "Okt"),
            Map.entry(11L, "Nov"),
            Map.entry(12L, "Dez"));
    
    DateTimeFormatter dtf =
        new DateTimeFormatterBuilder()
            .appendText(ChronoField.DAY_OF_WEEK, dayOfWeekTexts)
            .appendLiteral(' ')
            .appendText(ChronoField.MONTH_OF_YEAR, monthTexts)
            .appendPattern(" dd HH:mm:ss yyyy")
            .toFormatter(Locale.GERMAN);
    
    LocalDateTime dateTime = LocalDateTime.parse(dateString, dtf);
    

    这只是来自https://stackoverflow.com/a/50412644/1353930的略微修改的答案

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-15
      • 2019-05-09
      • 2012-05-06
      • 2011-01-23
      • 2015-03-19
      相关资源
      最近更新 更多