我这里有一个非常简单的程序:
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
为什么会这样?我做了什么,可能会导致未定义的行为?或者是否在指定某处的行为发生了变化?
ZHVM10:
$ 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
的后备,current CLDR data只是一种语言,而不是一个国家。
system property列表为后备国家/地区“001”(=全球)周定义(星期一为星期几,1 =日历年第一周的最小天数)。令人惊讶的是,这与美国定义(星期日,1)不同。我认为,Oracle刚刚做了自己的事情。就个人而言,我同意@Holger,而是期望ISO-8601作为后备(星期一,4)。
但是,您可以通过设置以下java.locale.providers=COMPAT,CLDR,SPI
(未测试)来恢复JVM-10计算机上的Java-8行为:
Locale
GERMAN
枚举区分了对语言有用的实例(如GERMANY
)和对国家有用的实例(如Locale
)。如果要设置不同的lang设置并保留本地WeekFields.ISO
,请使用第一个,另一方面使用后者设置时间和语言设置。
以下两个选项是等效的。选择最适合您情况的那个。
WeekFields.of(Locale.GERMANY)
java.locale.providers
使用国家,德国,而不是语言,德语。这里有两个不同之处:
不同语言环境中周计划的定义是语言环境数据的一部分。 Java可以从多达四个来源获取其语言环境数据。 Java包含了早期版本中自己的区域设置数据,这些是Java 8的默认数据。从Java 8 CLDR(Unicode公共区域设置数据存储库)数据也被包含在内,这些数据成为Java 9的默认值。这显然改变了一些正如您所经历的那样,功能和破坏了一些旧代码。更准确地说,默认值是:
可以通过设置系统属性COMPAT,SPI
来覆盖默认值。因此,我们可以通过将此属性设置为CLDR,JRE
来获取Java 9及更高版本中的Java 8行为。相反,我们可以通过将其设置为Locale.GERMAN
来获取Java 8中的Java 10行为。所以在它的基础上,这不是Java版本之间的区别,只是它们的默认值之间。
从Java到CLDR数据的更改是这样的:Java语言环境数据根据语言的主要位置将周定义分配给仅语言区域设置(如德语)。相比之下,CLDR的理念是,您可以在世界上任何一个国家/地区使用任何语言,而您更愿意将周计划的选择基于国家而非语言。因此,未指定国家/地区(如德语)的区域设置都使用全球默认周定义。
为什么全球默认周定义是CLDR中的“Sunday,1”我不明白。正如其他人所期望的那样,并且更喜欢国际标准ISO,“星期一,4”。正如我在评论中所说,我还发现了一个说明应该是这样的说明,但它仍然不是(至少不是在Java 8到11中使用的CLDR版本中)。
正如您所观察到的,在Java 9上使用默认语言环境数据,即使CLDR应该是第一个默认值,您也会从java.locale.providers
获得“Monday 4”。另一方面,如果我将CLDR
单独设置为LocaleServiceProvider
,我会在Java 10和11中获得“Sunday 1”。
可能的解释是Java 9中使用的CLDR版本不包括德语的周定义。因此,使用默认提供程序CLDR,COMPAT,Java可以使用COMPAT,它为德语提供“Monday,4”。当我单独使用CLDR时,它会回到全球基本默认值“星期日,1”。如果这个解释是正确的(我无法保证),那么Java 10和11中使用的CLDR数据版本似乎包含德语的周定义。
Java 8的文档,包含有关区域设置数据提供程序和默认提供程序规范的信息:
CLDR链接: