DateTimeFormatter基于周的年份差异

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

我遇到的一件事是使用DateTimeFormatter的模式打印基于周的年份。

注意:我已经看到了这个问题: 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")是不同的。

在Java Time使用DateTimeFormatter进行的基于星期的周模式解析中,声明这与本地化有关(可以清楚地看作DateTimeFormattertoString()的差异)。

现在有一些我不理解/不需要的东西:

  1. 因此,“以周为基础的年份”因Locale而异,很好。 然而我不明白,显然在一些Locales中,周基准年总是与“正常”年相同。 这是为什么?

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

  3. 我在哪里可以找到适当的文件? 甲骨文的明显“官方”文件至少可以说含糊不清。 :我使用DateTimeFormatterBuilder找到了更广泛的文档。

根据javadoc ,以周为基础的字段取决于两件事:一周的第一天是什么,以及第一周的最小天数。

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

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

WeekFields.ISO.weekBasedYear()等同于IsoFields.WEEK_BASED_YEAR ,与其他日历系统有细微差别 )

例如,考虑到2009年1月2日,这是一个星期五。 检查基于周的年份字段的javadoc :

第一周(1)是从getFirstDayOfWeek()开始的一周,其中一年中至少有getMinimalDaysInFirstWeek()天。 因此,第一周可以在年初之前开始。

考虑到ISO定义(周一周开始,第一周最短天数为4),第1周从2008年12月29 开始,到2009年1月4 结束(这是第一周从周一开始,至少有4周) (2009年1月2日),因此2009年1月2日的基于周的年份等于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语言环境的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语言环境的en_MT ,2009年1月2日属于2008年的第53周

现在,如果我采用ar_SA语言环境(阿拉伯语(沙特阿拉伯)) ,则星期六开始,第一周的最小天数为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天)。 因此,2009 1月2日在ar_SA语言环境中的基于星期的年份也是2009年(与使用IsoFields的情况相同, 即使 IsoFields 与ISO完全不同 )。


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

根据每个区域设置的定义(一周的第一天和第一周的最小天数),来自本地化模式( YYYY的基于周的年份可能具有与ISO字段相同的值(或不具有)。

虽然听起来很奇怪,一周可以在另一年开始或结束,但javadoc说它完全有效:

一年的第一周和最后一周可能分别包含上一个日历年或下一个日历年的天数。


java.time的模式字母基于CLDR ( Unicode公共区域设置数据存储库 )。 这个关于基于周的模式的链接说:

Y表示的年份通常从当地的第一天开始,到一周的最后一天结束

无论如何,CLDR完全是关于本地化的,所以Y也是本地化的 – 正如Stephen Colebourne在下面的评论所述:

CLDR的整个目的是本地化,所以是的,“Y”模式字母是本地化的。 虽然我理解对使用ISO规则的模式字母的渴望,但它不存在并且让CLDR添加它很难是不可能的。 (Java密切关注CLDR)


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