How does Java "week year" really work?

It's simple: December 27 2015 is day 1 of week 1 of week-year 2016 (and December 27 2026 is day 1 of week 1 of week-year 2027). This can be verified by adding these lines:

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

If a SimpleDateFormat outputs a date it can use all fields: year, month, day, day of week, week of month, week in year, week-year etc.

On parsing, SimpleDateFormat expects a matching set of values: either day, month, year or day of week, week in year, week-year. Since you supplied a week-year but did not supply day of week and week in year, those to values have been assumed as 1.


The actual values depend on your locale:

  • which week of a year is week 1
  • which day is the first day of the week

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

On my system (using de-ch locale, with "EEE MMM dd HH:mm:ss zzz yyyy - YYYY-ww-u" as format) I get

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

Definitions vary

A week can be defined in different ways. For example, in the United States, the first day of the week is considered to be Sunday most often while in Europe and many other places the first day is Monday.

Likewise, the week of a week-based year can also be defined in different ways.

  • Week # 1 contains January 1.
  • Week # 1 is the first week to contain a particular day-of-week such as Sunday.
  • Week # 1 is the first week to contain only days of the new year, with no dates from the previous year.
  • … and more

The legacy classes you are using implicitly use the definitions specified by a Locale. The locale being applied is also implicit, using the JVM’s current default Locale if you do not otherwise specify.

For even more interesting details about the difficulty in defining a week, see this Question, Different behavior of WeekFields on JVM 8 and JVM 10

ISO 8601

There is a practical international standard for date-time handling, ISO 8601.

The ISO 8601 definition of a week is:

  • Weeks start on a Monday
  • Week # 1 has the first Thursday of the calendar year
  • A year consists of either 52 or 53 whole weeks.
  • A year may have one or more days from the previous calendar year, and also from the following calendar year.

I suggest using the ISO 8601 standard definition whenever possible. This standard definition is simple and logical, with increasing adoption across industries.

java.time

The java.time classes offer some support for week of week-based year in the WeekFields class.

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

See this code run live at IdeOne.com.

ld.toString(): 2019-01-01

week: 1

org.threeten.extra.YearWeek

But if doing much of this work, I suggest adding the ThreeTen-Extra library to your project. You will find the YearWeek class to be helpful. This class offers several handy methods such as generating a LocalDate for any day within that week.

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

Notice how the first day of the year in the week-based year of 2019 is a date from the previous calendar year, 2018 rather than 2019.

calendar for month of January 2019, with week number, showing first week starts in the previous calendar year on 2018-12-31


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.

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

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

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

Where to obtain the java.time classes?

  • Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - 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
  • Most 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 (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….