How Does Java "Week Year" Really Work

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

SimpleDateFormat week year

TL;DR: The formatter uses its own week numbering

The date formatter is using its own week numbering scheme, not that of the date (in your case that of the Calendar object). I am demonstrating using java.time the modern Java date and time API. Specifically I am using a LocalDate. A LocalDate is always a date in the ISO calendar system.

    LocalDate date = LocalDate.of(1998, Month.JANUARY, 1);
DateTimeFormatter baseFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd");

DateTimeFormatter chinaFormatter = baseFormatter.withLocale(Locale.CHINA);
System.out.println("China: " + date.format(chinaFormatter));

DateTimeFormatter franceFormatter = baseFormatter.withLocale(Locale.FRANCE);
System.out.println("France: " + date.format(franceFormatter));

DateTimeFormatter irelandFormatter = baseFormatter.withLocale(Locale.forLanguageTag("en-IE"));
System.out.println("Ireland: " + date.format(irelandFormatter));

Output from this snippet is:

China:   1998-01-01
France: 1998-01-01
Ireland: 1997-01-01
  • If your default locale is China, your formatter was using China weeks. They begin on Sunday, and week 1 is the week that contains January 1 (like in the US, as another example). So January 1 of 1998 must belong to week year 1998.
  • France uses ISO numbering: The week begins on Monday, and as you said, in this case week 1 of 1998 begins on December 29, 1997. So we get 1998 again.
  • Ireland is one of the few countries using the mixed scheme that you were trying to obtain in your question: Minimal days in first week is 4 (as in ISO), and Sunday is the first day of the week (as in China and the US). So using a formatter with Ireland as locale country gives you the result you were trying to get, 1997.

A different way of obtaining a week year of 1997 from your date is to put your own WeekFields object together. Then you don’t need to worry about locale.

    WeekFields myWeekFields = WeekFields.of(DayOfWeek.SUNDAY, 4);
int weekYear = date.get(myWeekFields.weekBasedYear());
System.out.println("Week year: " + weekYear);

Week year: 1997

java.time??

The date and time classes that you were using, Calendar and SimpleDateFormat, are poorly designed and long outdated.

It seems from your document link that you are using Java 8, and if so, there’s absolutely no reason why you should struggle with the old classes. Use java.time as I do above. A backport for Java 6 and 7 exists too.

How come the code in the question didn’t work?

You were taking the time out of your Calendar using calendar.getTime(). This returns a Date (another poorly designed and long outdated class). A Date is a point in time, nothing more. It hasn’t got any notion of week number or week year. You passed this Date to your SimpleDateFormat, which had no way of detecting that the Date had originally come from a Calendar object with a certain setting of days in first week and first day of week. Instead the SimpleDateFormat uses its own definition of weeks. It always does that. Since you had not explicitly set a locale or calendar for the SimpleDateFormat, it used the default locale of your JVM. This produced a week year of 1998, as it would in the vast majority of locales.

SimpleDateFormat wrong week of year for last week

Don't use Calendar. It's obsolete, and, more to the point, incredibly bad API.

There's a list as long as my leg as to what's wrong with it. The specific one of about 200 things that is relevant here is that, boneheadedly, its month value is 0 indexed. So, '12, 3'? That's the 3rd of Undecimber, or whatever you'd like to call the 13th month. That, or the calendar doesn't do 13th month in which case it leniently just assumes you meant to say 3rd of jan 2022 . Either way, that's week 1.

So why is The 2nd of undecimber (or if you prefer, via rollover, 2nd jan 2022) "Week 52"?

Because it is.

Week numbering is weird, but they have to be. A week starts on monday (or sunday, for my silly standards loving USian brethren), and can't start on any other day. That means unless Jan 1st so happens to fall on a monday, there's going to be weirdness; either days in 2021 counting as being in 'week 1 of 2022', or days in 2022 counting as 'week 52 of 2021'. In fact, from time to time there'll have to be a week 53. After all, 52*7 is 364, but there are 365.2475 days in a year, so unless you just want to make some days poof out of existence, every so often a week 53 has to exist for it all to add up.

Use java.time instead.

LocalDate ld = LocalDate.of(2021, 12, 3);
WeekFields wf = WeekFields.of(Locale.ENGLISH);
int weekNumber = ld.get(wf.weekOfWeekBasedYear());

java.time does many things fantastically, and one of the things is does great is that it tends not to hide complex things. For example, 'when does a week start' is just not answerable, not unless you tell me where on the planet you're asking this question. Hence, 'which week is it' is also not actually an answerable question until you tell me exactly which week-counting system we're using, and there is no standard that is universally accepted enough. Hence, you have to go through the rigamarole of making a separate WeekFields instance to capture that info. We do it here based on locale.

Understanding java.util.Calendar WEEK_OF_YEAR

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

Date using 'YYYY'

For a concrete answer, the following table shows January 1 for each year from 2010 through 2020 with day-of-week, week-based year and week number in ISO (the international standard) and in the US.

Year  DOW  ISO      US
2010 Fri 2009-53 2010-01
2011 Sat 2010-52 2011-01
2012 Sun 2011-52 2012-01
2013 Tue 2013-01 2013-01
2014 Wed 2014-01 2014-01
2015 Thu 2015-01 2015-01
2016 Fri 2015-53 2016-01
2017 Sun 2016-52 2017-01
2018 Mon 2018-01 2018-01
2019 Tue 2019-01 2019-01
2020 Wed 2020-01 2020-01

As you can see, internationally January 1 regularly falls in week 52 or 53 of the previous year, while in the US it always falls in week 1 of its own year.

International rule: Week 1 is the first one that contains at least 4 days of the new year. In other words, a week belongs in the year where most of its days are. In yet other words, week 1 is the one that holds the first Thursday of the year (since weeks begin on Mondays). This implies that when January 1 is a Friday, Saturday or Sunday, it belongs to the last week of the previous year.

US rule: Week 1 is the week that contains January 1. That January 1 always falls in week 1 of its own year follows from this definition (weeks begin on Sundays).

The table came out of this snippet:

    System.out.println("Year  DOW  ISO      US");
for (int year = 2010; year <= 2020; year++) {
LocalDate jan1 = LocalDate.of(year, Month.JANUARY , 1);
System.out.format(Locale.ROOT, "%4d %3s %4d-%02d %4d-%02d%n",
year, jan1.getDayOfWeek().getDisplayName(TextStyle.SHORT_STANDALONE, Locale.ROOT),
jan1.get(WeekFields.ISO.weekBasedYear()), jan1.get(WeekFields.ISO.weekOfWeekBasedYear()),
jan1.get(WeekFields.SUNDAY_START.weekBasedYear()), jan1.get(WeekFields.SUNDAY_START.weekOfWeekBasedYear()));
}

I am of course using java.time, the modern Java date and time API. I warmly recommend it over the outdated Calendar, SimpleDateFormat and Date.

Java Time's week-of-week-based-year pattern parsing with DateTimeFormatter

The documentation of DateTimeFormatterBuilder specifies that "Y" appends the localized week-based year. Thus, the meaning of the week-based year and week fields will depend on the locale set in the formatter.



Related Topics



Leave a reply



Submit