我正在将应用程序从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()DateTimeFormatter中的差异)。

现在有几件事我不了解/不需要:


因此,“以周为基础的年份”随区域设置的不同而不同。但是,我不了解的是,在某些语言环境中,以周为基准年显然总是与“正常”年份相同。这是为什么?
为什么没有将YYYY的解析映射到ISO-8601定义,而不是(非常混乱!)本地化形式。
在哪里可以找到适当的文档?至少可以说,Oracle提供的明显的“官方”文档含糊不清。
答:我发现有关
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,并带有minor differences regarding another calendar systems

例如,以2009年1月2日为星期五。检查javadoc for the week-based-year field


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


考虑到ISO的定义(每周从星期一开始,第一周的最少天数是4),第1周从2008年12月29日开始,到2009年1月4日结束(这是从星期一开始的第一周,并且至少有4天在2009),因此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


但是,如果我考虑WeekFields locale (English (Malta))en_MT实例,则一周的第一天是星期日,而第一周的最小天数是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月2日属于基于周的年份2008的第53周。

现在,如果我使用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天)。因此,2009年1月2日在ar_SA区域设置中的基于周的年份也是2009(即使周定义与ISO完全不同,我使用IsoFields所获得的值也相同)。



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

根据每个语言环境的定义(一周的第一天和第一周的最少天数),本地化模式(YYYY)中基于周的年的ISO字段值可能相同(或不相同) 。

尽管一个星期可以在另一年开始或结束听起来很奇怪,但javadoc说它是完全有效的:


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




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


Y表示的年份通常从语言环境的一周的第一天开始,到一周的最后一天结束


无论如何,CLDR都与本地化有关,因此Y也已本地化-如以下Stephen Colebourne's comment所述:


CLDR的全部目的是本地化,因此,是的,“ Y”模式字母已本地化。虽然我了解对希望始终使用ISO规则运行的模式字母的渴望,但它并不存在,要添加CLDR很难甚至不可能。 (Java紧紧追随CLDR)




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

09-27 06:10