SimpleDateFormat with German Locale - Java 8 vs Java 10+

2022-08-31 20:40:13

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

@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_DEde_CHde_ATde_LU

我知道这样一个事实,即JDK 9JEP 252)更改了日期格式。但是,我认为这是一个破坏性的更改,破坏了向后兼容性。摘录:

在 JDK 9 中,Unicode 联盟的公共区域设置数据存储库 (CLDR) 数据被启用为默认区域设置数据,以便您可以使用标准区域设置数据而无需任何进一步的操作。

在 JDK 8 中,尽管 CLDR 语言环境数据与 JRE 捆绑在一起,但默认情况下不启用它。

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

在星期几 () 中添加 a 可补偿这一点,测试将通过。但是,这不是旧数据(以序列化形式,如 XML)的真正解决方案。.Mo.

检查此堆栈溢出帖子,似乎该行为是德语区域设置的故意行为,可以通过指定 with mode 来缓解。但是,我不喜欢依赖某些系统属性值的想法,因为它可能有两个原因:java.locale.providersCOMPAT

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

我的问题是:

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

答案 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并构建了我们需要的地图。如果这也要在Java 8中工作,你必须找到一些其他方法来填充这两个映射,我相信你会这样做。Map.ofMap.ofEntries

如果您确实需要一个老式的(可能在旧代码库中),请像这样转换: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中设置相关的系统属性:因此在任何环境中都不会忘记它。COMPAT 区域设置数据自 1.0 以来就已经存在了(我相信,至少是接近的),所以很多代码都依赖于它(不仅仅是你的)。在Java 9中,名称从JRE更改为COMPAT。对我来说,这听起来像是一个将数据保留相当长一段时间的计划。根据早期访问文档,它仍然可以在Java 11(下一个“长期支持”Java版本)中使用,并且没有弃用警告或类似警告。如果它在未来的Java版本中被删除,您可能很快就能找到可以在升级之前处理该问题的方法。System.setProperty("java.locale.providers", "COMPAT,CLDR");
  2. 使用上面的解决方案
  3. 使用 Basil Bourque 链接到的区域设置服务提供程序接口。毫无疑问,如果COMPAT数据将来应该在某个未知的时间被删除,这是一个不错的解决方案。您甚至可以将 COMPAT 区域设置数据复制到您自己的文件中,这样他们就无法将其从您身边带走,只有在执行此操作之前检查是否存在版权问题。我最后提到这个不错的解决方案的原因是,你说你不满意必须在程序可能运行的每个可能环境中设置系统属性。据我所知,通过区域设置服务提供程序接口使用您自己的区域设置数据仍然需要您设置相同的系统属性(仅设置为不同的值)。

答案 2

值得一提的是:是一种格式化日期的旧方法,BTW不是线程安全的。从Java 8开始,有一些名为和的新包,您应该使用它们来处理日期。出于您的目的,您应该使用类DateTimeFormatterSimpleDateFormatjava.timejava.time.format


推荐