具有德语语言环境的SimpleDateFormat - Java 8与Java 10+

时间:2018-05-18 12:10:04

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

我在传统应用程序中有代码和测试用例,可归纳如下:

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

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

  

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

     

在JDK 8中,尽管CLDR区域设置数据与JRE捆绑在一起,但默认情况下不会启用它。

     

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

为一周中的某一天添加​​.Mo.)会对此进行补偿并且测试将通过。但是,这不是旧数据的真正解决方案(以XML等序列化形式)。

检查此stackoverflow post,似乎该行为是德国语言环境的故意,可以通过java.locale.providers模式指定COMPAT来缓解此问题。但是,我不喜欢依赖于某些系统属性值的想法有两个原因:

  1. 更改JDK的下一版本。
  2. 在不同的环境中被遗忘。
  3. 我的问题是:

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

4 个答案:

答案 0 :(得分:18)

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

    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。对我而言,这可能听起来像是一个保持数据相当长一段时间的计划。根据{{​​3}},它仍然可以在Java 11(下一个“长期支持”Java版本)中使用,并且没有弃用警告等。如果在将来的某个Java版本中将其删除,您可能很快就会发现可以在升级之前处理该问题。
  2. 使用我的解决方案
  3. 使用Basil Bourque链接的the early access documentation 。毫无疑问,如果将来某个未知时间删除COMPAT数据,这是一个很好的解决方案。您甚至可以将COMPAT语言环境数据复制到您自己的文件中,这样他们就无法将它们从您身边带走,只有在您执行此操作之前,才能检查是否存在版权问题。我之前提到最好的解决方案的原因是你说你不满意必须在程序可能运行的每个可能环境中设置系统属性。据我所知,通过语言环境服务提供程序接口使用您自己的语言环境数据仍然需要您设置相同的系统属性(仅限于不同的值)。

答案 1 :(得分:1)

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

答案 2 :(得分:0)

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

它可以使软件变慢,使用substring方法也很好。

    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);

再次抱歉我的英语不好。

答案 3 :(得分:0)

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

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

稍作修改的答案