DateTimeFormatter基于周的年份差异

时间:2017-09-24 10:36:36

标签: java datetime java-8 java-time dayofweek

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

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

注意:我看过这个问题: Java Time's week-of-week-based-year pattern parsing with 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's week-of-week-based-year pattern parsing with DateTimeFormatter中,声明这与本地化有关(可以清楚地看作toString()的{​​{1}}的差异。)

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

  1. 因此,以周为基础的年份&#39;因地区而异,很好。然而我不明白的是,显然在某些Locales中,周基准年始终与正常情况相同[&#39;年。那是为什么?

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

  3. 我在哪里可以找到适当的文件?明显的官方&#39;来自Oracle的文档至少可以说含糊不清。 回答:我发现了更广泛的文档 DateTimeFormatterBuilder

1 个答案:

答案 0 :(得分:6)

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

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

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

WeekFields.ISO.weekBasedYear()相当于IsoFields.WEEK_BASED_YEARminor differences regarding another calendar systems

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

  

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

考虑到ISO定义(周一周开始,第一周最小天数为4),第1周从2008年12月29日 th 开始,到1月4日结束 th 2009年(这是星期一开始的第一周,2009年至少有4天),所以1月2日 nd 2009有一个以周为基础的年份等于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日 th 到10 th 的那一周。因此,根据en_MT区域设置的周定义,1月2日 nd 2009属于 53 th >基于周的年度 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日 th 开始,到1月2日结束 nd 2009(这是星期六开始的第一周)并且在2009年至少有1天)。因此,ar_SA区域内的1月2日 nd 2009的基于周的年度也是2009年(我使用{{的相同价值1}},即使周定义与ISO 完全不同。)

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

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

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

  

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

YYYY were based on CLDRUnicode Common Locale Data Repository)的模式字母。 This link about Week based patterns说:

  

Y指示的年份通常从区域设置的一周的第一天开始,到一周的最后一天结束

无论如何,CLDR完全是关于本地化的,所以java.time也是本地化的 - 正如下面的Stephen Colebourne's comment所述:

  

CLDR的全部目的是本地化,是的,&#34; Y&#34;模式字母已本地化。虽然我理解对一直使用ISO规则运行的模式字母的渴望,但它并不存在,并且让CLDR添加它很难是不可能的。 (Java密切关注CLDR)

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