Java "week year" 是如何运作的?

How does Java "week year" really work?

这是一个简单的错误:我在 SimpleDateFormat 对象的格式字符串中使用 YYYY 而不是 yyyy。但是我对格式字符串不正确的测试结果感到非常困惑。

此代码:

@Test
public void whatTheHell() {
        try {
                SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/YYYY");

                Date d1 = sdf.parse("01/07/2016");
                Date d2 = sdf.parse("02/08/2016");
                Date d3 = sdf.parse("11/29/2027");

                System.out.println(d1.toString());
                System.out.println(d2.toString());
                System.out.println(d3.toString());
        } catch (ParseException pe) {
                fail("ParseException: " + pe.getMessage());
        }
}

产生这个输出:

Sun Dec 27 00:00:00 PST 2015
Sun Dec 27 00:00:00 PST 2015
Sun Dec 27 00:00:00 PST 2026

我已经在此处阅读了有关 'Y' 参数的文档:https://docs.oracle.com/javase/7/docs/api/java/util/GregorianCalendar.html,但我仍然看不到此处的工作逻辑。特别是最后一个例子:我有点理解 1 月(也许是 2 月)的日期如何可以转换为上一年的 12 月,但是将 11 月 29 日的日期向后移动 11 个月让我感到困惑。 12 月 27 日有什么特别之处?

谁能解释一下?

更多信息

@Jan 建议依赖 toString() 方法可能会出现问题,因此我定义了一个日期格式以在与上面相同的代码中打印 YYYY MM dd '-' yyyy MM dd。这是额外的输出:

2016 12 27 - 2015 12 27
2016 12 27 - 2015 12 27
2027 12 27 - 2026 12 27

很简单:2015 年 12 月 27 日是 week-year 2016 年第 1 周的第 1 天(2026 年 12 月 27 日是 week-year 2027 年第 1 周的第 1 天)。这可以通过添加以下行来验证:

SimpleDateFormat odf = new SimpleDateFormat("YYYY-ww-u");
System.out.println(odf.format(d1));
System.out.println(odf.format(d2));
System.out.println(odf.format(d3));

如果 SimpleDateFormat 输出一个日期,它可以使用所有字段:年、月、日、星期几、月中的周、年中的周,week-year 等

在解析时,SimpleDateFormat 需要一组匹配的值:日、月、年 星期几、一年中的星期 week-year .由于您提供了 week-year 但没有提供星期几和一年中的星期几,因此这些值被假定为 1.


实际值取决于您的语言环境:

  • 一年中的第 1 周
  • 哪一天是一周的第一天

(参见 https://docs.oracle.com/javase/7/docs/api/java/util/GregorianCalendar.html#week_and_year

在我的系统上(使用 de-ch 语言环境,格式为 "EEE MMM dd HH:mm:ss zzz yyyy - YYYY-ww-u")我得到

Mo Jan 04 00:00:00 MEZ 2016 - 2016-01-1
Mo Jan 04 00:00:00 MEZ 2016 - 2016-01-1
Mo Jan 04 00:00:00 MEZ 2027 - 2027-01-1

定义不同

可以用不同的方式定义一周。例如,在美国,一周的第一天通常被认为是星期天,而在欧洲和许多其他地方,一周的第一天是星期一。

同样,week-based年的星期也可以用不同的方式定义。

  • 第 1 周包含 1 月 1 日。
  • 第 1 周是包含特定 day-of-week(例如星期日)的第一周。
  • 第 1 周是第一周,只包含新年的几天,没有上一年的日期。
  • ……还有更多

您正在使用的遗留 classes 隐含地使用 Locale 指定的定义。所应用的语言环境也是隐式的,如果您没有另外指定,则使用 JVM 当前的默认值 Locale

有关定义一周的困难的更多有趣细节,请参阅此问题,

ISO 8601

date-time处理有实用的国际标准,ISO 8601

ISO 8601 definition of a week是:

  • 每周从星期一开始
  • 第 1 周是日历年的第一个星期四
  • 一年由 52 或 53 个整周组成。
  • 一年可能有前一个日历年的一天或多天,也可能有下一个日历年的一天。

我建议尽可能使用 ISO 8601 标准定义。该标准定义简单且合乎逻辑,并在各行业中得到越来越多的采用。

java.time

java.time classes 为 WeekFields week-based 年的一周提供一些支持 class.

LocalDate ld = LocalDate.of( 2019 , Month.JANUARY , 1 ) ;
long week = ld.get( WeekFields.ISO.weekOfWeekBasedYear() ) ;

看到这个code run live at IdeOne.com

ld.toString(): 2019-01-01

week: 1

org.threeten.extra.YearWeek

但如果要完成大部分工作,我建议添加 ThreeTen-Extra library to your project. You will find the YearWeek class 以提供帮助。 class 提供了几种方便的方法,例如为该周内的任何一天生成 LocalDate

LocalDate ld = LocalDate.of ( 2019 , Month.JANUARY , 1 );
YearWeek yw = YearWeek.from ( ld );
LocalDate startOfWeek = yw.atDay ( DayOfWeek.MONDAY );

ld.toString(): 2019-01-01

yw.toString(): 2019-W01

startOfWeek.toString(): 2018-12-31

请注意 2019 年 week-based 年的第一天是上一日历年 2018 年而不是 2019 年的日期。


关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

您可以直接与数据库交换 java.time 对象。使用 JDBC driver compliant with JDBC 4.2 或更高版本。不需要字符串,不需要 java.sql.* classes.

在哪里获取 java.time classes?