【问题标题】:DateTimeFormatter week-based-year diffDateTimeFormatter 基于周的年份差异
【发布时间】:2018-03-05 11:21:45
【问题描述】:

我正在将我的应用程序从 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 的区别)。

现在我不明白/不需要的东西很少:

  1. 因此,“基于周的年份”会随区域设置而变化,很好。然而,我不明白的是,在某些语言环境中,周基准年总是与“正常”年相同。这是为什么呢?

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

  3. 我在哪里可以找到这方面的适当文档?至少可以说,来自 Oracle 的明显“官方”文档含糊不清。 Answer:我发现了更广泛的文档 DateTimeFormatterBuilder.

【问题讨论】:

  • 对于您的第一个问题,02-01-2000 仍然是 1999 年的第 52 周。这让您回到了 1999 年。2005 年和 2016 年也是如此。
  • @YoshuaNahar:我的意思是我希望年份和基于周的年份每隔几年就会有所不同。我很惊讶,显然在某些语言环境中没有区别。

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


【解决方案1】:

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

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 日nd,这是一个星期五。检查javadoc for the week-based-year field

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

考虑到 ISO 定义(一周从星期一开始,第一周最少有 4 天),第 1 周从 2008 年 12 月 29 日th 到 2008 年 1 月 4 日结束th 2009 年(即从星期一开始的第一周,2009 年至少有 4 天),因此 2009 年 1 月 2 日ndweek-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 日th 到 10th 的那一周。因此,根据 en_MT 语言环境的周定义,2009 年 1 月 2 日nd 属于 周的第 53 - 基于年份 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 到 2009 年 1 月 2 日nd 结束(这是从星期六开始的第一周,并且至少有2009 年 1 天)。因此,ar_SA 语言环境中 2009 年 1 月 2 日ndweek-based-year 也是 2009 年(我使用 IsoFields 得到的值相同,即使周定义与 ISO 完全不同)。


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

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

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

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


java.timewere 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,以及任何其他区域设置敏感数据)。

【讨论】:

  • CLDR 的全部目的是本地化,所以是的,“Y”模式字母是本地化的。虽然我理解对始终使用 ISO 规则运行的模式字母的需求,但它并不存在,并且让 CLDR 添加它很难甚至是不可能的。 (Java 密切关注 CLDR)。然而,这里的关键是,与 Java 7 不同,可以创建一个格式化程序来通过 DateTimeFormatterBuilder 处理这个用例。
  • @JodaStephen 我已经更新了答案(如果您不介意,请引用您的评论)。非常感谢!
  • 提示:如果确实需要标准的ISO 8601 weeks,请参阅ThreeTen-Extra 项目的YearWeek 类。
猜你喜欢
  • 2018-06-24
  • 2017-01-27
  • 2021-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多