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

时间:2019-03-13 10:14:31

标签: java java-8 jvm java-10 localdate

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

 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,并且很好奇为什么更改了它。

3 个答案:

答案 0 :(得分:10)

这样的星期字段是高度本地化的,因此取决于基础JVM的本地化资源,该资源可以从一个发行版更改为另一个发行版。

我认为JVM10更正确,因为Locale.GERMAN并没有指向任何国家,因此Java简单地假定了美国(将这个国家作为世界标准来处理是有问题的,而Java也是如此)。

您最好使用 Locale.GERMANY。该国家确实确实将星期一作为一周的第一天(与美国从星期日开始相反,后者被用作GERMAN的备用,java.locale.providers=COMPAT,CLDR,SPI 只是一种语言,而不是一个国家。

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

后备国家/地区“ 001”(=全球)的current CLDR data列表中的周定义(星期一为一周的第一天,1 =日历年第一周的最少天)。令人惊讶的是,这与美国定义不同(星期日,1)。我认为,Oracle只是做了自己的事。就个人而言,我同意@Holger的看法,而是希望将ISO-8601作为后备广告(星期一,4)。

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

com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String "2019-03-11T10:14:14Z[UTC]": not a valid representation (error: Failed to parse Date value '2019-03-11T10:14:14Z[UTC]': Cannot parse date "2019-03-11T10:14:14Z[UTC]": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSZ', parsing fails (leniency? null))

答案 1 :(得分:4)

Locale枚举区分对语言有用的实例(如GERMAN)和对国家有用的实例(如GERMANY)。如果要设置其他语言设置并保留本地Locale,请使用前者,另一方面,请使用后者来设置时间和语言设置。

答案 2 :(得分:3)

如何修复

以下两个选项是等效的。选择最适合自己情况的一个。

  • 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自己的语言环境数据。
  • Java 9、10和11:CLDR,COMPAT,其中所说的CLDR和COMPAT只是JRE数据的新名称。

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

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

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

Java 9很特别

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

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

链接

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

CLDR链接: