【问题标题】:How does Java "week year" really work?Java“周年”是如何工作的?
【发布时间】:2016-04-13 23:51:16
【问题描述】:

这开始是一个简单的错误:我的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

【问题讨论】:

  • 依靠 toString() 来呈现有用的东西也是乐观的。您可能希望使用不同的 DateFormat 进行输出。像一个同时包含 yyyy 和 YYYY 的?
  • 年与周年的解释见here
  • @theo 谢谢,但这仅说明日期在一年中的第一周或最后一周时的变化,这仅可能是我的三个示例中的第一个。
  • @Jan:好主意,但是使用格式字符串为yyyy YYYY 的格式会得到一致的结果:#1 和#2 都给出2016 2015,而#3 给出2027 2026。这不会让我们到任何地方。
  • 但是输出的 DateFormat 是否确认日期是 12 月 27 日?

标签: java simpledateformat


【解决方案1】:

很简单:2015 年 12 月 27 日是 2016 年第 1 周的第 1 天(2026 年 12 月 27 日是 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 输出一个日期,它可以使用所有字段:年、月、日、星期几、月中的星期、年中的星期、年中的星期等。

在解析时,SimpleDateFormat 需要一组匹配的值:日、月、年星期几、一年中的星期、星期几。由于您提供了一年中的一周,但没有提供一年中的星期几和星期,因此这些 to 值已假定为 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

【讨论】:

  • 否则是正确的,但“and of week-year 2027”肯定是错误的。修复那部分答案。 2015 年对应于 2027 年是没有意义的。
  • 不太正确:u 在所有这些情况下的输出都是 7,而不是 1。但除此之外是正确的:解析确实完全忽略了为 MMdd 提供的值.我找不到有关此行为的任何文档。
  • @DaveMulligan 感谢您的反馈。实际值取决于您的系统区域设置,因此我增强了我的答案。
  • 仅供参考,诸如 java.util.Datejava.util.Calendarjava.text.SimpleDateFormat 等非常麻烦的日期时间类现在是 legacy,被 Java 8 及更高版本中内置的 java.time 类所取代.见Tutorial by Oracle
【解决方案2】:

定义各不相同

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

同样,也可以用不同的方式定义以周为单位的一年中的哪一周。

  • 第 1 周包含 1 月 1 日。
  • 第 1 周是包含特定星期几(例如星期日)的第一周。
  • 第 1 周是第一周,仅包含新年的几天,没有上一年的日期。
  • ……等等

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

有关定义一周难度的更多有趣细节,请参阅此问题,Different behavior of WeekFields on JVM 8 and JVM 10

ISO 8601

日期时间处理有一个实用的国际标准,ISO 8601

ISO 8601 definition of a week 是:

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

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

java.time

java.time 类在 WeekFields 类中为基于周的年份提供了一些支持。

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

周:1

org.threeten.extra.YearWeek

但如果要完成大部分工作,我建议将ThreeTen-Extra 库添加到您的项目中。您会发现 YearWeek 课程很有帮助。此类提供了几种方便的方法,例如为该周内的任何一天生成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 年的第一天是从上一个日历年 2018 年开始的日期,而不是 2019 年。


关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

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

从哪里获取 java.time 类?

【讨论】:

    猜你喜欢
    • 2023-03-29
    • 2013-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-29
    • 2020-05-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多