理解java.util.Calendar的WEEK_OF_YEAR

10

我尝试理解java.util.Calendar.get(java.util.Calendar.WEEK_OF_YEAR)的工作原理,但似乎我漏掉了一些要点。

String time = "1998-12-31"; // year month day
java.util.Calendar date = java.util.Calendar.getInstance();
date.setTime((new java.text.SimpleDateFormat("yyyy-MM-dd")).parse(time));
System.err.println("Week of year = " + date.get(java.util.Calendar.WEEK_OF_YEAR));
// Week of year = 1 Why ???
为什么date.get(java.util.Calendar.WEEK_OF_YEAR)在一年的最后一周返回1?
此外,"1998-01-01"WEEK_OF_YEAR为1,而"1998-12-23"WEEK_OF_YEAR为52。有人能解释这种行为吗?

1
它对我来说打印了“年的第53周”。只需检查您发布的代码和您获得的输出是否来自同一位置。 - adarshr
这将是特定于区域设置的。您是否仔细阅读了关于WEEK_OF_YEAR的手册,并查看了getFirstDayOfWeek和getMinimalDaysInFirstWeek的值? - Corbin
3
在一些国家(例如美国,根据维基百科),第一周是从1月1日开始的那个星期。1998年12月31日是星期四,因此它属于1999年的第一周。 - mmaag
这是由于您的Locale设置引起的。请查看此问题的描述:https://dev59.com/SW445IYBdhLWcg3w9O7R - yatul
自1998年以来,由于该年有53周,因此应返回53。对我来说,它实际上打印了53。http://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=1998 - justSaid
显示剩余7条评论
2个回答

13

来自java.util.Calendar javadoc:

第一周

日历使用两个参数定义了一个特定于语言环境的七天星期:一周的第一天和第一周中最少的天数(从1到7)。当构建一个Calendar时,这些数字取自语言环境数据。它们也可以通过设定值的方法明确地指定。

在设置或获取WEEK_OF_MONTH或WEEK_OF_YEAR字段时,日历必须确定月份或年份的第一周作为参考点。一个月或年的第一周被定义为包含至少getMinimalDaysInFirstWeek()天,并从getFirstDayOfWeek()开始的最早的七天周期。编号为..., -1, 0的周在第一周之前;编号为2、3...的周跟随它。请注意,由get()返回的标准化编号可能不同。例如,特定的日历子类可能会将前一年的第1周之前的周指定为前一年的第n周。

因此,它是特定于语言环境的。在您的情况下,如果该周包含新年的日期,则将其视为新年的第1周。

您可以通过使用Calendar#setMinimalDaysInFirstWeek(int)来更改此行为。


谢谢。很遗憾,使用 setMinimalDaysInFirstWeek 在这里没有帮助(或者我不知道如何使用它)。如果将其设置为2,则会得到上述描述的行为。如果将其设置为7,则在“1999-01-01”中会得到52。 - khachik
3
这一周不能既是过去一年的最后一周,又是新一年的第一周。它要么是前者,要么是后者。 - npe

8

简而言之

java.time.LocalDate.parse( "1998-12-31" )
    .get( IsoFields.WEEK_OF_WEEK_BASED_YEAR )

53

或者,添加一个库,然后...

org.threeten.extra.YearWeek.from(     // Convert from a `LocalDate` object to a `YearWeek` object representing the entire week of that date’s week-based year.
    LocalDate.parse( "1998-12-31" )   // Parse string into a `LocalDate` objects. 
).getWeek()                           // Extract an integer number of that week of week-based-year, either 1-52 or 1-53 depending on the year.

53

详情

我试图理解java.util.Calendar.get(java.util.Calendar.WEEK_OF_YEAR)的工作原理

不要这样做!那个类很混乱,最好忘记它。

npe的答案是正确的。在Calendar中,一周的定义因语言环境而异。这是一个良好的意图,但令人困惑。

标准周定义

有许多方法来定义“一周”和“一年的第一周”。

然而,有一个主要的标准定义ISO 8601标准。该标准定义了一年中的周数,包括一年的第一周

一年中第一个星期四所在的那个星期

标准周从星期一开始,到星期日结束。

标准周年的第1周是指日历年中第一个星期四所在的那个星期。

java.time

java.time类取代了麻烦的旧日期时间类。这些现代化的类通过IsoFields 类支持 ISO 8601 周,该类包含三个实现 TemporalField 的常量:

调用 LocalDate::get 来访问 TemporalField

LocalDate ld = LocalDate.parse( "1998-12-31" ) ;
int weekOfWeekBasedYear = ld.get( IsoFields.WEEK_OF_WEEK_BASED_YEAR ) ;
int yearOfWeekBasedYear = ld.get( IsoFields.WEEK_BASED_YEAR ) ;

ld.toString(): 1998-12-31

weekOfWeekBasedYear: 53

yearOfWeekBasedYear: 1998

请注意,隔天即新的日历年1999年的第一天,也在同一周,即基于周的1998年的第53周。

LocalDate firstOf1999 = ld.plusDays( 1 );
int weekOfWeekBasedYear_FirstOf1999 = firstOf1999.get( IsoFields.WEEK_OF_WEEK_BASED_YEAR ) ;
int yearOfWeekBasedYear_FirstOf1999 = firstOf1999.get( IsoFields.WEEK_BASED_YEAR ) ;

firstOf1999.toString(): 1999-01-01

weekOfWeekBasedYear_FirstOf1999: 53

yearOfWeekBasedYear_FirstOf1999: 1998

ISO 8601字符串格式

ISO 8601标准定义了一种文本格式以及一个基于周的年份值的含义:yyyy-Www。 对于特定日期,添加星期一至星期日编号为1-7的星期几:yyyy-Www-d

构造这样一个字符串。

String outputWeek = ld.format( DateTimeFormatter.ISO_WEEK_DATE ) ;  // yyyy-Www 

1998-W53
String outputDate = outputWeek + "-" + ld.getDayOfWeek().getValue() ;  // yyyy-Www-d

1998-W53-4

YearWeek

如果您在项目中添加了ThreeTen-Extra库,这项工作就会更加容易。然后使用YearWeek类。

YearWeek yw = YearWeek.from( ld ) ;  // Determine ISO 8601 week of a `LocalDate`. 

生成标准字符串。
String output = yw.toString() ;

1998-W53

并解析。

YearWeek yearWeek = YearWeek.parse( "1998-W53" ) ;  

yearWeek.toString(): 1998-W53

确定一个日期。传递一个 java.time.DayOfWeek 枚举对象,表示星期一至星期日的某一天。

LocalDate localDate = yw.atDay( DayOfWeek.MONDAY ) ;

localDate.toString(): 1998-12-28

我强烈建议将这个库添加到您的项目中。然后,您可以传递智能对象而不是愚蠢的整数。这样做使您的代码更具自我记录性,提供了type-safety,并确保有效的值。


关于 java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了旧的遗留日期时间类,例如 java.util.Date, Calendar, 和 SimpleDateFormat

Joda-Time项目现在处于维护模式,建议迁移到java.time类。

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

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

如何获取java.time类?

ThreeTen-Extra项目通过增加类扩展了java.time。该项目是java.time可能未来添加的内容的试验场。您可能会在这里找到一些有用的类,例如Interval, YearWeek, YearQuarter更多


Joda-Time

更新:Joda-Time 项目现已进入维护模式,团队建议迁移到java.time 类。本节内容将保持不变作为历史记录。

优秀的 Joda-Time 框架使用 ISO 8601 作为其默认值。其类包括本周年份信息。Joda-Time 是 Java 自带的极具问题的 java.util.Date 和 java.util.Calendar 类的热门替代品。

示例代码

以下是一些示例代码,用于获取当前日期时间所在的年份的第一周的第一天的第一刻钟。

请注意调用 withTimeAtStartOfDay 来获取这一天的第一时刻。

DateTimeZone timeZone = DateTimeZone.forID( "Europe/Paris" );

DateTime now = new DateTime( timeZone );
DateTime firstWeekStart = now.withWeekOfWeekyear(1).withDayOfWeek(1).withTimeAtStartOfDay();
DateTime firstWeekStop = firstWeekStart.plusWeeks( 1 );
Interval firstWeek = new Interval( firstWeekStart, firstWeekStop );

将数据打印到控制台...

System.out.println( "now: " + now );
System.out.println( "firstWeekStart: " + firstWeekStart );
System.out.println( "firstWeekStop: " + firstWeekStop );
System.out.println( "firstWeek: " + firstWeek );

运行时...

now: 2014-02-07T12:49:33.623+01:00
firstWeekStart: 2013-12-30T00:00:00.000+01:00
firstWeekStop: 2014-01-06T00:00:00.000+01:00
firstWeek: 2013-12-30T00:00:00.000+01:00/2014-01-06T00:00:00.000+01:00

也许可以使用 java.time 进行更新? - OneCricketeer
1
@cricket_007 问吧,你就会得到。 - Basil Bourque

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接