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 9
与 JVM 8
具有相同的行为,而 JVM 11
与 JVM 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 和国家/地区与语言
这里有两个区别:
- 不同 Java 版本中的不同默认区域设置数据。
- 正如其他人所说,纯语言区域设置与包含国家/地区的区域设置之间的区别。
不同语言环境中周计划的定义是语言环境数据的一部分。 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 链接:
- CLDR - Unicode Common Locale Data Repository home page
- Reasons for Decisions: 2012-02-22 for CLDR 21.0.1 per #993 (reopened), restore firstDay for IE (Ireland) to be Sunday 提到“(因此默认为星期一,001 = 世界的默认第一天)”,这似乎表明如果未指定国家/地区,星期一应该是默认的一周第一天。
我这里有非常简单的程序:
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 9
与 JVM 8
具有相同的行为,而 JVM 11
与 JVM 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 和国家/地区与语言
这里有两个区别:
- 不同 Java 版本中的不同默认区域设置数据。
- 正如其他人所说,纯语言区域设置与包含国家/地区的区域设置之间的区别。
不同语言环境中周计划的定义是语言环境数据的一部分。 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 链接:
- CLDR - Unicode Common Locale Data Repository home page
- Reasons for Decisions: 2012-02-22 for CLDR 21.0.1 per #993 (reopened), restore firstDay for IE (Ireland) to be Sunday 提到“(因此默认为星期一,001 = 世界的默认第一天)”,这似乎表明如果未指定国家/地区,星期一应该是默认的一周第一天。