DateTimeFormatter 基于周年的差异

DateTimeFormatter week-based-year diff

我正在将我的应用程序从 Joda-Time 迁移到 Java 8 java.time.

我 运行 感兴趣的一件事是使用 DateTimeFormatter 中的模式打印基于周的年份。

注意:我看过这个问题:

根据文档

y       year-of-era                 year              2004; 04
Y       week-based-year             year              1996; 96

然而,当我尝试这两个时,似乎 Y 总是返回与 y 相同的结果。

我的测试代码:

DateTimeFormatter yearF = DateTimeFormatter.ofPattern("yyyy").withZone(ZoneOffset.UTC);
DateTimeFormatter weekYearF = DateTimeFormatter.ofPattern("YYYY").withZone(ZoneOffset.UTC);

DateTimeFormatter dateTimeFormatter = new DateTimeFormatterBuilder()
    .appendValue(ChronoField.YEAR_OF_ERA)   .appendLiteral(" ") .append(yearF)
    .appendLiteral(" -- ")
    .appendValue(IsoFields.WEEK_BASED_YEAR) .appendLiteral(" ") .append(weekYearF)
    .toFormatter()
    .withZone(ZoneOffset.UTC);

System.out.println(dateTimeFormatter.toString());

ZonedDateTime dateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(946778645000L), ZoneOffset.UTC);
for (int i = 2000 ; i < 2020; i ++ ) {
    System.out.println(dateTime.withYear(i).format(dateTimeFormatter));
}

输出:

Value(YearOfEra)' '(Value(YearOfEra,4,19,EXCEEDS_PAD))' -- 'Value(WeekBasedYear)' '(Localized(WeekBasedYear,4,19,EXCEEDS_PAD))
2000 2000 -- 1999 2000
2001 2001 -- 2001 2001
2002 2002 -- 2002 2002
2003 2003 -- 2003 2003
2004 2004 -- 2004 2004
2005 2005 -- 2004 2005
2006 2006 -- 2006 2006
2007 2007 -- 2007 2007
2008 2008 -- 2008 2008
2009 2009 -- 2009 2009
2010 2010 -- 2009 2010
2011 2011 -- 2010 2011
2012 2012 -- 2012 2012
2013 2013 -- 2013 2013
2014 2014 -- 2014 2014
2015 2015 -- 2015 2015
2016 2016 -- 2015 2016
2017 2017 -- 2017 2017
2018 2018 -- 2018 2018
2019 2019 -- 2019 2019

查看重要年份(如 2000、2005、2009 和 2016),.appendValue(IsoFields.WEEK_BASED_YEAR).ofPattern("YYYY") 的输出不同。

中指出这与本地化有关(可以清楚地看出DateTimeFormattertoString()的差异)。

现在有几件事我不会understand/need:

  1. 所以 'week-based-year' 随区域设置而变化,很好。然而我不明白的是,显然在某些语言环境中,周基准年始终与 'normal' 年相同。这是为什么?

  2. 为什么 YYYY 的解析没有映射到 ISO-8601 定义而不是(非常混乱!)本地化形式。

  3. 我在哪里可以找到这方面的正确文档?来自 Oracle 的明显 'official' 文档至少可以说是模糊的。 回答:我找到了更广泛的文档 DateTimeFormatterBuilder.

week-based-year 字段,根据 javadoc,取决于两件事:一周的第一天是哪一天,以及最少的天数第一周。

ISO标准定义星期一为一周的第一天,第一周至少有4天:

System.out.println(WeekFields.ISO.getFirstDayOfWeek()); // Monday
System.out.println(WeekFields.ISO.getMinimalDaysInFirstWeek()); // 4

(WeekFields.ISO.weekBasedYear() 等同于 IsoFields.WEEK_BASED_YEAR, 与 minor differences regarding another calendar systems)

例如,考虑 2009 年 1 月 2 日,这是一个星期五。检查 javadoc for the week-based-year field:

Week one(1) is the week starting on the getFirstDayOfWeek() where there are at least getMinimalDaysInFirstWeek() days in the year. Thus, week one may start before the start of the year.

考虑到 ISO 定义(周从星期一开始,第一周的最少天数为 4),第 1 周从 2008 年 12 月 29 日th 到 2008 年 1 月 4 日结束th 2009(这是从星期一开始的第一周,2009 年至少有 4 天),所以 January 2nd 2009 有一个 week-based-year 等于 2009(ISO 定义):

// January 2st 2009
LocalDate dt = LocalDate.of(2009, 1, 2);
System.out.println(dt.get(WeekFields.ISO.weekBasedYear())); // 2009
System.out.println(dt.get(WeekFields.ISO.weekOfWeekBasedYear())); // 1
// WeekFields.ISO and IsoFields are equivalent
System.out.println(dt.get(IsoFields.WEEK_BASED_YEAR)); // 2009
System.out.println(dt.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR)); // 1

但是如果我考虑 en_MT locale (English (Malta))WeekFields 实例,一周的第一天是星期日,第一周的最少天数是 4:

WeekFields wf = WeekFields.of(new Locale("en", "MT"));
System.out.println(wf.getFirstDayOfWeek()); // Sunday
System.out.println(wf.getMinimalDaysInFirstWeek()); // 4
System.out.println(dt.get(wf.weekBasedYear())); // 2008
System.out.println(dt.get(wf.weekOfWeekBasedYear())); // 53

2009年第一个从星期日开始并且至少有4天的一周是从1月4日到10日.因此,根据 en_MT 语言环境的周定义,2009 年 1 月 2nd 属于第 53th 2008 年基于周的年份

现在,如果我取ar_SA locale (Arabic (Saudi-Arabia)),一周从周六开始,第一周的最少天数是1:

WeekFields wf = WeekFields.of(new Locale("ar", "SA"));
System.out.println(wf.getFirstDayOfWeek()); // Saturday
System.out.println(wf.getMinimalDaysInFirstWeek()); // 1
System.out.println(dt.get(wf.weekBasedYear())); // 2009
System.out.println(dt.get(wf.weekOfWeekBasedYear())); // 1

对于此语言环境,第 1 周从 2008 年 12 月 27 日 开始,到 2009 年 1 月 2 日 结束(这是第一周从星期六开始,2009 年至少有 1 天)。因此,week-based-year for January 2nd 2009 in ar_SA locale 也是 2009(与我的值相同开始使用 IsoFields,尽管周定义与 ISO).

完全不同

虽然 IsoFields.WEEK_BASED_YEAR 使用 ISO 的定义,模式 YYYY 将使用 WeekFields 对应于格式化程序中设置的语言环境的实例(或 JVM 默认语言环境,如果 none 已设置)。

根据每个语言环境的定义(一周的第一天和第一周的最少天数),来自本地化模式的 week-based-year (YYYY) 可能具有与 ISO 字段相同(或不同)的值。

虽然一周可以在另一年开始或结束听起来很奇怪,但 javadoc 说这是完全正确的:

The first and last weeks of a year may contain days from the previous calendar year or next calendar year respectively.


java.time (The Unicode Common Locale Data Repository). This link about Week based patterns的模式字母表示:

The year indicated by Y typically begins on the locale’s first day of the week and ends on the last day of the week

无论如何,CLDR 都是关于本地化的,因此 Y 也已本地化 - 如下 所述:

The whole purpose of CLDR is localization, so yes, the "Y" pattern letter is localized. While I understand the desire for a pattern letter that always operates using the ISO rules, it doesn't exist and getting CLDR to add it would be hard to impossible. (Java is following CLDR closely)


我的结论是,如果您想要 ISO 周字段,请不要使用本地化模式。或者,作为一种 - 不理想,非常难看 - 解决方法,使用与 ISO 的周定义相匹配的语言环境(在我的 JVM 中,Locale.FRENCH 可以解决问题,因为 WeekFields.ISO.equals(WeekFields.of(Locale.FRENCH)) returns true).唯一的问题是区域设置也会影响其他字段(如果您有月份或星期几名称,例如 MMMEEE,以及任何其他区域设置敏感数据)。