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

2022-09-03 01:18:41

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

 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 8JVM 11JVM 10

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


答案 1

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

我认为JVM10更正确,因为它不是指任何国家,所以Java简单地假设美国(以某种方式将这个国家作为世界标准处理是有问题的,但Java也是如此)。Locale.GERMAN

您应该更好地使用 .该国确实使用星期一作为一周的第一天(与美国从星期日开始相反,后者被用作后备,它只是一种语言而不是一个国家。Locale.GERMANYGERMAN

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

后备国家/地区“001”(= 全球)周定义的当前 CLDR 数据列表(星期一作为一周的第一天,1 = 日历年中第一周的最少天数)。令人惊讶的是,这与美国的定义(星期日,1)不同。我认为,甲骨文刚刚做了自己的事情。就个人而言,我同意@Holger,而是期望ISO-8601作为后备方案(星期一,4)。

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

java.locale.providers=COMPAT,CLDR,SPI

答案 2

如何修复

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

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

为什么会发生这种情况?CLDR 和国家/地区与语言

这里有两个区别:

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

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

  • Java 8:JRE,SPI,其中JRE指的是Java自己的语言环境数据。
  • Java 9,10和11:CLDR,COMPAT,其中CLDR是所说的,COMPAT只是JRE数据的新名称。

可以通过设置 system 属性 来覆盖默认值。因此,我们可以在 Java 9 及更高版本中将此属性设置为 .相反,我们可以通过将 Java 8 中的 Java 10 行为设置为 .因此,在其基础上,这并不是Java版本之间的差异,而只是其默认值之间的差异。java.locale.providersCOMPAT,SPICLDR,JRE

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

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

Java 9 是特别的

正如您所观察到的,在具有缺省区域设置数据的 Java 9 上,即使 CLDR 应该是第一个缺省值,您也可以从中获得“Monday 4”。另一方面,如果我设置为单独使用,我确实会得到“Sunday 1”,就像Java 10和11一样。Locale.GERMANjava.locale.providersCLDR

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

链接

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

链接:


推荐