Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding java.util.Calendar WEEK_OF_YEAR [duplicate]

I'm trying to understand how java.util.Calendar.get(java.util.Calendar.WEEK_OF_YEAR) works, but it seems that I'm missing some points.

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 ???

Why date.get(java.util.Calendar.WEEK_OF_YEAR) returns 1 for the last week of the year?

Moreover, WEEK_OF_YEAR for "1998-01-01" is 1 and for "1998-12-23" it is 52.
Does anybody have an explanation for this behavior?

like image 268
khachik Avatar asked Jun 05 '12 07:06

khachik


2 Answers

From java.util.Calendar javadoc:

First Week

Calendar defines a locale-specific seven day week using two parameters: the first day of the week and the minimal days in first week (from 1 to 7). These numbers are taken from the locale resource data when a Calendar is constructed. They may also be specified explicitly through the methods for setting their values.

When setting or getting the WEEK_OF_MONTH or WEEK_OF_YEAR fields, Calendar must determine the first week of the month or year as a reference point. The first week of a month or year is defined as the earliest seven day period beginning on getFirstDayOfWeek() and containing at least getMinimalDaysInFirstWeek() days of that month or year. Weeks numbered ..., -1, 0 precede the first week; weeks numbered 2, 3,... follow it. Note that the normalized numbering returned by get() may be different. For example, a specific Calendar subclass may designate the week before week 1 of a year as week n of the previous year.

So it's locale-specific. In your case, if the week contains days from new year, it is counted as week 1 from the new year.

You can change this behavior by using Calendar#setMinimalDaysInFirstWeek(int).

like image 98
npe Avatar answered Nov 07 '22 14:11

npe


tl;dr

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

53

Or, add a library, and then…

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

Details

I'm trying to understand how java.util.Calendar.get(java.util.Calendar.WEEK_OF_YEAR) works

Don’t! That class is a bloody mess, and best left forgotten.

The answer by npe is correct. In Calendar, the definition of a week varies by locale. A well-intentioned feature, but confusing.

Standard week definition

There are many ways to define “a week” and “first week of the year”.

However, there is one major standard definition: the ISO 8601 standard. That standard defines weeks of the year, including the first week of the year.

the week with the year's first Thursday

A standard week begins with Monday and ends with Sunday.

Week # 1 of a standard week-based-year has the first Thursday of the calendar-year.

java.time

The java.time classes supplanted the troublesome legacy date-time classes. These modern classes support the ISO 8601 week through the IsoFields class, holding three constants that implement TemporalField:

  • WEEK_OF_WEEK_BASED_YEAR
  • WEEK_BASED_YEAR
  • WEEK_BASED_YEARS

Call LocalDate::get to access the 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

Notice the day after, the first day of the new calendar year 1999, also is in the same week, week # 53 of week-based 1998.

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 string format

The ISO 8601 standard defines a textual format as well as a meaning for week-based-year values: yyyy-Www. For a specific date, add day-of-week numbered 1-7 for Monday-Sunday: yyyy-Www-d.

Construct such a string.

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

This work is much easier if you add the ThreeTen-Extra library to your project. Then use the YearWeek class.

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

Generate the standard string.

String output = yw.toString() ;

1998-W53

And parse.

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

yearWeek.toString(): 1998-W53

Determine a date. Pass a java.time.DayOfWeek enum object for day-of-week Monday-Sunday.

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

localDate.toString(): 1998-12-28

I strongly recommending adding this library to your project. Then you can pass around smart objects rather than dumb ints. Doing so makes your code more self-documenting, provides type-safety, and ensures valid values.


About java.time

The 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.

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

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

Using a JDBC driver compliant with JDBC 4.2 or later, you may exchange java.time objects directly with your database. No need for strings nor java.sql.* classes.

Where to obtain the java.time classes?

  • Java SE 8, Java SE 9, and later
    • Built-in.
    • Part of the standard Java API with a bundled implementation.
    • Java 9 adds some minor features and fixes.
  • Java SE 6 and Java SE 7
    • Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
  • Android
    • Later versions of Android bundle implementations of the java.time classes.
    • For earlier Android, the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.


Joda-Time

UPDATE: The Joda-Time project is now in maintenance mode, with the team advising migration to the java.time classes. This section left intact as history.

The excellent Joda-Time framework uses ISO 8601 for its defaults. Its classes include this week-of-year information. Joda-Time is a popular replacement for the notoriously troublesome java.util.Date & java.util.Calendar classes bundled with Java.

Example Code

Here is some example code to get first moment of the first day of the first week of the year of the current date-time.

Note the call to withTimeAtStartOfDay to get the first moment of the day.

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 );

Dump to console…

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

When run…

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
like image 8
Basil Bourque Avatar answered Nov 07 '22 16:11

Basil Bourque