10

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?

khachik
  • 28,112
  • 9
  • 59
  • 94
  • 1
    It prints `Week of year = 53` for me. Just check the code you've posted and the output you've got are from the same place. – adarshr Jun 05 '12 at 07:43
  • This will be locale specific. Have you carefully read the manual on WEEK_OF_YEAR and looked into your values for getFirstDayOfWeek and getMinimalDaysInFirstWeek? – Corbin Jun 05 '12 at 07:44
  • @adashr, checked, it prints 1. – khachik Jun 05 '12 at 07:46
  • @Corbin, "First day of week = 1" and "Minimal days in the first week = 1". But I still don't understand how this may affect the last week of the year to be 1. – khachik Jun 05 '12 at 07:50
  • 3
    In some countries (e.g. USA, according to Wikipedia) the first week is the week with the 1. January. The 31. Dez '98 was a Thursday so it is in the first week of 1999 – mmaag Jun 05 '12 at 07:55
  • Not 100% sure on this, but my take on it: 1 Jan 1999 is a Friday. Friday is > Sunday (1) and {Friday, Saturday} would make 2 days, thus > the 1 day minimum. This means that 1 Jan 1999 is in week 1. Since weeks starts on Sunday, this means that from 2 Jan 1999 to 28 Dec 1998 is week 1. Is 1998-12-23 where it shifts to 52, or is it a random test date? Can you confirm that only between 28 Dec and 2 Jan is week 1 (don't have access to Java at the moment). – Corbin Jun 05 '12 at 07:55
  • What is your timezone and locale? – yatul Jun 05 '12 at 07:55
  • @Corbin, confirmed (between 27 Dec - 2 Jan, since my week starts on Sunday). – khachik Jun 05 '12 at 08:04
  • @yatul, timezone is `id="Europe/Moscow",offset=14400000,dstSavings=0,useDaylight=false,transitions=78,lastRule=null`, locale is `en_US`. – khachik Jun 05 '12 at 08:05
  • @khachik Seems that's what's happening then. I guess didn't look hard enough for the long-form explanation of it in the manual as npe just posted below basically what I was guessing Java is doing :) – Corbin Jun 05 '12 at 08:06
  • This happened because of your Locale. Look at this question for description http://stackoverflow.com/q/4608470/891391 – yatul Jun 05 '12 at 08:14
  • Since 1998 has actually 53 weeks it should return 53. For me it actually prints 53. http://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=1998 – justSaid Sep 29 '14 at 11:10

2 Answers2

13

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

npe
  • 15,395
  • 1
  • 56
  • 55
  • thank you. Unfortunately using `setMinimalDaysInFirstWeek` does not help here (or I don't understand how to use it). If I set it to `, then I get the behavior described above. If I set it to 7, I get 52 for "1999-01-01". – khachik Jun 05 '12 at 08:11
  • 3
    Well, the week cannot be both - the last in the old year, and the first in new year. It's either one, or another. – npe Jun 05 '12 at 08:13
8

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:

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?

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
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154