WeekFields 在 JVM 8 和 JVM 10 上的不同行为

Different behavior of WeekFields on JVM 8 and JVM 10

我这里有非常简单的程序:

 public static void main(String[] args) {
        LocalDate year = LocalDate.ofYearDay(2022, 100);
        System.out.println(year);

        System.out.println(WeekFields.of(Locale.GERMAN).weekOfYear());

        System.out.println(year.with(WeekFields.of(Locale.GERMAN).weekOfYear(), 0));
        System.out.println(year.with(WeekFields.of(Locale.GERMAN).weekOfYear(), 0).with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)));
    }

但它在 JVM 8 和 JVM 10 上的行为不同。问题似乎是 WeekFields.of(Locale.GERMAN).weekOfYear().

的实现

在 JVM 10 上,我得到以下结果:

JVM 10

2022-04-10
WeekOfYear[WeekFields[SUNDAY,1]]
2021-12-19
2021-12-13

而在 JVM 8 上:

JVM 8

2022-04-10
WeekOfYear[WeekFields[MONDAY,4]]
2022-01-02
2021-12-27

为什么会这样?我在做什么,可能会导致未定义的行为吗?或者这种行为变化是否在某处指定?

JVM10:

$ java -version
openjdk version "10.0.2" 2018-07-17
OpenJDK Runtime Environment (build 10.0.2+13-Ubuntu-1ubuntu0.18.04.4)
OpenJDK 64-Bit Server VM (build 10.0.2+13-Ubuntu-1ubuntu0.18.04.4, mixed mode)

JVM8

$ java -version
openjdk version "1.8.0_191"
OpenJDK Runtime Environment (build 1.8.0_191-8u191-b12-2ubuntu0.18.04.1-b12)
OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)

编辑: JVM 9JVM 8 具有相同的行为,而 JVM 11JVM 10

具有相同的行为

编辑 2: 我实际上找到了改变行为的提交 -> here on github 我很好奇为什么会这样。

这样的星期字段是高度本地化的,因此依赖于底层 JVM 的本地化资源,这些资源可以从一个版本更改到另一个版本。

我认为 JVM10 更正确,因为 Locale.GERMAN 没有指代任何国家,所以 Java 简单假设美国(将这个国家作为世界标准处理有点问题,但 Java).

你最好使用 Locale.GERMANY。该国家/地区确实使用星期一作为一周的第一天(与美国从星期日开始作为对比,后者用作 GERMAN 的后备,这只是一种语言而不是一个国家/地区。

更新 - 我对 CLDR 数据的研究:

后备 country/territory“001”(= 全球)周定义的 current CLDR data 列表(星期一作为一周的第一天,1 = 日历年第一周的最少天数)。令人惊讶的是,这与美国的定义不同(星期日,1 日)。我认为,甲骨文只是做了自己的事情。就个人而言,我同意@Holger 的观点,而是希望 ISO-8601 作为后备(星期一,4)。

但是,您可以通过设置以下 system property(未测试)来恢复 JVM-10 机器上的 Java-8 行为:

java.locale.providers=COMPAT,CLDR,SPI

Locale 枚举区分对语言有用的实例(如 GERMAN)和对国家/地区有用的实例(如 GERMANY)。如果您想设置不同的语言设置并保留本地 Locale,请使用第一个,另一方面,请使用后者来设置时间和语言设置。

如何修复

以下两个选项是等价的。选择您认为最适合您的情况。

  • WeekFields.ISO
  • WeekFields.of(Locale.GERMANY) 使用国家德国,而不是语言德语。

为什么会这样? CLDR 和国家/地区与语言

这里有两个区别:

  1. 不同 Java 版本中的不同默认区域设置数据。
  2. 正如其他人所说,纯语言区域设置与包含国家/地区的区域设置之间的区别。

不同语言环境中周计划的定义是语言环境数据的一部分。 Java 最多可以从四个来源获取其语言环境数据。 Java 包含早期版本中自己的区域设置数据,这些是 Java 8 之前的默认设置。从 Java 8 开始,CLDR(Unicode 公共区域设置数据存储库)数据也包含在内,这些成为 Java 9 的默认值。正如您所经历的,这显然改变了一些功能并破坏了一些旧代码。更准确地说,默认值是:

  • Java 8: JRE,SPI 其中JRE指的是Java自己的locale数据。
  • Java 9、10 和 11:CLDR,COMPAT,其中 CLDR 是原话,COMPAT 只是 JRE 数据的新名称。

可以通过设置系统 属性 java.locale.providers 来覆盖默认值。因此,我们可以通过将此 属性 设置为 COMPAT,SPI 来获得 Java 9 和更高版本中的 Java 8 行为。相反,我们可以通过将 Java 8 设置为 CLDR,JRE 来获得 Java 10 行为。因此,从根本上讲,Java 版本之间并没有太大区别,只是它们的默认设置之间存在差异。

从 Java 到 CLDR 数据 的变化是这样的:Java 语言环境数据将周定义分配给基于语言的语言环境(如德语)在主要使用该语言的地方。相比之下,CLDR 的理念是您可以在世界上任何国家/地区使用任何语言,并且您宁愿根据国家/地区而不是语言来选择周计划。因此,未指定国家/地区的语言环境(如德语)都使用全球默认周定义。

为什么全球默认的星期定义在 CLDR 中是“星期日,1”我不明白。与其他人一样,我会期望并更喜欢国际标准 ISO,即“星期一,4”。正如我在评论中所说,我还发现一条注释说应该是这种情况,但事实并非如此(至少在 Java 8 到 11 中使用的 CLDR 版本中不是)。

Java9特别

如您所见,在 Java 9 上使用默认语言环境数据,您从 Locale.GERMAN 获得“星期一 4” 即使 CLDR 应该是第一个默认值。另一方面,如果我单独将 java.locale.providers 设置为 CLDR,我会得到“星期日 1”,如 Java 10 和 11。

一个可能的解释是 Java9 中使用的 CLDR 版本不包括德语的星期定义。因此,使用默认提供程序 CLDR,COMPAT, Java 后退到 COMPAT,它为德语提供“星期一,4”。当我单独使用 CLDR 时,它会退回到全球基本默认值“Sunday, 1”。如果这个解释是正确的(我不能保证),Java 10 和 11 中使用的 CLDR 数据版本似乎包含德语的星期定义。

链接

LocaleServiceProvider 的文档,其中包含有关区域设置数据提供程序和默认提供程序规范的信息:

CLDR 链接: