java - DateTimeFormatter 基于周年的差异

标签 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()DateTimeFormatter 的差异)。

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

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

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

  3. 我在哪里可以找到这方面的适当文档?来自 Oracle 的明显“官方”文档至少可以说是模糊的。 回答:我找到了更广泛的文档 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 日nd,这是一个星期五。检查 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 开始,到 1 月 4th 结束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

但是如果我考虑一个 WeekFields 实例用于 en_MT locale (English (Malta)) ,一周的第一天是星期天,第一周的最少天数是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 月 4th 到 10th 的那一周。因此,根据 en_MT 语言环境的周定义,2009 年 1 月 2 日nd 属于 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 2009 年 1 月 2 日 ndar_SA 语言环境中也是 2009(与我使用 IsoFields即使周定义与 ISO 完全不同)。


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

根据每个语言环境的定义(一周的第一天和第一周的最少天数),来自本地化模式的基于周的年份(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的模式字母were based on CLDR (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 也已本地化 - 如 Stephen Colebourne's comment 所述下面:

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)) 返回 true)。唯一的问题是区域设置也会影响其他字段(如果您有月份或星期几名称,例如 MMMEEE,以及任何其他区域设置敏感数据).

关于java - DateTimeFormatter 基于周年的差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46389117/

相关文章:

java - 在 Java 中,新建或增强类加载器的用例是什么?

java - 在节电模式 Android 上运行后台服务

java - 使用使用 getter 和 setter 的静态字段与声明为公共(public)的静态字段之间的区别

mysql - 如何将SQL中日期时间字段的默认值设置为当前时间戳加上固定的时间跨度?

java - 不同版本的 JDK 的并发(多线程)实现有哪些变化?

java-8 - 使用过滤器和计数的java 8流

java - 在 Hibernate 中设置连接

java - EasyMock 和测试 protected 方法

delphi - 如何从 TDateTimePicker 读取日期和时间

javascript - 如何将 24 小时添加到 javascript 的 datetime 对象